@scribeup/react-native-scribeup 0.0.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/LICENSE +20 -0
- package/README.md +164 -0
- package/RNScribeupSDK.podspec +25 -0
- package/android/build.gradle +92 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/rnscribeupsdk/ScribeupModule.kt +60 -0
- package/android/src/main/java/com/rnscribeupsdk/ScribeupModuleImpl.kt +117 -0
- package/android/src/main/java/com/rnscribeupsdk/ScribeupPackage.kt +16 -0
- package/ios/Scribeup.m +11 -0
- package/ios/ScribeupModule.h +0 -0
- package/ios/ScribeupModule.swift +105 -0
- package/package.json +57 -0
- package/src/ScribeUp.d.ts +30 -0
- package/src/ScribeUp.tsx +90 -0
- package/src/index.d.ts +2 -0
- package/src/index.ts +3 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# ScribeUp React-Native SDK
|
|
2
|
+
|
|
3
|
+
Easily integrate the [ScribeUp](https://scribeup.io) subscription-manager experience in any React-Native application.
|
|
4
|
+
The package is a thin wrapper around the native [iOS](https://github.com/ScribeUp/scribeup-sdk-ios) and [Android](https://github.com/ScribeUp/scribeup-sdk-android) SDKs, providing a single cross-platform API.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
1. [Installation](#installation)
|
|
11
|
+
1. [Bare React-Native](#bare-react-native)
|
|
12
|
+
2. [Expo Projects](#expo-projects)
|
|
13
|
+
2. [Quick Start](#quick-start)
|
|
14
|
+
3. [API Reference](#api-reference)
|
|
15
|
+
4. [Example Projects](#example-projects)
|
|
16
|
+
5. [Troubleshooting](#troubleshooting)
|
|
17
|
+
6. [Author](#author)
|
|
18
|
+
7. [License](#license)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
### Bare React-Native
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @scribeup/react-native-scribeup
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The library supports autolinking on both platforms – no additional manual steps are required.
|
|
30
|
+
|
|
31
|
+
### Expo Projects
|
|
32
|
+
|
|
33
|
+
The SDK works in **development builds** (aka *Expo Dev Client*) and production builds generated with `eas build`.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
expo install @scribeup/react-native-scribeup
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
After installing, create a development build (`eas build --profile development`) or a production build. The SDK cannot run in the standard Expo Go client because it contains custom native code.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// App.tsx
|
|
47
|
+
import React, { useState } from "react";
|
|
48
|
+
import { Button, SafeAreaView } from "react-native";
|
|
49
|
+
import ScribeUp from "@scribeup/react-native-scribeup";
|
|
50
|
+
|
|
51
|
+
export default function App() {
|
|
52
|
+
const [visible, setVisible] = useState(false);
|
|
53
|
+
|
|
54
|
+
const authenticatedUrl = "https://example.com/subscriptions?token=YOUR_JWT"; // Obtain from your backend (see docs)
|
|
55
|
+
|
|
56
|
+
const handleExit = (data?: { message?: string; code?: number }) => {
|
|
57
|
+
console.log("ScribeUp finished", data);
|
|
58
|
+
setVisible(false);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<SafeAreaView style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
|
63
|
+
<Button title="Manage my subscriptions" onPress={() => setVisible(true)} />
|
|
64
|
+
|
|
65
|
+
{/* Mounting the component presents the native view controller / activity */}
|
|
66
|
+
{visible && (
|
|
67
|
+
<ScribeUp
|
|
68
|
+
url={authenticatedUrl}
|
|
69
|
+
productName="Subscription Manager" // optional text in the nav-bar
|
|
70
|
+
onExit={handleExit} // called on success or error
|
|
71
|
+
/>
|
|
72
|
+
)}
|
|
73
|
+
</SafeAreaView>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Obtaining the `url` parameter
|
|
79
|
+
|
|
80
|
+
`url` must be a **fully authenticated URL** for managing the current user's subscriptions. Follow the steps in the [ScribeUp documentation](https://docs.scribeup.io) to create this URL on your backend.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## API Reference
|
|
85
|
+
|
|
86
|
+
ScribeUp is a *React component*. Mounting it immediately presents the native subscription-manager UI.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
<ScribeUp
|
|
90
|
+
url: string; // required – authenticated manage-subscriptions URL
|
|
91
|
+
productName?: string; // optional – title shown in the navigation bar
|
|
92
|
+
onExit?: (data?) => void; // optional – called when the user exits, with optional error
|
|
93
|
+
/>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Exit Callback
|
|
97
|
+
|
|
98
|
+
`onExit` receives an object with two optional fields:
|
|
99
|
+
|
|
100
|
+
* `message` – descriptive error or informational message.
|
|
101
|
+
* `code` – numeric error code (0 on success, -1 on unknown error).
|
|
102
|
+
|
|
103
|
+
If both fields are undefined, the flow completed without errors.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Example Projects
|
|
108
|
+
|
|
109
|
+
This repository contains two fully-working example apps:
|
|
110
|
+
|
|
111
|
+
* **`example/`** – a bare React-Native app.
|
|
112
|
+
* **`example_expo/`** – an Expo Router app using the dev-client.
|
|
113
|
+
|
|
114
|
+
You can run either example directly from the repository **root** using the convenience scripts below:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# ---------------------------
|
|
118
|
+
# Bare React-Native example
|
|
119
|
+
# ---------------------------
|
|
120
|
+
|
|
121
|
+
# iOS (default)
|
|
122
|
+
npm run dev
|
|
123
|
+
|
|
124
|
+
# Android
|
|
125
|
+
npm run dev -- --android
|
|
126
|
+
|
|
127
|
+
# ---------------------------
|
|
128
|
+
# Expo Router example
|
|
129
|
+
# ---------------------------
|
|
130
|
+
|
|
131
|
+
# iOS (default)
|
|
132
|
+
npm run expo
|
|
133
|
+
|
|
134
|
+
# Android
|
|
135
|
+
npm run expo -- --android
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The first execution may take a while – the scripts:
|
|
139
|
+
1. Build a local tarball of the SDK.
|
|
140
|
+
2. Install it in the corresponding example app.
|
|
141
|
+
3. Install all native dependencies (including CocoaPods on iOS).
|
|
142
|
+
4. Start the Metro bundler and launch the app on the chosen simulator / emulator.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Troubleshooting
|
|
147
|
+
|
|
148
|
+
1. **iOS: `The package '@scribeup/react-native-scribeup' doesn't seem to be linked`.**
|
|
149
|
+
Make sure you ran `pod install` after installing the package and rebuilt the app.
|
|
150
|
+
2. **Expo Go crashes when opening the manager.**
|
|
151
|
+
Expo Go does not include native code. Use a dev-client or production build.
|
|
152
|
+
3. Still stuck? Reach us at **dev@scribeup.io** or open an issue.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Author
|
|
157
|
+
|
|
158
|
+
[ScribeUp](https://scribeup.io)
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
ScribeUpSDK is released under the MIT license. See the [LICENSE](./LICENSE) file for details.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
# Read iOS SDK version with fallback
|
|
5
|
+
ios_sdk_version = package.dig("config", "nativeSDKVersions", "ios") || "0.5.7"
|
|
6
|
+
|
|
7
|
+
Pod::Spec.new do |s|
|
|
8
|
+
s.name = "RNScribeupSDK"
|
|
9
|
+
s.version = package["version"]
|
|
10
|
+
s.summary = package["description"]
|
|
11
|
+
s.homepage = package["homepage"]
|
|
12
|
+
s.license = package["license"]
|
|
13
|
+
s.authors = package["author"]
|
|
14
|
+
|
|
15
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
16
|
+
s.source = { :git => "https://github.com/ScribeUp/react-native-scribeup-sdk.git", :tag => s.version.to_s }
|
|
17
|
+
|
|
18
|
+
s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
|
|
19
|
+
s.private_header_files = "ios/**/*.h"
|
|
20
|
+
|
|
21
|
+
s.dependency 'ScribeUpSDK', ios_sdk_version
|
|
22
|
+
s.swift_version = '5.0'
|
|
23
|
+
|
|
24
|
+
install_modules_dependencies(s)
|
|
25
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.getExtOrDefault = {name ->
|
|
3
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['RNScribeupSDK_' + name]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
13
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
apply plugin: "com.android.library"
|
|
19
|
+
apply plugin: "kotlin-android"
|
|
20
|
+
|
|
21
|
+
apply plugin: "com.facebook.react"
|
|
22
|
+
|
|
23
|
+
def getExtOrIntegerDefault(name) {
|
|
24
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["RNScribeupSDK_" + name]).toInteger()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
android {
|
|
28
|
+
namespace "com.rnscribeupsdk"
|
|
29
|
+
|
|
30
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
31
|
+
|
|
32
|
+
defaultConfig {
|
|
33
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
34
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
buildFeatures {
|
|
38
|
+
buildConfig true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
buildTypes {
|
|
42
|
+
release {
|
|
43
|
+
minifyEnabled false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
lintOptions {
|
|
48
|
+
disable "GradleCompatible"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
compileOptions {
|
|
52
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
53
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
sourceSets {
|
|
57
|
+
main {
|
|
58
|
+
java.srcDirs += [
|
|
59
|
+
"generated/java",
|
|
60
|
+
"generated/jni"
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
repositories {
|
|
67
|
+
mavenCentral()
|
|
68
|
+
google()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
72
|
+
|
|
73
|
+
// Read SDK version from package.json
|
|
74
|
+
def readSDKVersion = { ->
|
|
75
|
+
def packageJsonFile = file("${project.projectDir}/../package.json")
|
|
76
|
+
def packageJson = new groovy.json.JsonSlurper().parseText(packageJsonFile.text)
|
|
77
|
+
return packageJson.config?.nativeSDKVersions?.android ?: "0.5.0"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
def androidSDKVersion = readSDKVersion()
|
|
81
|
+
|
|
82
|
+
dependencies {
|
|
83
|
+
implementation "com.facebook.react:react-android"
|
|
84
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
85
|
+
implementation "io.scribeup:scribeupsdk:$androidSDKVersion"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
react {
|
|
89
|
+
jsRootDir = file("../src/")
|
|
90
|
+
libraryName = "RNScribeupSDK"
|
|
91
|
+
codegenJavaPackageName = "com.rnscribeupsdk"
|
|
92
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
package com.rnscribeupsdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
5
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
6
|
+
import com.facebook.react.bridge.ReactMethod
|
|
7
|
+
import com.facebook.react.bridge.Promise
|
|
8
|
+
import android.util.Log
|
|
9
|
+
import com.facebook.react.bridge.Arguments
|
|
10
|
+
|
|
11
|
+
@ReactModule(name = ScribeupModule.NAME)
|
|
12
|
+
class ScribeupModule(reactContext: ReactApplicationContext) :
|
|
13
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
14
|
+
|
|
15
|
+
companion object {
|
|
16
|
+
const val NAME = "Scribeup"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private val moduleImpl = ScribeupModuleImpl(reactContext)
|
|
20
|
+
private var hasExited = false
|
|
21
|
+
private var currentPromise: Promise? = null
|
|
22
|
+
|
|
23
|
+
override fun getName(): String {
|
|
24
|
+
return NAME
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@ReactMethod
|
|
28
|
+
fun checkExitStatus(promise: Promise) {
|
|
29
|
+
val map = Arguments.createMap()
|
|
30
|
+
map.putBoolean("hasExited", hasExited)
|
|
31
|
+
promise.resolve(map)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@ReactMethod
|
|
35
|
+
fun presentWithUrl(url: String, productName: String, promise: Promise) {
|
|
36
|
+
try {
|
|
37
|
+
currentPromise = promise
|
|
38
|
+
moduleImpl.present(url, productName)
|
|
39
|
+
moduleImpl.setExitCallback { error ->
|
|
40
|
+
hasExited = true
|
|
41
|
+
if (error != null) {
|
|
42
|
+
val params = Arguments.createMap()
|
|
43
|
+
params.putString("message", error.message)
|
|
44
|
+
params.putString("code", error.code.toString())
|
|
45
|
+
currentPromise?.resolve(params)
|
|
46
|
+
} else {
|
|
47
|
+
currentPromise?.resolve(null)
|
|
48
|
+
}
|
|
49
|
+
currentPromise = null
|
|
50
|
+
}
|
|
51
|
+
} catch (e: Exception) {
|
|
52
|
+
val params = Arguments.createMap()
|
|
53
|
+
params.putString("message", e.message ?: "Unknown error")
|
|
54
|
+
params.putString("code", "-1")
|
|
55
|
+
promise.resolve(params)
|
|
56
|
+
currentPromise = null
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
package com.rnscribeupsdk
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import androidx.fragment.app.FragmentActivity
|
|
6
|
+
import com.facebook.react.bridge.Arguments
|
|
7
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
8
|
+
import com.facebook.react.bridge.WritableMap
|
|
9
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
10
|
+
import io.scribeup.scribeupsdk.SubscriptionManager
|
|
11
|
+
import io.scribeup.scribeupsdk.SubscriptionManagerListener
|
|
12
|
+
import io.scribeup.scribeupsdk.data.models.SubscriptionManagerError
|
|
13
|
+
import android.webkit.URLUtil
|
|
14
|
+
import java.net.URL
|
|
15
|
+
|
|
16
|
+
// Error codes shared between iOS and Android
|
|
17
|
+
object ErrorCodes {
|
|
18
|
+
const val UNKNOWN = -1
|
|
19
|
+
const val INVALID_URL = 1001
|
|
20
|
+
const val ACTIVITY_NULL = 1002
|
|
21
|
+
const val INVALID_ACTIVITY_TYPE = 1003
|
|
22
|
+
const val NO_ROOT_VIEW_CONTROLLER = 1004
|
|
23
|
+
const val SDK_ERROR = 2001
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class ScribeupModuleImpl(private val reactContext: ReactApplicationContext) {
|
|
27
|
+
|
|
28
|
+
private var exitCallback: ((SubscriptionManagerError?) -> Unit)? = null
|
|
29
|
+
|
|
30
|
+
fun setExitCallback(callback: (SubscriptionManagerError?) -> Unit) {
|
|
31
|
+
this.exitCallback = callback
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fun present(url: String, productName: String) {
|
|
35
|
+
val activity: Activity? = reactContext.currentActivity
|
|
36
|
+
|
|
37
|
+
// Check for null activity
|
|
38
|
+
if (activity == null) {
|
|
39
|
+
Log.e("Scribeup", "Activity is null")
|
|
40
|
+
val error = SubscriptionManagerError(message = "Activity is null", code = ErrorCodes.ACTIVITY_NULL)
|
|
41
|
+
exitCallback?.invoke(error)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check if activity is FragmentActivity
|
|
46
|
+
if (activity !is FragmentActivity) {
|
|
47
|
+
Log.e("Scribeup", "Activity is not a FragmentActivity")
|
|
48
|
+
val error = SubscriptionManagerError(message = "Activity is not a FragmentActivity", code = ErrorCodes.INVALID_ACTIVITY_TYPE)
|
|
49
|
+
exitCallback?.invoke(error)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Validate the URL before proceeding
|
|
54
|
+
if (!isValidUrl(url)) {
|
|
55
|
+
Log.e("Scribeup", "Invalid URL: $url")
|
|
56
|
+
val error = SubscriptionManagerError(message = "Invalid URL: $url", code = ErrorCodes.INVALID_URL)
|
|
57
|
+
exitCallback?.invoke(error)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
SubscriptionManager.present(
|
|
63
|
+
host = activity as FragmentActivity,
|
|
64
|
+
url = url,
|
|
65
|
+
productName = productName,
|
|
66
|
+
listener = object : SubscriptionManagerListener {
|
|
67
|
+
override fun onExit(error: SubscriptionManagerError?) {
|
|
68
|
+
val params = Arguments.createMap()
|
|
69
|
+
|
|
70
|
+
if (error != null) {
|
|
71
|
+
params.putString("message", error.message)
|
|
72
|
+
params.putInt("code", error.code)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
exitCallback?.invoke(error)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
} catch (e: Exception) {
|
|
80
|
+
Log.e("Scribeup", "Error presenting subscription manager: ${e.message}")
|
|
81
|
+
val error = SubscriptionManagerError(message = e.message ?: "Unknown error", code = ErrorCodes.UNKNOWN)
|
|
82
|
+
exitCallback?.invoke(error)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Helper method to validate URLs
|
|
87
|
+
private fun isValidUrl(urlString: String): Boolean {
|
|
88
|
+
return try {
|
|
89
|
+
val url = URL(urlString)
|
|
90
|
+
URLUtil.isValidUrl(urlString) && (url.protocol == "http" || url.protocol == "https")
|
|
91
|
+
} catch (e: Exception) {
|
|
92
|
+
false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fun sendEvent(eventName: String, params: WritableMap?) {
|
|
97
|
+
reactContext.runOnUiQueueThread {
|
|
98
|
+
try {
|
|
99
|
+
val eventEmitter = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
100
|
+
|
|
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
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
eventEmitter.emit("ScribeupExitSignal", newParams)
|
|
112
|
+
} catch (e: Exception) {
|
|
113
|
+
Log.e("Scribeup", "Error emitting event: ${e.message}")
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.rnscribeupsdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class RNScribeupSDKPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return listOf(ScribeupModule(reactContext))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
+
return emptyList()
|
|
15
|
+
}
|
|
16
|
+
}
|
package/ios/Scribeup.m
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
#import <React/RCTEventEmitter.h>
|
|
3
|
+
|
|
4
|
+
@interface RCT_EXTERN_MODULE(Scribeup, RCTEventEmitter)
|
|
5
|
+
|
|
6
|
+
RCT_EXTERN_METHOD(presentWithUrl:(NSString *)url
|
|
7
|
+
withProductName:(NSString *)productName
|
|
8
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
9
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
10
|
+
|
|
11
|
+
@end
|
|
File without changes
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import ScribeUpSDK
|
|
3
|
+
import React
|
|
4
|
+
|
|
5
|
+
// MARK: - Error Codes (shared between iOS and Android)
|
|
6
|
+
fileprivate enum ErrorCodes {
|
|
7
|
+
static let unknown = -1
|
|
8
|
+
static let invalidUrl = 1001
|
|
9
|
+
static let activityNull = 1002
|
|
10
|
+
static let invalidActivityType = 1003
|
|
11
|
+
static let noRootViewController = 1004
|
|
12
|
+
static let sdkError = 2001
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@objc(Scribeup)
|
|
16
|
+
class Scribeup: RCTEventEmitter {
|
|
17
|
+
|
|
18
|
+
// MARK: - RCTEventEmitter Methods
|
|
19
|
+
|
|
20
|
+
@objc
|
|
21
|
+
override static func requiresMainQueueSetup() -> Bool {
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@objc
|
|
26
|
+
override func supportedEvents() -> [String] {
|
|
27
|
+
return ["ScribeupOnExit"]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// MARK: - Properties
|
|
31
|
+
|
|
32
|
+
internal var resolver: RCTPromiseResolveBlock?
|
|
33
|
+
internal var rejecter: RCTPromiseRejectBlock?
|
|
34
|
+
private var subscriptionListener: SubscriptionManagerListener?
|
|
35
|
+
private var hasListeners: Bool = false
|
|
36
|
+
|
|
37
|
+
// MARK: - Public Methods
|
|
38
|
+
|
|
39
|
+
@objc(presentWithUrl:withProductName:resolver:rejecter:)
|
|
40
|
+
func presentWithParams(url: String, productName: String, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
|
|
41
|
+
self.resolver = resolver
|
|
42
|
+
self.rejecter = rejecter
|
|
43
|
+
|
|
44
|
+
let listener = ScribeupSubscriptionListener(delegate: self)
|
|
45
|
+
self.subscriptionListener = listener
|
|
46
|
+
|
|
47
|
+
DispatchQueue.main.async {
|
|
48
|
+
guard let rootVC = UIApplication.shared.delegate?.window??.rootViewController else {
|
|
49
|
+
self.rejecter?(String(ErrorCodes.noRootViewController), "Cannot find root view controller", nil)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let subscriptionVC = SubscriptionManagerViewController(
|
|
54
|
+
url: url,
|
|
55
|
+
productName: productName,
|
|
56
|
+
listener: self.subscriptionListener
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
rootVC.present(subscriptionVC, animated: true)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func sendExitEvent(error: SubscriptionManagerError?) {
|
|
64
|
+
if self.hasListeners {
|
|
65
|
+
var body: [String: Any] = [:]
|
|
66
|
+
|
|
67
|
+
if let error = error {
|
|
68
|
+
body["error"] = ["code": ErrorCodes.sdkError, "message": error.message]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
self.sendEvent(withName: "ScribeupOnExit", body: body)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override func startObserving() {
|
|
76
|
+
self.hasListeners = true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override func stopObserving() {
|
|
80
|
+
self.hasListeners = false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
class ScribeupSubscriptionListener: NSObject, SubscriptionManagerListener {
|
|
85
|
+
weak var delegate: Scribeup?
|
|
86
|
+
|
|
87
|
+
init(delegate: Scribeup) {
|
|
88
|
+
self.delegate = delegate
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func onExit(_ error: SubscriptionManagerError?) {
|
|
92
|
+
if let delegate = self.delegate {
|
|
93
|
+
if let error = error {
|
|
94
|
+
delegate.rejecter?(String(error.code), error.message, nil)
|
|
95
|
+
} else {
|
|
96
|
+
delegate.resolver?(nil)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
delegate.resolver = nil
|
|
100
|
+
delegate.rejecter = nil
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
self.delegate?.sendExitEvent(error: error)
|
|
104
|
+
}
|
|
105
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scribeup/react-native-scribeup",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"types": "src/index.ts",
|
|
7
|
+
"description": "ScribeUp React Native SDK",
|
|
8
|
+
"react-native": "src/index",
|
|
9
|
+
"source": "src/index",
|
|
10
|
+
"homepage": "https://github.com/scribeup/react-native-scribeup",
|
|
11
|
+
"files": [
|
|
12
|
+
"src",
|
|
13
|
+
"android",
|
|
14
|
+
"ios",
|
|
15
|
+
"RNScribeupSDK.podspec",
|
|
16
|
+
"!android/build",
|
|
17
|
+
"!ios/build",
|
|
18
|
+
"!**/__tests__",
|
|
19
|
+
"!**/__fixtures__",
|
|
20
|
+
"!**/__mocks__"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"dev": "./scripts/dev.sh",
|
|
25
|
+
"expo": "./scripts/dev_expo.sh"
|
|
26
|
+
},
|
|
27
|
+
"author": "ScribeUp",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"react": "*",
|
|
31
|
+
"react-native": "*"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/react-native": "^0.72.2",
|
|
35
|
+
"concurrently": "^9.2.0",
|
|
36
|
+
"prettier": "^3.3.3",
|
|
37
|
+
"typescript": "^4.9.5"
|
|
38
|
+
},
|
|
39
|
+
"codegenConfig": {
|
|
40
|
+
"name": "RNScribeUpSpec",
|
|
41
|
+
"type": "all",
|
|
42
|
+
"jsSrcsDir": "./src",
|
|
43
|
+
"outputDir": {
|
|
44
|
+
"ios": "ios/generated",
|
|
45
|
+
"android": "android/generated"
|
|
46
|
+
},
|
|
47
|
+
"android": {
|
|
48
|
+
"javaPackageName": "com.rnscribeupsdk"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"config": {
|
|
52
|
+
"nativeSDKVersions": {
|
|
53
|
+
"android": "0.5.0",
|
|
54
|
+
"ios": "0.5.7"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* ScribeUp component properties
|
|
11
|
+
*/
|
|
12
|
+
export interface ScribeUpProps {
|
|
13
|
+
url: string;
|
|
14
|
+
productName: string;
|
|
15
|
+
onExit: (data?: ExitData) => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* ScribeUp Component
|
|
19
|
+
*
|
|
20
|
+
* 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.
|
|
23
|
+
*/
|
|
24
|
+
declare class ScribeUp extends React.Component<ScribeUpProps> {
|
|
25
|
+
constructor(props: ScribeUpProps);
|
|
26
|
+
componentDidMount(): void;
|
|
27
|
+
private present;
|
|
28
|
+
render(): any;
|
|
29
|
+
}
|
|
30
|
+
export default ScribeUp;
|
package/src/ScribeUp.tsx
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { NativeModules, Platform } from "react-native";
|
|
3
|
+
|
|
4
|
+
const LINKING_ERROR =
|
|
5
|
+
`The package 'scribeup-react-native-scribeup' doesn't seem to be linked. Make sure: \n\n` +
|
|
6
|
+
Platform.select({
|
|
7
|
+
ios: "- You have run 'pod install'\n",
|
|
8
|
+
default: "",
|
|
9
|
+
}) +
|
|
10
|
+
"- You rebuilt the app after installing the package\n";
|
|
11
|
+
|
|
12
|
+
const Scribeup = NativeModules.Scribeup
|
|
13
|
+
? NativeModules.Scribeup
|
|
14
|
+
: new Proxy(
|
|
15
|
+
{},
|
|
16
|
+
{
|
|
17
|
+
get() {
|
|
18
|
+
throw new Error(LINKING_ERROR);
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Data type returned when the SDK process ends
|
|
25
|
+
*/
|
|
26
|
+
export type ExitData = {
|
|
27
|
+
message?: string;
|
|
28
|
+
code?: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* ScribeUp component properties
|
|
33
|
+
*/
|
|
34
|
+
export interface ScribeUpProps {
|
|
35
|
+
url: string;
|
|
36
|
+
productName: string;
|
|
37
|
+
onExit: (data?: ExitData) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* ScribeUp Component
|
|
42
|
+
*
|
|
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.
|
|
46
|
+
*/
|
|
47
|
+
class ScribeUp extends React.Component<ScribeUpProps> {
|
|
48
|
+
constructor(props: ScribeUpProps) {
|
|
49
|
+
super(props);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
componentDidMount() {
|
|
53
|
+
const { url, productName, onExit } = this.props;
|
|
54
|
+
this.present(url, productName, onExit);
|
|
55
|
+
}
|
|
56
|
+
|
|
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)
|
|
65
|
+
.then((result: any) => {
|
|
66
|
+
onExit(result || {});
|
|
67
|
+
})
|
|
68
|
+
.catch((error: any) => {
|
|
69
|
+
onExit({
|
|
70
|
+
message: error?.message || "Unknown error",
|
|
71
|
+
code: error?.code || -1,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
} catch (error: any) {
|
|
75
|
+
onExit({
|
|
76
|
+
message: error?.message || "Unknown error",
|
|
77
|
+
code: error?.code || -1,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
throw new Error(`ScribeUp: Native module not found for ${Platform.OS}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
render() {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default ScribeUp;
|
package/src/index.d.ts
ADDED
package/src/index.ts
ADDED