@scribeup/react-native-scribeup 0.4.0 → 0.6.0

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 CHANGED
@@ -204,9 +204,9 @@ The first execution may take a while – the scripts:
204
204
 
205
205
  ## Troubleshooting
206
206
 
207
- 1. **iOS: `The package '@scribeup/react-native-scribeup' doesn't seem to be linked`.**
207
+ 1. **iOS: `The package '@scribeup/react-native-scribeup' doesn't seem to be linked`.**
208
208
  Make sure you ran `pod install` after installing the package and rebuilt the app.
209
- 2. **Expo Go crashes when opening the manager.**
209
+ 2. **Expo Go crashes when opening the manager.**
210
210
  Expo Go does not include native code. Use a dev-client or production build.
211
211
  3. Still stuck? Reach us at **dev@scribeup.io** or open an issue.
212
212
 
@@ -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
@@ -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
- exitCallback?.invoke(error)
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
- exitCallback?.invoke(error)
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
- exitCallback?.invoke(error)
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.putString("message", error.message)
72
- params.putInt("code", error.code)
90
+ params.putMap("error", Arguments.createMap().apply {
91
+ putInt("code", error.code)
92
+ putString("message", error.message)
93
+ })
73
94
  }
74
95
 
75
- exitCallback?.invoke(error)
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
113
  val error = SubscriptionManagerError(message = e.message ?: "Unknown error", code = ErrorCodes.UNKNOWN)
82
- exitCallback?.invoke(error)
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
- val newParams = Arguments.createMap()
102
- if (params != null) {
103
- if (params.hasKey("message")) {
104
- newParams.putString("message", params.getString("message"))
105
- }
106
- if (params.hasKey("code")) {
107
- newParams.putString("code", params.getString("code"))
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
- eventEmitter.emit("ScribeupExitSignal", newParams)
112
- } catch (e: Exception) {
113
- Log.e("Scribeup", "Error emitting event: ${e.message}")
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
  }
@@ -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
@@ -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": ErrorCodes.sdkError, "message": error.message]
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
- delegate.resolver?(nil)
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.4.0",
3
+ "version": "0.6.0",
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.0",
56
- "ios": "0.8.0"
55
+ "android": "0.8.2",
56
+ "ios": "0.8.3"
57
57
  }
58
58
  }
59
59
  }
package/src/ScribeUp.d.ts CHANGED
@@ -1,30 +1,46 @@
1
1
  import React from "react";
2
- /**
3
- * Data type returned when the SDK process ends
4
- */
5
- export type ExitData = {
6
- message?: string;
7
- code?: number;
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
- url: string;
14
- productName: string;
15
- onExit: (data?: ExitData) => void;
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 onExit callback when the user completes or
22
- * cancels the process.
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
- constructor(props: ScribeUpProps);
26
- componentDidMount(): void;
27
- private present;
28
- render(): any;
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 { NativeModules, Platform } from "react-native";
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
- * Data type returned when the SDK process ends
25
- */
26
- export type ExitData = {
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
57
  productName: string;
37
- onExit: (data?: ExitData) => void;
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
- * React component that displays the ScribeUp SDK interface when
44
- * mounted and invokes the onExit callback when the user completes or
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
- constructor(props: ScribeUpProps) {
49
- super(props);
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, onExit } = this.props;
54
- this.present(url, productName, onExit);
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 ?? -1), 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
- private present(
58
- url: string,
59
- productName: string,
60
- onExit: (data?: ExitData) => void,
61
- ) {
62
- if (Scribeup && typeof Scribeup.presentWithUrl === "function") {
63
- try {
64
- Scribeup.presentWithUrl(url, productName)
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
- onExit(result || {});
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 ?? -1), 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((error: any) => {
69
- onExit({
70
- message: error?.message || "Unknown error",
71
- code: error?.code || -1,
72
- });
158
+ .catch((err: any) => {
159
+ if (this.didExit) return;
160
+ this.didExit = true;
161
+ this.props.onExit?.(
162
+ { code: Number(err?.code ?? -1), message: err?.message || "Unknown 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
- } else {
81
- throw new Error(`ScribeUp: Native module not found for ${Platform.OS}`);
167
+ } catch (err: any) {
168
+ if (this.didExit) return;
169
+ this.didExit = true;
170
+ this.props.onExit?.(
171
+ { code: Number(err?.code ?? -1), message: err?.message || "Unknown error" },
172
+ null,
173
+ );
82
174
  }
83
175
  }
84
176