flowstudio-rn 0.0.1-alpha
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/LICENSE +21 -0
- package/README.md +160 -0
- package/android/build.gradle +41 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/id/bureau/flowstudio/rn/FlowStudioReactNativeModule.kt +173 -0
- package/app.plugin.js +2 -0
- package/build/FlowStudio.d.ts +21 -0
- package/build/FlowStudio.d.ts.map +1 -0
- package/build/FlowStudio.js +58 -0
- package/build/FlowStudio.js.map +1 -0
- package/build/FlowStudioReactNative.types.d.ts +48 -0
- package/build/FlowStudioReactNative.types.d.ts.map +1 -0
- package/build/FlowStudioReactNative.types.js +2 -0
- package/build/FlowStudioReactNative.types.js.map +1 -0
- package/build/FlowStudioReactNativeModule.d.ts +15 -0
- package/build/FlowStudioReactNativeModule.d.ts.map +1 -0
- package/build/FlowStudioReactNativeModule.js +3 -0
- package/build/FlowStudioReactNativeModule.js.map +1 -0
- package/build/FlowStudioReactNativeModule.web.d.ts +6 -0
- package/build/FlowStudioReactNativeModule.web.d.ts.map +1 -0
- package/build/FlowStudioReactNativeModule.web.js +6 -0
- package/build/FlowStudioReactNativeModule.web.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/FlowStudioReactNative.podspec +23 -0
- package/ios/FlowStudioReactNativeModule.swift +7 -0
- package/package.json +86 -0
- package/plugin/build/index.d.ts +3 -0
- package/plugin/build/index.js +29 -0
- package/src/FlowStudio.ts +77 -0
- package/src/FlowStudioReactNative.types.ts +45 -0
- package/src/FlowStudioReactNativeModule.ts +16 -0
- package/src/FlowStudioReactNativeModule.web.ts +6 -0
- package/src/index.ts +10 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# flowstudio-rn
|
|
2
|
+
|
|
3
|
+
React Native SDK for Bureau Flow Studio. It runs Flow Studio identity and verification journeys inside React Native and Expo apps, on top of Bureau's native FlowStudio SDK.
|
|
4
|
+
|
|
5
|
+
> **Alpha:** Android only for now. iOS support is coming. On iOS, `initialize()` and `start()` throw `FlowStudio: ios is not supported yet`, and `isInitialized()` returns `false`.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install flowstudio-rn@alpha
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Expo projects can use `npx expo install flowstudio-rn@alpha`.
|
|
14
|
+
|
|
15
|
+
### Android setup
|
|
16
|
+
|
|
17
|
+
The native FlowStudio SDK is downloaded from Bureau's Maven repository (`https://packages.bureau.id/api/packages/Bureau/maven`). The SDK registers this repository for your app automatically:
|
|
18
|
+
|
|
19
|
+
- **Expo (prebuild / CNG):** add the config plugin to `app.json`, then run `npx expo prebuild` (or `npx expo run:android`):
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"expo": {
|
|
24
|
+
"plugins": ["flowstudio-rn"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- **Bare React Native:** nothing to add. The library's Gradle file registers the repository for all projects.
|
|
30
|
+
|
|
31
|
+
If your `settings.gradle` sets `repositoriesMode` to `FAIL_ON_PROJECT_REPOS`, add the repository yourself under `dependencyResolutionManagement { repositories { ... } }`:
|
|
32
|
+
|
|
33
|
+
```groovy
|
|
34
|
+
maven { url "https://packages.bureau.id/api/packages/Bureau/maven" }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Requirements:** `minSdkVersion` 24 or higher. The SDK declares the `CAMERA` permission, which it uses for document capture and liveness.
|
|
38
|
+
|
|
39
|
+
## API Reference
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { FlowStudio } from 'flowstudio-rn';
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### `FlowStudio.initialize(config)`
|
|
46
|
+
|
|
47
|
+
Initializes the SDK. Call it before `start()`, for example once you have created the execution on your backend.
|
|
48
|
+
|
|
49
|
+
**Parameters (`FlowStudioConfig`):**
|
|
50
|
+
|
|
51
|
+
- `credentialId` (string): your Bureau credential ID
|
|
52
|
+
- `deviceCredentialId` (string): credential ID used for device intelligence
|
|
53
|
+
- `executionId` (string): ID of the Flow Studio execution to run
|
|
54
|
+
- `environment` (string, optional): `'PROD'`, `'STAGING'` or `'DEV'` (default: `'PROD'`)
|
|
55
|
+
- `enableLogging` (boolean, optional): enables SDK logs (default: `false`)
|
|
56
|
+
|
|
57
|
+
It throws an `Error` if a required field is missing or `environment` is invalid.
|
|
58
|
+
|
|
59
|
+
### `FlowStudio.start()`
|
|
60
|
+
|
|
61
|
+
Opens the Flow Studio journey. The promise resolves when the journey ends.
|
|
62
|
+
|
|
63
|
+
**Returns:** `Promise<FlowStudioResult>`
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
type FlowStudioResult =
|
|
67
|
+
| { type: 'result'; response: ExecutionResponse } // journey finished
|
|
68
|
+
| { type: 'exit' }; // user left the journey without a result
|
|
69
|
+
|
|
70
|
+
interface ExecutionResponse {
|
|
71
|
+
executionId: string;
|
|
72
|
+
merchantId: string;
|
|
73
|
+
requestId: string;
|
|
74
|
+
executionStatus: string; // 'PROCESSING' | 'AWAITING' | 'COMPLETED' | 'TIMEOUT' | 'ERROR'
|
|
75
|
+
currentState: Record<string, unknown>;
|
|
76
|
+
createdAt: number; // epoch timestamp
|
|
77
|
+
updatedAt: number; // epoch timestamp
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Only one journey can run at a time. `start()` can be called again once the previous call has resolved.
|
|
82
|
+
|
|
83
|
+
### `FlowStudio.isInitialized()`
|
|
84
|
+
|
|
85
|
+
**Returns:** `boolean`, which is `true` once `initialize()` has succeeded.
|
|
86
|
+
|
|
87
|
+
## Usage Example
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { FlowStudio } from 'flowstudio-rn';
|
|
91
|
+
|
|
92
|
+
async function runVerification(executionId: string) {
|
|
93
|
+
FlowStudio.initialize({
|
|
94
|
+
credentialId: 'your-credential-id',
|
|
95
|
+
deviceCredentialId: 'your-device-credential-id',
|
|
96
|
+
executionId,
|
|
97
|
+
environment: 'STAGING', // 'PROD' for live
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const result = await FlowStudio.start();
|
|
102
|
+
|
|
103
|
+
if (result.type === 'result') {
|
|
104
|
+
console.log('Status:', result.response.executionStatus);
|
|
105
|
+
} else {
|
|
106
|
+
console.log('User exited the journey');
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('FlowStudio failed:', error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Errors
|
|
115
|
+
|
|
116
|
+
`initialize()` and `start()` throw or reject with an `Error`. Errors that come from the native side carry a `code`:
|
|
117
|
+
|
|
118
|
+
| Code | Meaning |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `ERR_ALREADY_STARTED` | `start()` was called while a journey is already running |
|
|
121
|
+
| `ERR_NO_CONTEXT` | No Activity was available to open the journey |
|
|
122
|
+
| `ERR_START_FAILED` | The native SDK failed to start the journey (see `message`) |
|
|
123
|
+
| `ERR_INVALID_ENVIRONMENT` | Unknown `environment` value |
|
|
124
|
+
| `ERR_MODULE_DESTROYED` | The app reloaded or the React context was torn down during a journey |
|
|
125
|
+
|
|
126
|
+
## Notes
|
|
127
|
+
|
|
128
|
+
- **Execution ID:** create a new execution for each journey. Execution IDs are single use.
|
|
129
|
+
- **Environment:** use `'STAGING'` for testing and `'PROD'` for live traffic. Credentials are environment specific.
|
|
130
|
+
- **Exit handling:** a `{ type: 'exit' }` result is not an error. It means the user closed the journey.
|
|
131
|
+
|
|
132
|
+
## Example App
|
|
133
|
+
|
|
134
|
+
The `example/` directory contains an Expo app that initializes the SDK and starts a journey. To run it on a connected Android device:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
npm install
|
|
138
|
+
cd example && npm install && npx expo run:android
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Releasing (maintainers)
|
|
142
|
+
|
|
143
|
+
Releases are published from GitHub Actions ([`.github/workflows/release.yml`](.github/workflows/release.yml)) when a version tag (`1.2.3`, `1.2.3-alpha`, or the same with a leading `v`) is pushed to a commit on `main`:
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
git tag 0.0.1-alpha
|
|
147
|
+
git push origin 0.0.1-alpha
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
You can also create the tag from the GitHub UI under **Releases → Draft a new release**.
|
|
151
|
+
|
|
152
|
+
- The tag sets the published version. Keep `package.json` in sync through a PR before tagging.
|
|
153
|
+
- Prerelease tags (`-alpha`, `-beta`, `-rc`, ...) publish to the matching npm dist-tag. They never become `latest`.
|
|
154
|
+
- The workflow publishes to npm and creates a GitHub Release with generated notes and the package tarball.
|
|
155
|
+
- **Actions → Release → Run workflow** does a dry run: the same checks and build, with nothing published.
|
|
156
|
+
- The workflow needs an `NPM_TOKEN` repository secret: an npm automation token for the publishing account.
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
plugins {
|
|
2
|
+
id 'com.android.library'
|
|
3
|
+
id 'expo-module-gradle-plugin'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
def bureauMavenUrl = "https://packages.bureau.id/api/packages/Bureau/maven"
|
|
7
|
+
|
|
8
|
+
// Make the Bureau Maven repository available to the host app and all its subprojects, so
|
|
9
|
+
// clients (Expo or bare React Native) do not need to add it to their android/build.gradle.
|
|
10
|
+
// The Expo config plugin (app.plugin.js) adds it too, for apps that regenerate android/.
|
|
11
|
+
rootProject.allprojects {
|
|
12
|
+
repositories {
|
|
13
|
+
maven { url bureauMavenUrl }
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def packageJson = new groovy.json.JsonSlurper().parse(file("../package.json"))
|
|
18
|
+
|
|
19
|
+
group = 'id.bureau.flowstudio.rn'
|
|
20
|
+
version = packageJson.version
|
|
21
|
+
|
|
22
|
+
repositories {
|
|
23
|
+
google()
|
|
24
|
+
mavenCentral()
|
|
25
|
+
maven { url bureauMavenUrl }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
android {
|
|
29
|
+
namespace "id.bureau.flowstudio.rn"
|
|
30
|
+
defaultConfig {
|
|
31
|
+
versionCode 1
|
|
32
|
+
versionName packageJson.version
|
|
33
|
+
}
|
|
34
|
+
lintOptions {
|
|
35
|
+
abortOnError false
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
dependencies {
|
|
40
|
+
implementation 'id.bureau:flowstudio:0.0.3-alpha'
|
|
41
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
package id.bureau.flowstudio.rn
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.Application
|
|
5
|
+
import android.os.Bundle
|
|
6
|
+
import android.os.Handler
|
|
7
|
+
import android.os.Looper
|
|
8
|
+
import com.bureau.flowstudio.FlowStudio
|
|
9
|
+
import com.bureau.flowstudio.FlowStudioCallback
|
|
10
|
+
import com.bureau.flowstudio.FlowStudioConfig
|
|
11
|
+
import com.bureau.flowstudio.FlowStudioEnvironment
|
|
12
|
+
import com.bureau.flowstudio.ui.FlowStudioActivity
|
|
13
|
+
import com.bureau.network.data.model.network.ExecutionResponse
|
|
14
|
+
import expo.modules.kotlin.Promise
|
|
15
|
+
import expo.modules.kotlin.exception.CodedException
|
|
16
|
+
import expo.modules.kotlin.exception.Exceptions
|
|
17
|
+
import expo.modules.kotlin.functions.Queues
|
|
18
|
+
import expo.modules.kotlin.modules.Module
|
|
19
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
20
|
+
import expo.modules.kotlin.records.Field
|
|
21
|
+
import expo.modules.kotlin.records.Record
|
|
22
|
+
import java.util.concurrent.atomic.AtomicReference
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Config received from JS. Mirrors `Required<FlowStudioConfig>` in `src/FlowStudio.ts`;
|
|
26
|
+
* defaults are already applied on the JS side.
|
|
27
|
+
*/
|
|
28
|
+
class FlowStudioConfigRecord : Record {
|
|
29
|
+
@Field val credentialId: String = ""
|
|
30
|
+
@Field val deviceCredentialId: String = ""
|
|
31
|
+
@Field val executionId: String = ""
|
|
32
|
+
@Field val environment: String = "PROD"
|
|
33
|
+
@Field val enableLogging: Boolean = false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Android implementation of the bridge contract defined in `src/FlowStudio.ts`
|
|
38
|
+
* (`FlowStudioNativeContract`). Function names must match it exactly.
|
|
39
|
+
*/
|
|
40
|
+
class FlowStudioReactNativeModule : Module() {
|
|
41
|
+
// Promise of the flow currently running, if any. Resolved exactly once.
|
|
42
|
+
private val pendingStart = AtomicReference<Promise?>(null)
|
|
43
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
44
|
+
private var flowActivityWatcher: Application.ActivityLifecycleCallbacks? = null
|
|
45
|
+
|
|
46
|
+
private fun settle(block: (Promise) -> Unit) {
|
|
47
|
+
pendingStart.getAndSet(null)?.let { promise ->
|
|
48
|
+
stopWatchingFlowActivity()
|
|
49
|
+
block(promise)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The SDK does not invoke FlowStudioCallback when the user backs out of the flow.
|
|
55
|
+
* Resolve as `exit` once FlowStudioActivity is finished for good, unless the SDK
|
|
56
|
+
* reports a result first (the delay gives its callback time to arrive).
|
|
57
|
+
*/
|
|
58
|
+
private fun watchFlowActivity(application: Application) {
|
|
59
|
+
stopWatchingFlowActivity()
|
|
60
|
+
val watcher = object : Application.ActivityLifecycleCallbacks {
|
|
61
|
+
override fun onActivityDestroyed(activity: Activity) {
|
|
62
|
+
if (activity is FlowStudioActivity && activity.isFinishing) {
|
|
63
|
+
mainHandler.postDelayed({ settle { it.resolve(mapOf("type" to "exit")) } }, EXIT_FALLBACK_DELAY_MS)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = Unit
|
|
68
|
+
override fun onActivityStarted(activity: Activity) = Unit
|
|
69
|
+
override fun onActivityResumed(activity: Activity) = Unit
|
|
70
|
+
override fun onActivityPaused(activity: Activity) = Unit
|
|
71
|
+
override fun onActivityStopped(activity: Activity) = Unit
|
|
72
|
+
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit
|
|
73
|
+
}
|
|
74
|
+
application.registerActivityLifecycleCallbacks(watcher)
|
|
75
|
+
flowActivityWatcher = watcher
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private fun stopWatchingFlowActivity() {
|
|
79
|
+
val watcher = flowActivityWatcher ?: return
|
|
80
|
+
flowActivityWatcher = null
|
|
81
|
+
(appContext.reactContext?.applicationContext as? Application)
|
|
82
|
+
?.unregisterActivityLifecycleCallbacks(watcher)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override fun definition() = ModuleDefinition {
|
|
86
|
+
Name("FlowStudioReactNative")
|
|
87
|
+
|
|
88
|
+
// initialize(config: Required<FlowStudioConfig>): void
|
|
89
|
+
Function("initialize") { config: FlowStudioConfigRecord ->
|
|
90
|
+
val application = appContext.reactContext?.applicationContext as? Application
|
|
91
|
+
?: throw Exceptions.ReactContextLost()
|
|
92
|
+
|
|
93
|
+
val environment = FlowStudioEnvironment.entries.find { it.name == config.environment }
|
|
94
|
+
?: throw CodedException("ERR_INVALID_ENVIRONMENT", "Unknown environment: ${config.environment}", null)
|
|
95
|
+
|
|
96
|
+
FlowStudio.initialize(
|
|
97
|
+
application,
|
|
98
|
+
FlowStudioConfig(
|
|
99
|
+
config.enableLogging,
|
|
100
|
+
config.credentialId,
|
|
101
|
+
environment,
|
|
102
|
+
config.deviceCredentialId,
|
|
103
|
+
config.executionId,
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// start(): Promise<FlowStudioResult>
|
|
109
|
+
// FlowStudio.start() must be called on the main thread.
|
|
110
|
+
AsyncFunction("start") { promise: Promise ->
|
|
111
|
+
if (!FlowStudio.isInitialized()) {
|
|
112
|
+
promise.reject("ERR_NOT_INITIALIZED", "Call initialize() before start()", null)
|
|
113
|
+
return@AsyncFunction
|
|
114
|
+
}
|
|
115
|
+
if (!pendingStart.compareAndSet(null, promise)) {
|
|
116
|
+
promise.reject("ERR_ALREADY_STARTED", "A FlowStudio flow is already in progress", null)
|
|
117
|
+
return@AsyncFunction
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
val context = appContext.currentActivity ?: appContext.reactContext
|
|
121
|
+
if (context == null) {
|
|
122
|
+
pendingStart.set(null)
|
|
123
|
+
promise.reject("ERR_NO_CONTEXT", "No Activity or React context available to start FlowStudio", null)
|
|
124
|
+
return@AsyncFunction
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
(context.applicationContext as? Application)?.let(::watchFlowActivity)
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
FlowStudio.start(context, object : FlowStudioCallback {
|
|
131
|
+
override fun onResult(response: ExecutionResponse) {
|
|
132
|
+
settle { it.resolve(mapOf("type" to "result", "response" to response.toMap())) }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
override fun onExit() {
|
|
136
|
+
settle { it.resolve(mapOf("type" to "exit")) }
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
} catch (e: Exception) {
|
|
140
|
+
settle { it.reject("ERR_START_FAILED", e.message ?: "Failed to start FlowStudio", e) }
|
|
141
|
+
}
|
|
142
|
+
}.runOnQueue(Queues.MAIN)
|
|
143
|
+
|
|
144
|
+
// isInitialized(): boolean
|
|
145
|
+
Function("isInitialized") {
|
|
146
|
+
FlowStudio.isInitialized()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
OnDestroy {
|
|
150
|
+
mainHandler.removeCallbacksAndMessages(null)
|
|
151
|
+
settle { it.reject("ERR_MODULE_DESTROYED", "FlowStudio module was destroyed before the flow finished", null) }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private companion object {
|
|
156
|
+
const val EXIT_FALLBACK_DELAY_MS = 500L
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private fun ExecutionResponse.toMap(): Map<String, Any> = mapOf(
|
|
161
|
+
"executionId" to executionId,
|
|
162
|
+
"merchantId" to merchantId,
|
|
163
|
+
"requestId" to requestId,
|
|
164
|
+
"executionStatus" to executionStatus.name,
|
|
165
|
+
"currentState" to mapOf(
|
|
166
|
+
"type" to currentState.type.name,
|
|
167
|
+
"popCurrent" to currentState.popCurrent,
|
|
168
|
+
"screenType" to currentState.screenType.name,
|
|
169
|
+
"screenId" to currentState.screenId,
|
|
170
|
+
),
|
|
171
|
+
"createdAt" to createdAt,
|
|
172
|
+
"updatedAt" to updatedAt,
|
|
173
|
+
)
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FlowStudioConfig, FlowStudioResult } from './FlowStudioReactNative.types';
|
|
2
|
+
/**
|
|
3
|
+
* Initializes the Flow Studio SDK. Must be called before `start()`.
|
|
4
|
+
*/
|
|
5
|
+
declare function initialize(config: FlowStudioConfig): void;
|
|
6
|
+
/**
|
|
7
|
+
* Launches the Flow Studio flow. Resolves when the flow finishes
|
|
8
|
+
* (`type: 'result'`) or the user exits it (`type: 'exit'`).
|
|
9
|
+
*/
|
|
10
|
+
declare function start(): Promise<FlowStudioResult>;
|
|
11
|
+
/**
|
|
12
|
+
* Returns whether `initialize()` has completed successfully.
|
|
13
|
+
*/
|
|
14
|
+
declare function isInitialized(): boolean;
|
|
15
|
+
export declare const FlowStudio: {
|
|
16
|
+
initialize: typeof initialize;
|
|
17
|
+
start: typeof start;
|
|
18
|
+
isInitialized: typeof isInitialized;
|
|
19
|
+
};
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=FlowStudio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowStudio.d.ts","sourceRoot":"","sources":["../src/FlowStudio.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,gBAAgB,EAEhB,gBAAgB,EACjB,MAAM,+BAA+B,CAAC;AAsBvC;;GAEG;AACH,iBAAS,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAoBlD;AAED;;;GAGG;AACH,iBAAe,KAAK,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAMhD;AAED;;GAEG;AACH,iBAAS,aAAa,IAAI,OAAO,CAEhC;AAED,eAAO,MAAM,UAAU;;;;CAItB,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import native from './FlowStudioReactNativeModule';
|
|
3
|
+
const ENVIRONMENTS = ['PROD', 'STAGING', 'DEV'];
|
|
4
|
+
// Only the Android native module is implemented so far.
|
|
5
|
+
const isSupportedPlatform = Platform.OS === 'android';
|
|
6
|
+
function requireSupportedPlatform() {
|
|
7
|
+
if (!isSupportedPlatform) {
|
|
8
|
+
throw new Error(`FlowStudio: ${Platform.OS} is not supported yet (Android only in this release)`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function requireNonEmpty(value, name) {
|
|
12
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
13
|
+
throw new Error(`FlowStudio: \`${name}\` is required and must be a non-empty string`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Initializes the Flow Studio SDK. Must be called before `start()`.
|
|
18
|
+
*/
|
|
19
|
+
function initialize(config) {
|
|
20
|
+
requireSupportedPlatform();
|
|
21
|
+
requireNonEmpty(config?.credentialId, 'credentialId');
|
|
22
|
+
requireNonEmpty(config.deviceCredentialId, 'deviceCredentialId');
|
|
23
|
+
requireNonEmpty(config.executionId, 'executionId');
|
|
24
|
+
const environment = config.environment ?? 'PROD';
|
|
25
|
+
if (!ENVIRONMENTS.includes(environment)) {
|
|
26
|
+
throw new Error(`FlowStudio: \`environment\` must be one of ${ENVIRONMENTS.join(', ')}, got "${environment}"`);
|
|
27
|
+
}
|
|
28
|
+
native.initialize({
|
|
29
|
+
credentialId: config.credentialId,
|
|
30
|
+
deviceCredentialId: config.deviceCredentialId,
|
|
31
|
+
executionId: config.executionId,
|
|
32
|
+
environment,
|
|
33
|
+
enableLogging: config.enableLogging ?? false,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Launches the Flow Studio flow. Resolves when the flow finishes
|
|
38
|
+
* (`type: 'result'`) or the user exits it (`type: 'exit'`).
|
|
39
|
+
*/
|
|
40
|
+
async function start() {
|
|
41
|
+
requireSupportedPlatform();
|
|
42
|
+
if (!native.isInitialized()) {
|
|
43
|
+
throw new Error('FlowStudio: call `initialize()` before `start()`');
|
|
44
|
+
}
|
|
45
|
+
return native.start();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Returns whether `initialize()` has completed successfully.
|
|
49
|
+
*/
|
|
50
|
+
function isInitialized() {
|
|
51
|
+
return isSupportedPlatform && native.isInitialized();
|
|
52
|
+
}
|
|
53
|
+
export const FlowStudio = {
|
|
54
|
+
initialize,
|
|
55
|
+
start,
|
|
56
|
+
isInitialized,
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=FlowStudio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowStudio.js","sourceRoot":"","sources":["../src/FlowStudio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAOxC,OAAO,MAAM,MAAM,+BAA+B,CAAC;AAEnD,MAAM,YAAY,GAAqC,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;AAElF,wDAAwD;AACxD,MAAM,mBAAmB,GAAG,QAAQ,CAAC,EAAE,KAAK,SAAS,CAAC;AAEtD,SAAS,wBAAwB;IAC/B,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,eAAe,QAAQ,CAAC,EAAE,sDAAsD,CACjF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,KAAc,EAAE,IAAY;IACnD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,+CAA+C,CAAC,CAAC;IACxF,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,UAAU,CAAC,MAAwB;IAC1C,wBAAwB,EAAE,CAAC;IAC3B,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;IACtD,eAAe,CAAC,MAAM,CAAC,kBAAkB,EAAE,oBAAoB,CAAC,CAAC;IACjE,eAAe,CAAC,MAAM,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAEnD,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC;IACjD,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CACb,8CAA8C,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,WAAW,GAAG,CAC9F,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,UAAU,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;QAC7C,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,WAAW;QACX,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,KAAK;KAC7C,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,KAAK;IAClB,wBAAwB,EAAE,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,EAAE,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,SAAS,aAAa;IACpB,OAAO,mBAAmB,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;AACvD,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,UAAU;IACV,KAAK;IACL,aAAa;CACd,CAAC","sourcesContent":["import { Platform } from 'react-native';\n\nimport {\n FlowStudioConfig,\n FlowStudioEnvironment,\n FlowStudioResult,\n} from './FlowStudioReactNative.types';\nimport native from './FlowStudioReactNativeModule';\n\nconst ENVIRONMENTS: readonly FlowStudioEnvironment[] = ['PROD', 'STAGING', 'DEV'];\n\n// Only the Android native module is implemented so far.\nconst isSupportedPlatform = Platform.OS === 'android';\n\nfunction requireSupportedPlatform(): void {\n if (!isSupportedPlatform) {\n throw new Error(\n `FlowStudio: ${Platform.OS} is not supported yet (Android only in this release)`\n );\n }\n}\n\nfunction requireNonEmpty(value: unknown, name: string): void {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new Error(`FlowStudio: \\`${name}\\` is required and must be a non-empty string`);\n }\n}\n\n/**\n * Initializes the Flow Studio SDK. Must be called before `start()`.\n */\nfunction initialize(config: FlowStudioConfig): void {\n requireSupportedPlatform();\n requireNonEmpty(config?.credentialId, 'credentialId');\n requireNonEmpty(config.deviceCredentialId, 'deviceCredentialId');\n requireNonEmpty(config.executionId, 'executionId');\n\n const environment = config.environment ?? 'PROD';\n if (!ENVIRONMENTS.includes(environment)) {\n throw new Error(\n `FlowStudio: \\`environment\\` must be one of ${ENVIRONMENTS.join(', ')}, got \"${environment}\"`\n );\n }\n\n native.initialize({\n credentialId: config.credentialId,\n deviceCredentialId: config.deviceCredentialId,\n executionId: config.executionId,\n environment,\n enableLogging: config.enableLogging ?? false,\n });\n}\n\n/**\n * Launches the Flow Studio flow. Resolves when the flow finishes\n * (`type: 'result'`) or the user exits it (`type: 'exit'`).\n */\nasync function start(): Promise<FlowStudioResult> {\n requireSupportedPlatform();\n if (!native.isInitialized()) {\n throw new Error('FlowStudio: call `initialize()` before `start()`');\n }\n return native.start();\n}\n\n/**\n * Returns whether `initialize()` has completed successfully.\n */\nfunction isInitialized(): boolean {\n return isSupportedPlatform && native.isInitialized();\n}\n\nexport const FlowStudio = {\n initialize,\n start,\n isInitialized,\n};\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend environment the Flow Studio SDK connects to.
|
|
3
|
+
* Mirrors native `FlowStudioEnvironment` (PROD | STAGING | DEV).
|
|
4
|
+
*/
|
|
5
|
+
export type FlowStudioEnvironment = 'PROD' | 'STAGING' | 'DEV';
|
|
6
|
+
/**
|
|
7
|
+
* Configuration passed to `FlowStudio.initialize()`.
|
|
8
|
+
* Mirrors native `FlowStudioConfig`.
|
|
9
|
+
*/
|
|
10
|
+
export interface FlowStudioConfig {
|
|
11
|
+
/** Credential ID issued by Bureau. */
|
|
12
|
+
credentialId: string;
|
|
13
|
+
/** Credential ID used for device intelligence. */
|
|
14
|
+
deviceCredentialId: string;
|
|
15
|
+
/** ID of the flow execution to run. */
|
|
16
|
+
executionId: string;
|
|
17
|
+
/** Defaults to `'PROD'`. */
|
|
18
|
+
environment?: FlowStudioEnvironment;
|
|
19
|
+
/** Enables SDK logging. Defaults to `false`. */
|
|
20
|
+
enableLogging?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Result of a completed flow execution.
|
|
24
|
+
* Mirrors native `ExecutionResponse`.
|
|
25
|
+
*/
|
|
26
|
+
export interface ExecutionResponse {
|
|
27
|
+
executionId: string;
|
|
28
|
+
merchantId: string;
|
|
29
|
+
requestId: string;
|
|
30
|
+
executionStatus: string;
|
|
31
|
+
currentState: Record<string, unknown>;
|
|
32
|
+
/** Epoch timestamp. */
|
|
33
|
+
createdAt: number;
|
|
34
|
+
/** Epoch timestamp. */
|
|
35
|
+
updatedAt: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Outcome of `FlowStudio.start()`. Mirrors native `FlowStudioCallback`:
|
|
39
|
+
* - `result` → `onResult(response)`
|
|
40
|
+
* - `exit` → `onExit()` (user left the flow without a result)
|
|
41
|
+
*/
|
|
42
|
+
export type FlowStudioResult = {
|
|
43
|
+
type: 'result';
|
|
44
|
+
response: ExecutionResponse;
|
|
45
|
+
} | {
|
|
46
|
+
type: 'exit';
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=FlowStudioReactNative.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowStudioReactNative.types.d.ts","sourceRoot":"","sources":["../src/FlowStudioReactNative.types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,CAAC;AAE/D;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,gDAAgD;IAChD,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,uBAAuB;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowStudioReactNative.types.js","sourceRoot":"","sources":["../src/FlowStudioReactNative.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Backend environment the Flow Studio SDK connects to.\n * Mirrors native `FlowStudioEnvironment` (PROD | STAGING | DEV).\n */\nexport type FlowStudioEnvironment = 'PROD' | 'STAGING' | 'DEV';\n\n/**\n * Configuration passed to `FlowStudio.initialize()`.\n * Mirrors native `FlowStudioConfig`.\n */\nexport interface FlowStudioConfig {\n /** Credential ID issued by Bureau. */\n credentialId: string;\n /** Credential ID used for device intelligence. */\n deviceCredentialId: string;\n /** ID of the flow execution to run. */\n executionId: string;\n /** Defaults to `'PROD'`. */\n environment?: FlowStudioEnvironment;\n /** Enables SDK logging. Defaults to `false`. */\n enableLogging?: boolean;\n}\n\n/**\n * Result of a completed flow execution.\n * Mirrors native `ExecutionResponse`.\n */\nexport interface ExecutionResponse {\n executionId: string;\n merchantId: string;\n requestId: string;\n executionStatus: string;\n currentState: Record<string, unknown>;\n /** Epoch timestamp. */\n createdAt: number;\n /** Epoch timestamp. */\n updatedAt: number;\n}\n\n/**\n * Outcome of `FlowStudio.start()`. Mirrors native `FlowStudioCallback`:\n * - `result` → `onResult(response)`\n * - `exit` → `onExit()` (user left the flow without a result)\n */\nexport type FlowStudioResult = { type: 'result'; response: ExecutionResponse } | { type: 'exit' };\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NativeModule } from 'expo';
|
|
2
|
+
import { FlowStudioConfig, FlowStudioResult } from './FlowStudioReactNative.types';
|
|
3
|
+
/**
|
|
4
|
+
* Contract the native modules (Kotlin on Android, Swift on iOS) must implement.
|
|
5
|
+
* Names must match `Function(...)` / `AsyncFunction(...)` in the native modules exactly.
|
|
6
|
+
* Only plain data crosses the bridge, so config is passed fully resolved.
|
|
7
|
+
*/
|
|
8
|
+
declare class FlowStudioReactNativeModule extends NativeModule<{}> {
|
|
9
|
+
initialize(config: Required<FlowStudioConfig>): void;
|
|
10
|
+
start(): Promise<FlowStudioResult>;
|
|
11
|
+
isInitialized(): boolean;
|
|
12
|
+
}
|
|
13
|
+
declare const _default: FlowStudioReactNativeModule;
|
|
14
|
+
export default _default;
|
|
15
|
+
//# sourceMappingURL=FlowStudioReactNativeModule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowStudioReactNativeModule.d.ts","sourceRoot":"","sources":["../src/FlowStudioReactNativeModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEnF;;;;GAIG;AACH,OAAO,OAAO,2BAA4B,SAAQ,YAAY,CAAC,EAAE,CAAC;IAChE,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GAAG,IAAI;IACpD,KAAK,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAClC,aAAa,IAAI,OAAO;CACzB;;AAED,wBAAyF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowStudioReactNativeModule.js","sourceRoot":"","sources":["../src/FlowStudioReactNativeModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAezD,eAAe,mBAAmB,CAA8B,uBAAuB,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from 'expo';\n\nimport { FlowStudioConfig, FlowStudioResult } from './FlowStudioReactNative.types';\n\n/**\n * Contract the native modules (Kotlin on Android, Swift on iOS) must implement.\n * Names must match `Function(...)` / `AsyncFunction(...)` in the native modules exactly.\n * Only plain data crosses the bridge, so config is passed fully resolved.\n */\ndeclare class FlowStudioReactNativeModule extends NativeModule<{}> {\n initialize(config: Required<FlowStudioConfig>): void;\n start(): Promise<FlowStudioResult>;\n isInitialized(): boolean;\n}\n\nexport default requireNativeModule<FlowStudioReactNativeModule>('FlowStudioReactNative');\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowStudioReactNativeModule.web.d.ts","sourceRoot":"","sources":["../src/FlowStudioReactNativeModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,YAAY,EAAE,MAAM,MAAM,CAAC;AAGvD,cAAM,2BAA4B,SAAQ,YAAY,CAAC,EAAE,CAAC;CAAG;;AAE7D,wBAA6F"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { registerWebModule, NativeModule } from 'expo';
|
|
2
|
+
// FlowStudioReactNativeModule is not available on the web platform.
|
|
3
|
+
class FlowStudioReactNativeModule extends NativeModule {
|
|
4
|
+
}
|
|
5
|
+
export default registerWebModule(FlowStudioReactNativeModule, 'FlowStudioReactNativeModule');
|
|
6
|
+
//# sourceMappingURL=FlowStudioReactNativeModule.web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowStudioReactNativeModule.web.js","sourceRoot":"","sources":["../src/FlowStudioReactNativeModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEvD,oEAAoE;AACpE,MAAM,2BAA4B,SAAQ,YAAgB;CAAG;AAE7D,eAAe,iBAAiB,CAAC,2BAA2B,EAAE,6BAA6B,CAAC,CAAC","sourcesContent":["import { registerWebModule, NativeModule } from 'expo';\n\n// FlowStudioReactNativeModule is not available on the web platform.\nclass FlowStudioReactNativeModule extends NativeModule<{}> {}\n\nexport default registerWebModule(FlowStudioReactNativeModule, 'FlowStudioReactNativeModule');\n"]}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAErD,YAAY,EACV,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,+BAA+B,CAAC"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC","sourcesContent":["// Public API of flowstudio-rn. Only what is exported here is available to clients.\nexport { FlowStudio } from './FlowStudio';\nexport { FlowStudio as default } from './FlowStudio';\n\nexport type {\n ExecutionResponse,\n FlowStudioConfig,\n FlowStudioEnvironment,\n FlowStudioResult,\n} from './FlowStudioReactNative.types';\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Pod::Spec.new do |s|
|
|
2
|
+
s.name = 'FlowStudioReactNative'
|
|
3
|
+
s.version = '1.0.0'
|
|
4
|
+
s.summary = 'A sample project summary'
|
|
5
|
+
s.description = 'A sample project description'
|
|
6
|
+
s.author = ''
|
|
7
|
+
s.homepage = 'https://docs.expo.dev/modules/'
|
|
8
|
+
s.platforms = {
|
|
9
|
+
:ios => '16.4',
|
|
10
|
+
:tvos => '16.4'
|
|
11
|
+
}
|
|
12
|
+
s.source = { git: '' }
|
|
13
|
+
s.static_framework = true
|
|
14
|
+
|
|
15
|
+
s.dependency 'ExpoModulesCore'
|
|
16
|
+
|
|
17
|
+
# Swift/Objective-C compatibility
|
|
18
|
+
s.pod_target_xcconfig = {
|
|
19
|
+
'DEFINES_MODULE' => 'YES',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
|
|
23
|
+
end
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flowstudio-rn",
|
|
3
|
+
"version": "0.0.1-alpha",
|
|
4
|
+
"description": "React Native SDK for Bureau Flow Studio: run Flow Studio identity and verification journeys in React Native / Expo apps.",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"build",
|
|
9
|
+
"src",
|
|
10
|
+
"android",
|
|
11
|
+
"!android/build",
|
|
12
|
+
"!android/.gradle",
|
|
13
|
+
"!android/.gitignore",
|
|
14
|
+
"!android/src/test",
|
|
15
|
+
"!android/src/androidTest",
|
|
16
|
+
"ios",
|
|
17
|
+
"plugin/build",
|
|
18
|
+
"app.plugin.js",
|
|
19
|
+
"expo-module.config.json",
|
|
20
|
+
"!**/__tests__",
|
|
21
|
+
"!**/__mocks__"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "node internal/module_scripts/build.js",
|
|
25
|
+
"clean": "node internal/module_scripts/clean.js",
|
|
26
|
+
"lint": "eslint src/ plugin/src/",
|
|
27
|
+
"test": "node internal/module_scripts/test.js",
|
|
28
|
+
"prepare": "node internal/module_scripts/prepare.js",
|
|
29
|
+
"open:ios": "node internal/module_scripts/open-ios.js",
|
|
30
|
+
"open:android": "node internal/module_scripts/open-android.js",
|
|
31
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p plugin"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"react-native",
|
|
35
|
+
"expo",
|
|
36
|
+
"bureau",
|
|
37
|
+
"flowstudio",
|
|
38
|
+
"identity-verification",
|
|
39
|
+
"kyc"
|
|
40
|
+
],
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/deepak-bureau/flowstudio-react-native-sdk.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/deepak-bureau/flowstudio-react-native-sdk/issues"
|
|
47
|
+
},
|
|
48
|
+
"author": "Bureau <techops@bureau.id> (https://bureau.id)",
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"homepage": "https://github.com/deepak-bureau/flowstudio-react-native-sdk#readme",
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"registry": "https://registry.npmjs.org/",
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": "^20.19.4 || ^22.13.0 || >=24.3.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@babel/core": "^7.26.0",
|
|
60
|
+
"@react-native/jest-preset": "^0.86.3",
|
|
61
|
+
"@types/jest": "^29.2.1",
|
|
62
|
+
"@types/react": "~19.2.2",
|
|
63
|
+
"babel-preset-expo": "~57.0.12",
|
|
64
|
+
"eslint": "~9.39.4",
|
|
65
|
+
"eslint-config-universe": "^15.0.3",
|
|
66
|
+
"expo": "~57.0.24",
|
|
67
|
+
"expo-modules-core": "~57.0.18",
|
|
68
|
+
"jest": "^29.7.0",
|
|
69
|
+
"jest-expo": "~57.0.5",
|
|
70
|
+
"prettier": "^3.0.0",
|
|
71
|
+
"react": "19.2.3",
|
|
72
|
+
"react-native": "0.86.3",
|
|
73
|
+
"typescript": "^5.9.2"
|
|
74
|
+
},
|
|
75
|
+
"jest": {
|
|
76
|
+
"preset": "jest-expo",
|
|
77
|
+
"roots": [
|
|
78
|
+
"<rootDir>/src"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"peerDependencies": {
|
|
82
|
+
"expo": "*",
|
|
83
|
+
"react": "*",
|
|
84
|
+
"react-native": "*"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
4
|
+
// Maven repository hosting the native FlowStudio SDK (id.bureau:flowstudio).
|
|
5
|
+
const BUREAU_MAVEN_URL = 'https://packages.bureau.id/api/packages/Bureau/maven';
|
|
6
|
+
/**
|
|
7
|
+
* Adds the Bureau Maven repository to the app's android/build.gradle
|
|
8
|
+
* (`allprojects { repositories { ... } }`) so the native FlowStudio SDK can be resolved.
|
|
9
|
+
*/
|
|
10
|
+
const withBureauMavenRepo = (config) => (0, config_plugins_1.withProjectBuildGradle)(config, (config) => {
|
|
11
|
+
const { contents, language } = config.modResults;
|
|
12
|
+
// Already added (by us on a previous run, or by hand).
|
|
13
|
+
if (contents.includes(BUREAU_MAVEN_URL)) {
|
|
14
|
+
return config;
|
|
15
|
+
}
|
|
16
|
+
const repoLine = language === 'kt'
|
|
17
|
+
? `maven { url = uri("${BUREAU_MAVEN_URL}") }`
|
|
18
|
+
: `maven { url '${BUREAU_MAVEN_URL}' }`;
|
|
19
|
+
const anchor = /allprojects\s*\{\s*repositories\s*\{/;
|
|
20
|
+
if (!anchor.test(contents)) {
|
|
21
|
+
config_plugins_1.WarningAggregator.addWarningAndroid('flowstudio-rn', `Could not find "allprojects { repositories { ... } }" in android/build.gradle. ` +
|
|
22
|
+
`Add the FlowStudio Maven repository manually: ${repoLine}`);
|
|
23
|
+
return config;
|
|
24
|
+
}
|
|
25
|
+
config.modResults.contents = contents.replace(anchor, (match) => `${match}\n ${repoLine}`);
|
|
26
|
+
return config;
|
|
27
|
+
});
|
|
28
|
+
const withFlowStudio = (config) => withBureauMavenRepo(config);
|
|
29
|
+
exports.default = withFlowStudio;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
FlowStudioConfig,
|
|
5
|
+
FlowStudioEnvironment,
|
|
6
|
+
FlowStudioResult,
|
|
7
|
+
} from './FlowStudioReactNative.types';
|
|
8
|
+
import native from './FlowStudioReactNativeModule';
|
|
9
|
+
|
|
10
|
+
const ENVIRONMENTS: readonly FlowStudioEnvironment[] = ['PROD', 'STAGING', 'DEV'];
|
|
11
|
+
|
|
12
|
+
// Only the Android native module is implemented so far.
|
|
13
|
+
const isSupportedPlatform = Platform.OS === 'android';
|
|
14
|
+
|
|
15
|
+
function requireSupportedPlatform(): void {
|
|
16
|
+
if (!isSupportedPlatform) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`FlowStudio: ${Platform.OS} is not supported yet (Android only in this release)`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function requireNonEmpty(value: unknown, name: string): void {
|
|
24
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
25
|
+
throw new Error(`FlowStudio: \`${name}\` is required and must be a non-empty string`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initializes the Flow Studio SDK. Must be called before `start()`.
|
|
31
|
+
*/
|
|
32
|
+
function initialize(config: FlowStudioConfig): void {
|
|
33
|
+
requireSupportedPlatform();
|
|
34
|
+
requireNonEmpty(config?.credentialId, 'credentialId');
|
|
35
|
+
requireNonEmpty(config.deviceCredentialId, 'deviceCredentialId');
|
|
36
|
+
requireNonEmpty(config.executionId, 'executionId');
|
|
37
|
+
|
|
38
|
+
const environment = config.environment ?? 'PROD';
|
|
39
|
+
if (!ENVIRONMENTS.includes(environment)) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`FlowStudio: \`environment\` must be one of ${ENVIRONMENTS.join(', ')}, got "${environment}"`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
native.initialize({
|
|
46
|
+
credentialId: config.credentialId,
|
|
47
|
+
deviceCredentialId: config.deviceCredentialId,
|
|
48
|
+
executionId: config.executionId,
|
|
49
|
+
environment,
|
|
50
|
+
enableLogging: config.enableLogging ?? false,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Launches the Flow Studio flow. Resolves when the flow finishes
|
|
56
|
+
* (`type: 'result'`) or the user exits it (`type: 'exit'`).
|
|
57
|
+
*/
|
|
58
|
+
async function start(): Promise<FlowStudioResult> {
|
|
59
|
+
requireSupportedPlatform();
|
|
60
|
+
if (!native.isInitialized()) {
|
|
61
|
+
throw new Error('FlowStudio: call `initialize()` before `start()`');
|
|
62
|
+
}
|
|
63
|
+
return native.start();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Returns whether `initialize()` has completed successfully.
|
|
68
|
+
*/
|
|
69
|
+
function isInitialized(): boolean {
|
|
70
|
+
return isSupportedPlatform && native.isInitialized();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const FlowStudio = {
|
|
74
|
+
initialize,
|
|
75
|
+
start,
|
|
76
|
+
isInitialized,
|
|
77
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend environment the Flow Studio SDK connects to.
|
|
3
|
+
* Mirrors native `FlowStudioEnvironment` (PROD | STAGING | DEV).
|
|
4
|
+
*/
|
|
5
|
+
export type FlowStudioEnvironment = 'PROD' | 'STAGING' | 'DEV';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Configuration passed to `FlowStudio.initialize()`.
|
|
9
|
+
* Mirrors native `FlowStudioConfig`.
|
|
10
|
+
*/
|
|
11
|
+
export interface FlowStudioConfig {
|
|
12
|
+
/** Credential ID issued by Bureau. */
|
|
13
|
+
credentialId: string;
|
|
14
|
+
/** Credential ID used for device intelligence. */
|
|
15
|
+
deviceCredentialId: string;
|
|
16
|
+
/** ID of the flow execution to run. */
|
|
17
|
+
executionId: string;
|
|
18
|
+
/** Defaults to `'PROD'`. */
|
|
19
|
+
environment?: FlowStudioEnvironment;
|
|
20
|
+
/** Enables SDK logging. Defaults to `false`. */
|
|
21
|
+
enableLogging?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Result of a completed flow execution.
|
|
26
|
+
* Mirrors native `ExecutionResponse`.
|
|
27
|
+
*/
|
|
28
|
+
export interface ExecutionResponse {
|
|
29
|
+
executionId: string;
|
|
30
|
+
merchantId: string;
|
|
31
|
+
requestId: string;
|
|
32
|
+
executionStatus: string;
|
|
33
|
+
currentState: Record<string, unknown>;
|
|
34
|
+
/** Epoch timestamp. */
|
|
35
|
+
createdAt: number;
|
|
36
|
+
/** Epoch timestamp. */
|
|
37
|
+
updatedAt: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Outcome of `FlowStudio.start()`. Mirrors native `FlowStudioCallback`:
|
|
42
|
+
* - `result` → `onResult(response)`
|
|
43
|
+
* - `exit` → `onExit()` (user left the flow without a result)
|
|
44
|
+
*/
|
|
45
|
+
export type FlowStudioResult = { type: 'result'; response: ExecutionResponse } | { type: 'exit' };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NativeModule, requireNativeModule } from 'expo';
|
|
2
|
+
|
|
3
|
+
import { FlowStudioConfig, FlowStudioResult } from './FlowStudioReactNative.types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Contract the native modules (Kotlin on Android, Swift on iOS) must implement.
|
|
7
|
+
* Names must match `Function(...)` / `AsyncFunction(...)` in the native modules exactly.
|
|
8
|
+
* Only plain data crosses the bridge, so config is passed fully resolved.
|
|
9
|
+
*/
|
|
10
|
+
declare class FlowStudioReactNativeModule extends NativeModule<{}> {
|
|
11
|
+
initialize(config: Required<FlowStudioConfig>): void;
|
|
12
|
+
start(): Promise<FlowStudioResult>;
|
|
13
|
+
isInitialized(): boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default requireNativeModule<FlowStudioReactNativeModule>('FlowStudioReactNative');
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { registerWebModule, NativeModule } from 'expo';
|
|
2
|
+
|
|
3
|
+
// FlowStudioReactNativeModule is not available on the web platform.
|
|
4
|
+
class FlowStudioReactNativeModule extends NativeModule<{}> {}
|
|
5
|
+
|
|
6
|
+
export default registerWebModule(FlowStudioReactNativeModule, 'FlowStudioReactNativeModule');
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Public API of flowstudio-rn. Only what is exported here is available to clients.
|
|
2
|
+
export { FlowStudio } from './FlowStudio';
|
|
3
|
+
export { FlowStudio as default } from './FlowStudio';
|
|
4
|
+
|
|
5
|
+
export type {
|
|
6
|
+
ExecutionResponse,
|
|
7
|
+
FlowStudioConfig,
|
|
8
|
+
FlowStudioEnvironment,
|
|
9
|
+
FlowStudioResult,
|
|
10
|
+
} from './FlowStudioReactNative.types';
|