@scribeup/react-native-scribeup 0.5.0 → 0.6.1
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 +35 -19
- package/android/src/main/java/com/rnscribeupsdk/ScribeupModule.kt +3 -3
- package/android/src/main/java/com/rnscribeupsdk/ScribeupModuleImpl.kt +98 -25
- package/ios/ScribeupModule.swift +28 -13
- package/package.json +3 -3
- package/src/ScribeUp.d.ts +32 -16
- package/src/ScribeUp.tsx +129 -37
package/README.md
CHANGED
|
@@ -15,11 +15,13 @@ The package is a thin wrapper around the native [iOS](https://github.com/ScribeU
|
|
|
15
15
|
1. [ScribeUp (Full Screen)](#scribeup-full-screen)
|
|
16
16
|
2. [ScribeUpWidget (Embeddable)](#scribeupwidget-embeddable)
|
|
17
17
|
4. [API Reference](#api-reference)
|
|
18
|
-
5. [
|
|
19
|
-
6. [
|
|
20
|
-
7. [
|
|
21
|
-
8. [
|
|
18
|
+
5. [Migration Note (0.3.x → 0.6.0)](#migration-note-03x--060)
|
|
19
|
+
6. [Example Projects](#example-projects)
|
|
20
|
+
7. [Troubleshooting](#troubleshooting)
|
|
21
|
+
8. [Author](#author)
|
|
22
|
+
9. [License](#license)
|
|
22
23
|
|
|
24
|
+
---
|
|
23
25
|
|
|
24
26
|
## Installation
|
|
25
27
|
|
|
@@ -56,11 +58,15 @@ export default function App() {
|
|
|
56
58
|
|
|
57
59
|
const authenticatedUrl = "https://example.com/subscriptions?token=YOUR_JWT"; // Obtain from your backend (see docs)
|
|
58
60
|
|
|
59
|
-
const handleExit = (
|
|
60
|
-
console.log("ScribeUp
|
|
61
|
+
const handleExit = (error: { code?: number; message?: string } | null, data: Record<string, any> | null) => {
|
|
62
|
+
console.log("ScribeUp exited", { error, data });
|
|
61
63
|
setVisible(false);
|
|
62
64
|
};
|
|
63
65
|
|
|
66
|
+
const handleEvent = (data: Record<string, any>) => {
|
|
67
|
+
console.log("ScribeUp event", data);
|
|
68
|
+
};
|
|
69
|
+
|
|
64
70
|
return (
|
|
65
71
|
<SafeAreaView style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
|
66
72
|
<Button title="Manage my subscriptions" onPress={() => setVisible(true)} />
|
|
@@ -71,6 +77,7 @@ export default function App() {
|
|
|
71
77
|
url={authenticatedUrl}
|
|
72
78
|
productName="Subscription Manager" // optional text in the nav-bar
|
|
73
79
|
onExit={handleExit} // called on success or error
|
|
80
|
+
onEvent={handleEvent}
|
|
74
81
|
/>
|
|
75
82
|
)}
|
|
76
83
|
</SafeAreaView>
|
|
@@ -130,14 +137,25 @@ export default function MyComponent() {
|
|
|
130
137
|
|
|
131
138
|
### ScribeUp (Full Screen)
|
|
132
139
|
|
|
133
|
-
```
|
|
140
|
+
```tsx
|
|
134
141
|
<ScribeUp
|
|
135
|
-
url: string;
|
|
136
|
-
productName?: string;
|
|
137
|
-
onExit?: (data
|
|
138
|
-
|
|
142
|
+
url: string; // required – authenticated manage-subscriptions URL
|
|
143
|
+
productName?: string; // optional – title in navigation bar
|
|
144
|
+
onExit?: (error: ExitError|null, data: object|null) => void;
|
|
145
|
+
onEvent?: (data: object) => void;
|
|
146
|
+
>
|
|
139
147
|
```
|
|
140
148
|
|
|
149
|
+
**`onExit`**
|
|
150
|
+
Called when the user exits the flow.
|
|
151
|
+
Receives two arguments:
|
|
152
|
+
- `error`: `{ code: number; message?: string }` or `null` on success
|
|
153
|
+
- `data`: structured object with optional payload
|
|
154
|
+
|
|
155
|
+
**`onEvent`**
|
|
156
|
+
Emitted zero or more times during the session to notify about intermediate states or actions (e.g., UI transitions, user actions).
|
|
157
|
+
|
|
158
|
+
|
|
141
159
|
### ScribeUpWidget (Embeddable)
|
|
142
160
|
|
|
143
161
|
```
|
|
@@ -152,14 +170,12 @@ export default function MyComponent() {
|
|
|
152
170
|
- `reload()` – reloads the current page
|
|
153
171
|
- `loadURL(url: string)` – loads a new URL
|
|
154
172
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
`onExit` receives an object with two optional fields:
|
|
173
|
+
---
|
|
158
174
|
|
|
159
|
-
|
|
160
|
-
* `code` – numeric error code (0 on success, -1 on unknown error).
|
|
175
|
+
## Migration Note (0.3.x → 0.6.0)
|
|
161
176
|
|
|
162
|
-
|
|
177
|
+
- `onExit` now receives **two parameters**: `(error, data)` instead of a single `data` object.
|
|
178
|
+
- New `onEvent(data)` listener added for real-time progress updates.
|
|
163
179
|
|
|
164
180
|
---
|
|
165
181
|
|
|
@@ -204,9 +220,9 @@ The first execution may take a while – the scripts:
|
|
|
204
220
|
|
|
205
221
|
## Troubleshooting
|
|
206
222
|
|
|
207
|
-
1. **iOS: `The package '@scribeup/react-native-scribeup' doesn't seem to be linked`.**
|
|
223
|
+
1. **iOS: `The package '@scribeup/react-native-scribeup' doesn't seem to be linked`.**
|
|
208
224
|
Make sure you ran `pod install` after installing the package and rebuilt the app.
|
|
209
|
-
2. **Expo Go crashes when opening the manager.**
|
|
225
|
+
2. **Expo Go crashes when opening the manager.**
|
|
210
226
|
Expo Go does not include native code. Use a dev-client or production build.
|
|
211
227
|
3. Still stuck? Reach us at **dev@scribeup.io** or open an issue.
|
|
212
228
|
|
|
@@ -41,7 +41,7 @@ class ScribeupModule(reactContext: ReactApplicationContext) :
|
|
|
41
41
|
if (error != null) {
|
|
42
42
|
val params = Arguments.createMap()
|
|
43
43
|
params.putString("message", error.message)
|
|
44
|
-
params.
|
|
44
|
+
params.putInt("code", error.code)
|
|
45
45
|
currentPromise?.resolve(params)
|
|
46
46
|
} else {
|
|
47
47
|
currentPromise?.resolve(null)
|
|
@@ -50,8 +50,8 @@ class ScribeupModule(reactContext: ReactApplicationContext) :
|
|
|
50
50
|
}
|
|
51
51
|
} catch (e: Exception) {
|
|
52
52
|
val params = Arguments.createMap()
|
|
53
|
-
params.putString("message", e.message ?: "
|
|
54
|
-
params.
|
|
53
|
+
params.putString("message", e.message ?: "Unexpected Error")
|
|
54
|
+
params.putInt("code", 1000)
|
|
55
55
|
promise.resolve(params)
|
|
56
56
|
currentPromise = null
|
|
57
57
|
}
|
|
@@ -5,6 +5,7 @@ import android.util.Log
|
|
|
5
5
|
import androidx.fragment.app.FragmentActivity
|
|
6
6
|
import com.facebook.react.bridge.Arguments
|
|
7
7
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
8
|
+
import com.facebook.react.bridge.WritableArray
|
|
8
9
|
import com.facebook.react.bridge.WritableMap
|
|
9
10
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
10
11
|
import io.scribeup.scribeupsdk.SubscriptionManager
|
|
@@ -15,12 +16,12 @@ import java.net.URL
|
|
|
15
16
|
|
|
16
17
|
// Error codes shared between iOS and Android
|
|
17
18
|
object ErrorCodes {
|
|
18
|
-
const val UNKNOWN =
|
|
19
|
+
const val UNKNOWN = 1000
|
|
19
20
|
const val INVALID_URL = 1001
|
|
20
|
-
const val
|
|
21
|
-
const val
|
|
22
|
-
const val
|
|
23
|
-
const val
|
|
21
|
+
const val INVALID_ENV = 1002
|
|
22
|
+
const val ACTIVITY_NULL = 1003
|
|
23
|
+
const val INVALID_ACTIVITY_TYPE = 1004
|
|
24
|
+
const val NO_ROOT_VIEW_CONTROLLER = 1005
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
class ScribeupModuleImpl(private val reactContext: ReactApplicationContext) {
|
|
@@ -38,7 +39,13 @@ class ScribeupModuleImpl(private val reactContext: ReactApplicationContext) {
|
|
|
38
39
|
if (activity == null) {
|
|
39
40
|
Log.e("Scribeup", "Activity is null")
|
|
40
41
|
val error = SubscriptionManagerError(message = "Activity is null", code = ErrorCodes.ACTIVITY_NULL)
|
|
41
|
-
|
|
42
|
+
val params = Arguments.createMap().apply {
|
|
43
|
+
putMap("error", Arguments.createMap().apply {
|
|
44
|
+
putInt("code", error.code)
|
|
45
|
+
putString("message", error.message)
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
sendEvent("ScribeupOnExit", params)
|
|
42
49
|
return
|
|
43
50
|
}
|
|
44
51
|
|
|
@@ -46,7 +53,13 @@ class ScribeupModuleImpl(private val reactContext: ReactApplicationContext) {
|
|
|
46
53
|
if (activity !is FragmentActivity) {
|
|
47
54
|
Log.e("Scribeup", "Activity is not a FragmentActivity")
|
|
48
55
|
val error = SubscriptionManagerError(message = "Activity is not a FragmentActivity", code = ErrorCodes.INVALID_ACTIVITY_TYPE)
|
|
49
|
-
|
|
56
|
+
val params = Arguments.createMap().apply {
|
|
57
|
+
putMap("error", Arguments.createMap().apply {
|
|
58
|
+
putInt("code", error.code)
|
|
59
|
+
putString("message", error.message)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
sendEvent("ScribeupOnExit", params)
|
|
50
63
|
return
|
|
51
64
|
}
|
|
52
65
|
|
|
@@ -54,7 +67,13 @@ class ScribeupModuleImpl(private val reactContext: ReactApplicationContext) {
|
|
|
54
67
|
if (!isValidUrl(url)) {
|
|
55
68
|
Log.e("Scribeup", "Invalid URL: $url")
|
|
56
69
|
val error = SubscriptionManagerError(message = "Invalid URL: $url", code = ErrorCodes.INVALID_URL)
|
|
57
|
-
|
|
70
|
+
val params = Arguments.createMap().apply {
|
|
71
|
+
putMap("error", Arguments.createMap().apply {
|
|
72
|
+
putInt("code", error.code)
|
|
73
|
+
putString("message", error.message)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
sendEvent("ScribeupOnExit", params)
|
|
58
77
|
return
|
|
59
78
|
}
|
|
60
79
|
|
|
@@ -64,22 +83,41 @@ class ScribeupModuleImpl(private val reactContext: ReactApplicationContext) {
|
|
|
64
83
|
url = url,
|
|
65
84
|
productName = productName,
|
|
66
85
|
listener = object : SubscriptionManagerListener {
|
|
67
|
-
override fun onExit(error: SubscriptionManagerError
|
|
86
|
+
override fun onExit(error: SubscriptionManagerError?, data: Map<String, Any?>?) {
|
|
68
87
|
val params = Arguments.createMap()
|
|
69
88
|
|
|
70
89
|
if (error != null) {
|
|
71
|
-
params.
|
|
72
|
-
|
|
90
|
+
params.putMap("error", Arguments.createMap().apply {
|
|
91
|
+
putInt("code", error.code)
|
|
92
|
+
putString("message", error.message)
|
|
93
|
+
})
|
|
73
94
|
}
|
|
74
95
|
|
|
75
|
-
|
|
96
|
+
if (data != null) {
|
|
97
|
+
params.putMap("data", toWritableMap(data))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
sendEvent("ScribeupOnExit", params)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
override fun onEvent(data: Map<String, Any?>) {
|
|
104
|
+
val params = Arguments.createMap().apply {
|
|
105
|
+
putMap("data", toWritableMap(data))
|
|
106
|
+
}
|
|
107
|
+
sendEvent("ScribeupOnEvent", params)
|
|
76
108
|
}
|
|
77
109
|
}
|
|
78
110
|
)
|
|
79
111
|
} catch (e: Exception) {
|
|
80
112
|
Log.e("Scribeup", "Error presenting subscription manager: ${e.message}")
|
|
81
|
-
val error = SubscriptionManagerError(message = e.message ?: "
|
|
82
|
-
|
|
113
|
+
val error = SubscriptionManagerError(message = e.message ?: "Unexpected Error", code = ErrorCodes.UNKNOWN)
|
|
114
|
+
val params = Arguments.createMap().apply {
|
|
115
|
+
putMap("error", Arguments.createMap().apply {
|
|
116
|
+
putInt("code", error.code)
|
|
117
|
+
putString("message", error.message)
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
sendEvent("ScribeupOnExit", params)
|
|
83
121
|
}
|
|
84
122
|
}
|
|
85
123
|
|
|
@@ -97,21 +135,56 @@ class ScribeupModuleImpl(private val reactContext: ReactApplicationContext) {
|
|
|
97
135
|
reactContext.runOnUiQueueThread {
|
|
98
136
|
try {
|
|
99
137
|
val eventEmitter = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
138
|
+
eventEmitter.emit(eventName, params ?: Arguments.createMap())
|
|
139
|
+
} catch (e: Exception) {
|
|
140
|
+
Log.e("Scribeup", "Error emitting event: ${e.message}")
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
100
144
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
145
|
+
// -------------------- Converters: Map/List -> Writable* --------------------
|
|
146
|
+
|
|
147
|
+
@Suppress("UNCHECKED_CAST")
|
|
148
|
+
private fun toWritableMap(map: Map<String, Any?>): WritableMap {
|
|
149
|
+
val out = Arguments.createMap()
|
|
150
|
+
for ((kAny, v) in map) {
|
|
151
|
+
val key = kAny as String
|
|
152
|
+
when (v) {
|
|
153
|
+
null -> out.putNull(key)
|
|
154
|
+
is String -> out.putString(key, v)
|
|
155
|
+
is Int -> out.putInt(key, v)
|
|
156
|
+
is Long -> {
|
|
157
|
+
if (v in Int.MIN_VALUE..Int.MAX_VALUE) out.putInt(key, v.toInt()) else out.putDouble(key, v.toDouble())
|
|
109
158
|
}
|
|
159
|
+
is Float -> out.putDouble(key, v.toDouble())
|
|
160
|
+
is Double -> out.putDouble(key, v)
|
|
161
|
+
is Boolean -> out.putBoolean(key, v)
|
|
162
|
+
is Map<*, *> -> out.putMap(key, toWritableMap(v as Map<String, Any?>))
|
|
163
|
+
is List<*> -> out.putArray(key, toWritableArray(v))
|
|
164
|
+
else -> out.putString(key, v.toString())
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return out
|
|
168
|
+
}
|
|
110
169
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
170
|
+
private fun toWritableArray(list: List<*>): WritableArray {
|
|
171
|
+
val out = Arguments.createArray()
|
|
172
|
+
for (v in list) {
|
|
173
|
+
when (v) {
|
|
174
|
+
null -> out.pushNull()
|
|
175
|
+
is String -> out.pushString(v)
|
|
176
|
+
is Int -> out.pushInt(v)
|
|
177
|
+
is Long -> {
|
|
178
|
+
if (v in Int.MIN_VALUE..Int.MAX_VALUE) out.pushInt(v.toInt()) else out.pushDouble(v.toDouble())
|
|
179
|
+
}
|
|
180
|
+
is Float -> out.pushDouble(v.toDouble())
|
|
181
|
+
is Double -> out.pushDouble(v)
|
|
182
|
+
is Boolean -> out.pushBoolean(v)
|
|
183
|
+
is Map<*, *> -> out.pushMap(toWritableMap(v as Map<String, Any?>))
|
|
184
|
+
is List<*> -> out.pushArray(toWritableArray(v))
|
|
185
|
+
else -> out.pushString(v.toString())
|
|
114
186
|
}
|
|
115
187
|
}
|
|
188
|
+
return out
|
|
116
189
|
}
|
|
117
190
|
}
|
package/ios/ScribeupModule.swift
CHANGED
|
@@ -4,12 +4,12 @@ import React
|
|
|
4
4
|
|
|
5
5
|
// MARK: - Error Codes (shared between iOS and Android)
|
|
6
6
|
fileprivate enum ErrorCodes {
|
|
7
|
-
static let
|
|
8
|
-
static let
|
|
9
|
-
static let
|
|
10
|
-
static let
|
|
11
|
-
static let
|
|
12
|
-
static let
|
|
7
|
+
static let UNKNOWN = 1000
|
|
8
|
+
static let INVALID_URL = 1001
|
|
9
|
+
static let INVALID_ENV = 1002
|
|
10
|
+
static let ACTIVITY_NULL = 1003
|
|
11
|
+
static let INVALID_ACTIVITY_TYPE = 1004
|
|
12
|
+
static let NO_ROOT_VIEW_CONTROLLER = 1005
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
@objc(Scribeup)
|
|
@@ -24,7 +24,7 @@ class Scribeup: RCTEventEmitter {
|
|
|
24
24
|
|
|
25
25
|
@objc
|
|
26
26
|
override func supportedEvents() -> [String] {
|
|
27
|
-
return ["ScribeupOnExit"]
|
|
27
|
+
return ["ScribeupOnExit", "ScribeupOnEvent"]
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// MARK: - Properties
|
|
@@ -46,7 +46,7 @@ class Scribeup: RCTEventEmitter {
|
|
|
46
46
|
|
|
47
47
|
DispatchQueue.main.async {
|
|
48
48
|
guard let rootVC = UIApplication.shared.delegate?.window??.rootViewController else {
|
|
49
|
-
self.rejecter?(String(ErrorCodes.
|
|
49
|
+
self.rejecter?(String(ErrorCodes.NO_ROOT_VIEW_CONTROLLER), "Cannot find root view controller", nil)
|
|
50
50
|
return
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -60,18 +60,27 @@ class Scribeup: RCTEventEmitter {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
func sendExitEvent(error: SubscriptionManagerError?) {
|
|
63
|
+
func sendExitEvent(error: SubscriptionManagerError?, data: [String: Any]?) {
|
|
64
64
|
if self.hasListeners {
|
|
65
65
|
var body: [String: Any] = [:]
|
|
66
66
|
|
|
67
67
|
if let error = error {
|
|
68
|
-
body["error"] = ["code":
|
|
68
|
+
body["error"] = ["code": error.code, "message": error.message]
|
|
69
|
+
}
|
|
70
|
+
if let data = data {
|
|
71
|
+
body["data"] = data
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
self.sendEvent(withName: "ScribeupOnExit", body: body)
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
func sendOnEvent(data: [String: Any]) {
|
|
79
|
+
if self.hasListeners {
|
|
80
|
+
self.sendEvent(withName: "ScribeupOnEvent", body: ["data": data])
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
75
84
|
override func startObserving() {
|
|
76
85
|
self.hasListeners = true
|
|
77
86
|
}
|
|
@@ -88,18 +97,24 @@ class ScribeupSubscriptionListener: NSObject, SubscriptionManagerListener {
|
|
|
88
97
|
self.delegate = delegate
|
|
89
98
|
}
|
|
90
99
|
|
|
91
|
-
func onExit(_ error: SubscriptionManagerError?) {
|
|
100
|
+
func onExit(_ error: SubscriptionManagerError?, _ data: [String: Any]?) {
|
|
92
101
|
if let delegate = self.delegate {
|
|
93
102
|
if let error = error {
|
|
94
103
|
delegate.rejecter?(String(error.code), error.message, nil)
|
|
95
104
|
} else {
|
|
96
|
-
|
|
105
|
+
var payload: [String: Any] = [:]
|
|
106
|
+
if let data = data { payload["data"] = data }
|
|
107
|
+
delegate.resolver?(payload)
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
delegate.resolver = nil
|
|
100
111
|
delegate.rejecter = nil
|
|
101
112
|
}
|
|
102
113
|
|
|
103
|
-
self.delegate?.sendExitEvent(error: error)
|
|
114
|
+
self.delegate?.sendExitEvent(error: error, data: data)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
func onEvent(_ data: [String: Any]) {
|
|
118
|
+
self.delegate?.sendOnEvent(data: data)
|
|
104
119
|
}
|
|
105
120
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scribeup/react-native-scribeup",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
},
|
|
53
53
|
"config": {
|
|
54
54
|
"nativeSDKVersions": {
|
|
55
|
-
"android": "0.8.
|
|
56
|
-
"ios": "0.8.
|
|
55
|
+
"android": "0.8.5",
|
|
56
|
+
"ios": "0.8.5"
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
}
|
package/src/ScribeUp.d.ts
CHANGED
|
@@ -1,30 +1,46 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
|
|
3
|
+
/** JSON value types allowed inside data payloads */
|
|
4
|
+
export type JsonValue =
|
|
5
|
+
| string
|
|
6
|
+
| number
|
|
7
|
+
| boolean
|
|
8
|
+
| null
|
|
9
|
+
| JsonObject
|
|
10
|
+
| JsonValue[];
|
|
11
|
+
|
|
12
|
+
export interface JsonObject {
|
|
13
|
+
[key: string]: JsonValue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Error returned when the SDK process ends with failure */
|
|
17
|
+
export interface ExitError {
|
|
18
|
+
code: number;
|
|
19
|
+
message?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
/**
|
|
10
23
|
* ScribeUp component properties
|
|
11
24
|
*/
|
|
12
25
|
export interface ScribeUpProps {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
url: string;
|
|
27
|
+
productName?: string;
|
|
28
|
+
onExit: (error: ExitError | null, data: JsonObject | null) => void;
|
|
29
|
+
onEvent?: (data: JsonObject) => void;
|
|
16
30
|
}
|
|
31
|
+
|
|
17
32
|
/**
|
|
18
33
|
* ScribeUp Component
|
|
19
34
|
*
|
|
20
35
|
* React component that displays the ScribeUp SDK interface when
|
|
21
|
-
* mounted and invokes the
|
|
22
|
-
*
|
|
36
|
+
* mounted and invokes the callbacks as the user interacts with or
|
|
37
|
+
* exits the flow.
|
|
23
38
|
*/
|
|
24
39
|
declare class ScribeUp extends React.Component<ScribeUpProps> {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
40
|
+
constructor(props: ScribeUpProps);
|
|
41
|
+
componentDidMount(): void;
|
|
42
|
+
private present;
|
|
43
|
+
render(): any;
|
|
29
44
|
}
|
|
45
|
+
|
|
30
46
|
export default ScribeUp;
|
package/src/ScribeUp.tsx
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
NativeModules,
|
|
4
|
+
Platform,
|
|
5
|
+
NativeEventEmitter,
|
|
6
|
+
EmitterSubscription,
|
|
7
|
+
} from "react-native";
|
|
3
8
|
|
|
4
9
|
const LINKING_ERROR =
|
|
5
10
|
`The package 'scribeup-react-native-scribeup' doesn't seem to be linked. Make sure: \n\n` +
|
|
@@ -20,65 +25,152 @@ const Scribeup = NativeModules.Scribeup
|
|
|
20
25
|
},
|
|
21
26
|
);
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
const EXIT_EVENT = "ScribeupOnExit";
|
|
29
|
+
const EVENT_NAME = "ScribeupOnEvent";
|
|
30
|
+
|
|
31
|
+
/** JSON value types allowed inside data payloads */
|
|
32
|
+
export type JsonValue =
|
|
33
|
+
| string
|
|
34
|
+
| number
|
|
35
|
+
| boolean
|
|
36
|
+
| null
|
|
37
|
+
| JsonObject
|
|
38
|
+
| JsonValue[];
|
|
39
|
+
|
|
40
|
+
export interface JsonObject {
|
|
41
|
+
[key: string]: JsonValue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type ExitError = {
|
|
45
|
+
code: number;
|
|
27
46
|
message?: string;
|
|
28
|
-
code?: number;
|
|
29
47
|
};
|
|
30
48
|
|
|
49
|
+
type ExitPayload = { error?: ExitError; data?: JsonObject };
|
|
50
|
+
type EventPayload = { data?: JsonObject };
|
|
51
|
+
|
|
31
52
|
/**
|
|
32
53
|
* ScribeUp component properties
|
|
33
54
|
*/
|
|
34
55
|
export interface ScribeUpProps {
|
|
35
56
|
url: string;
|
|
36
|
-
productName
|
|
37
|
-
onExit:
|
|
57
|
+
productName?: string;
|
|
58
|
+
onExit?: (error: ExitError | null, data: JsonObject | null) => void;
|
|
59
|
+
onEvent?: (data: JsonObject) => void;
|
|
38
60
|
}
|
|
39
61
|
|
|
40
62
|
/**
|
|
41
63
|
* ScribeUp Component
|
|
42
64
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* cancels the process.
|
|
65
|
+
* Presents the native UI on mount and wires callbacks via device events.
|
|
66
|
+
* Safe against modules that don't implement addListener/removeListeners.
|
|
46
67
|
*/
|
|
47
68
|
class ScribeUp extends React.Component<ScribeUpProps> {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
69
|
+
private emitter?: NativeEventEmitter;
|
|
70
|
+
private exitSub?: EmitterSubscription;
|
|
71
|
+
private eventSub?: EmitterSubscription;
|
|
72
|
+
private didExit = false;
|
|
51
73
|
|
|
52
74
|
componentDidMount() {
|
|
53
|
-
const { url, productName
|
|
54
|
-
|
|
75
|
+
const { url, productName = "" } = this.props;
|
|
76
|
+
|
|
77
|
+
// Use module-backed emitter only if it implements the listener API.
|
|
78
|
+
const canAttach =
|
|
79
|
+
Scribeup &&
|
|
80
|
+
typeof (Scribeup as any).addListener === "function" &&
|
|
81
|
+
typeof (Scribeup as any).removeListeners === "function";
|
|
82
|
+
|
|
83
|
+
this.emitter = new NativeEventEmitter(canAttach ? (Scribeup as any) : undefined);
|
|
84
|
+
|
|
85
|
+
// Subscribe to onExit
|
|
86
|
+
this.exitSub = this.emitter.addListener(
|
|
87
|
+
EXIT_EVENT,
|
|
88
|
+
(payload?: ExitPayload) => {
|
|
89
|
+
if (this.didExit) return;
|
|
90
|
+
this.didExit = true;
|
|
91
|
+
|
|
92
|
+
const error: ExitError | null = payload?.error
|
|
93
|
+
? { code: Number(payload.error.code ?? 1000), message: payload.error.message }
|
|
94
|
+
: null;
|
|
95
|
+
|
|
96
|
+
const data: JsonObject | null =
|
|
97
|
+
payload?.data && typeof payload.data === "object"
|
|
98
|
+
? (payload.data as JsonObject)
|
|
99
|
+
: null;
|
|
100
|
+
|
|
101
|
+
this.props.onExit?.(error, data);
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Subscribe to onEvent
|
|
106
|
+
this.eventSub = this.emitter.addListener(
|
|
107
|
+
EVENT_NAME,
|
|
108
|
+
(payload?: EventPayload) => {
|
|
109
|
+
const data =
|
|
110
|
+
payload?.data && typeof payload.data === "object"
|
|
111
|
+
? (payload.data as JsonObject)
|
|
112
|
+
: null;
|
|
113
|
+
if (data) this.props.onEvent?.(data);
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Present the native UI. Support both promise- and event-based native APIs.
|
|
118
|
+
this.present(url, productName);
|
|
55
119
|
}
|
|
56
120
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
121
|
+
componentWillUnmount() {
|
|
122
|
+
try {
|
|
123
|
+
this.exitSub?.remove();
|
|
124
|
+
this.eventSub?.remove();
|
|
125
|
+
} finally {
|
|
126
|
+
this.exitSub = undefined;
|
|
127
|
+
this.eventSub = undefined;
|
|
128
|
+
this.emitter = undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private present(url: string, productName: string) {
|
|
133
|
+
if (!(Scribeup && typeof (Scribeup as any).presentWithUrl === "function")) {
|
|
134
|
+
throw new Error(`ScribeUp: Native module not found for ${Platform.OS}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const ret = (Scribeup as any).presentWithUrl(url, productName);
|
|
139
|
+
|
|
140
|
+
// If native returns a Promise, also resolve to onExit (guarded to avoid duplicate calls).
|
|
141
|
+
if (ret && typeof ret.then === "function") {
|
|
142
|
+
ret
|
|
65
143
|
.then((result: any) => {
|
|
66
|
-
|
|
144
|
+
if (this.didExit) return; // event already handled exit
|
|
145
|
+
this.didExit = true;
|
|
146
|
+
|
|
147
|
+
const error: ExitError | null = result?.error
|
|
148
|
+
? { code: Number(result.error.code ?? 1000), message: result.error.message }
|
|
149
|
+
: null;
|
|
150
|
+
|
|
151
|
+
const data: JsonObject | null =
|
|
152
|
+
result && result.data && typeof result.data === "object"
|
|
153
|
+
? (result.data as JsonObject)
|
|
154
|
+
: null;
|
|
155
|
+
|
|
156
|
+
this.props.onExit?.(error, data);
|
|
67
157
|
})
|
|
68
|
-
.catch((
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
158
|
+
.catch((err: any) => {
|
|
159
|
+
if (this.didExit) return;
|
|
160
|
+
this.didExit = true;
|
|
161
|
+
this.props.onExit?.(
|
|
162
|
+
{ code: Number(err?.code ?? 1000), message: err?.message || "Unexpected Error" },
|
|
163
|
+
null,
|
|
164
|
+
);
|
|
73
165
|
});
|
|
74
|
-
} catch (error: any) {
|
|
75
|
-
onExit({
|
|
76
|
-
message: error?.message || "Unknown error",
|
|
77
|
-
code: error?.code || -1,
|
|
78
|
-
});
|
|
79
166
|
}
|
|
80
|
-
}
|
|
81
|
-
|
|
167
|
+
} catch (err: any) {
|
|
168
|
+
if (this.didExit) return;
|
|
169
|
+
this.didExit = true;
|
|
170
|
+
this.props.onExit?.(
|
|
171
|
+
{ code: Number(err?.code ?? 1000), message: err?.message || "Unexpected Error" },
|
|
172
|
+
null,
|
|
173
|
+
);
|
|
82
174
|
}
|
|
83
175
|
}
|
|
84
176
|
|