@octopus-community/react-native 1.0.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.
Files changed (106) hide show
  1. package/LICENSE.md +143 -0
  2. package/OctopusReactNativeSdk.podspec +33 -0
  3. package/README.md +161 -0
  4. package/android/build.gradle +92 -0
  5. package/android/gradle.properties +9 -0
  6. package/android/src/main/AndroidManifest.xml +9 -0
  7. package/android/src/main/AndroidManifestNew.xml +8 -0
  8. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusEventEmitter.kt +48 -0
  9. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusReactNativeSdkModule.kt +73 -0
  10. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusReactNativeSdkPackage.kt +17 -0
  11. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusSDKInitializer.kt +55 -0
  12. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusSSOAuthenticator.kt +124 -0
  13. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusUIActivity.kt +71 -0
  14. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusUIController.kt +34 -0
  15. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/ProfileFieldMapper.kt +39 -0
  16. package/ios/OctopusEventManager.swift +44 -0
  17. package/ios/OctopusReactNativeSdk-Bridging-Header.h +3 -0
  18. package/ios/OctopusReactNativeSdk.mm +37 -0
  19. package/ios/OctopusReactNativeSdk.swift +148 -0
  20. package/ios/OctopusSDKInitializer.swift +62 -0
  21. package/ios/OctopusSSOAuthenticator.swift +157 -0
  22. package/ios/OctopusUIManager.swift +40 -0
  23. package/ios/ProfileFieldMapper.swift +43 -0
  24. package/lib/module/addEditUserListener.js +13 -0
  25. package/lib/module/addEditUserListener.js.map +1 -0
  26. package/lib/module/addLoginRequiredListener.js +14 -0
  27. package/lib/module/addLoginRequiredListener.js.map +1 -0
  28. package/lib/module/addUserTokenRequestListener.js +26 -0
  29. package/lib/module/addUserTokenRequestListener.js.map +1 -0
  30. package/lib/module/closeUI.js +11 -0
  31. package/lib/module/closeUI.js.map +1 -0
  32. package/lib/module/connectUser.js +14 -0
  33. package/lib/module/connectUser.js.map +1 -0
  34. package/lib/module/disconnectUser.js +7 -0
  35. package/lib/module/disconnectUser.js.map +1 -0
  36. package/lib/module/enums/LogLevel.enum.js +13 -0
  37. package/lib/module/enums/LogLevel.enum.js.map +1 -0
  38. package/lib/module/index.js +15 -0
  39. package/lib/module/index.js.map +1 -0
  40. package/lib/module/initialize.js +38 -0
  41. package/lib/module/initialize.js.map +1 -0
  42. package/lib/module/internals/eventEmitter.js +6 -0
  43. package/lib/module/internals/eventEmitter.js.map +1 -0
  44. package/lib/module/internals/logger.js +42 -0
  45. package/lib/module/internals/logger.js.map +1 -0
  46. package/lib/module/internals/nativeModule.js +13 -0
  47. package/lib/module/internals/nativeModule.js.map +1 -0
  48. package/lib/module/logger.js +4 -0
  49. package/lib/module/logger.js.map +1 -0
  50. package/lib/module/openUI.js +11 -0
  51. package/lib/module/openUI.js.map +1 -0
  52. package/lib/module/package.json +1 -0
  53. package/lib/module/types/userProfileField.js +2 -0
  54. package/lib/module/types/userProfileField.js.map +1 -0
  55. package/lib/module/useUserTokenProvider.js +33 -0
  56. package/lib/module/useUserTokenProvider.js.map +1 -0
  57. package/lib/typescript/package.json +1 -0
  58. package/lib/typescript/src/addEditUserListener.d.ts +14 -0
  59. package/lib/typescript/src/addEditUserListener.d.ts.map +1 -0
  60. package/lib/typescript/src/addLoginRequiredListener.d.ts +10 -0
  61. package/lib/typescript/src/addLoginRequiredListener.d.ts.map +1 -0
  62. package/lib/typescript/src/addUserTokenRequestListener.d.ts +10 -0
  63. package/lib/typescript/src/addUserTokenRequestListener.d.ts.map +1 -0
  64. package/lib/typescript/src/closeUI.d.ts +5 -0
  65. package/lib/typescript/src/closeUI.d.ts.map +1 -0
  66. package/lib/typescript/src/connectUser.d.ts +30 -0
  67. package/lib/typescript/src/connectUser.d.ts.map +1 -0
  68. package/lib/typescript/src/disconnectUser.d.ts +2 -0
  69. package/lib/typescript/src/disconnectUser.d.ts.map +1 -0
  70. package/lib/typescript/src/enums/LogLevel.enum.d.ts +10 -0
  71. package/lib/typescript/src/enums/LogLevel.enum.d.ts.map +1 -0
  72. package/lib/typescript/src/index.d.ts +13 -0
  73. package/lib/typescript/src/index.d.ts.map +1 -0
  74. package/lib/typescript/src/initialize.d.ts +50 -0
  75. package/lib/typescript/src/initialize.d.ts.map +1 -0
  76. package/lib/typescript/src/internals/eventEmitter.d.ts +3 -0
  77. package/lib/typescript/src/internals/eventEmitter.d.ts.map +1 -0
  78. package/lib/typescript/src/internals/logger.d.ts +20 -0
  79. package/lib/typescript/src/internals/logger.d.ts.map +1 -0
  80. package/lib/typescript/src/internals/nativeModule.d.ts +2 -0
  81. package/lib/typescript/src/internals/nativeModule.d.ts.map +1 -0
  82. package/lib/typescript/src/logger.d.ts +3 -0
  83. package/lib/typescript/src/logger.d.ts.map +1 -0
  84. package/lib/typescript/src/openUI.d.ts +5 -0
  85. package/lib/typescript/src/openUI.d.ts.map +1 -0
  86. package/lib/typescript/src/types/userProfileField.d.ts +14 -0
  87. package/lib/typescript/src/types/userProfileField.d.ts.map +1 -0
  88. package/lib/typescript/src/useUserTokenProvider.d.ts +17 -0
  89. package/lib/typescript/src/useUserTokenProvider.d.ts.map +1 -0
  90. package/package.json +136 -0
  91. package/src/addEditUserListener.ts +19 -0
  92. package/src/addLoginRequiredListener.ts +16 -0
  93. package/src/addUserTokenRequestListener.ts +32 -0
  94. package/src/closeUI.ts +8 -0
  95. package/src/connectUser.ts +34 -0
  96. package/src/disconnectUser.ts +5 -0
  97. package/src/enums/LogLevel.enum.ts +9 -0
  98. package/src/index.ts +12 -0
  99. package/src/initialize.ts +56 -0
  100. package/src/internals/eventEmitter.ts +4 -0
  101. package/src/internals/logger.ts +60 -0
  102. package/src/internals/nativeModule.ts +18 -0
  103. package/src/logger.ts +7 -0
  104. package/src/openUI.ts +8 -0
  105. package/src/types/userProfileField.ts +13 -0
  106. package/src/useUserTokenProvider.ts +36 -0
@@ -0,0 +1,124 @@
1
+ package com.octopuscommunity.octopusreactnativesdk
2
+
3
+ import com.facebook.react.bridge.ReadableMap
4
+ import com.facebook.react.bridge.Promise
5
+ import com.octopuscommunity.sdk.OctopusSDK
6
+ import com.octopuscommunity.sdk.domain.model.ClientUser
7
+ import com.octopuscommunity.sdk.domain.model.ClientUser.Profile.AgeInformation
8
+ import com.octopuscommunity.sdk.domain.model.Image
9
+ import kotlinx.coroutines.CoroutineScope
10
+ import kotlinx.coroutines.Dispatchers
11
+ import kotlinx.coroutines.launch
12
+ import kotlinx.coroutines.suspendCancellableCoroutine
13
+ import kotlinx.coroutines.CancellationException
14
+ import java.util.UUID
15
+ import java.util.concurrent.ConcurrentHashMap
16
+ import kotlin.coroutines.resume
17
+ import kotlin.coroutines.resumeWithException
18
+
19
+ class OctopusSSOAuthenticator(private val eventEmitter: OctopusEventEmitter) {
20
+
21
+ private val coroutineScope = CoroutineScope(Dispatchers.Main)
22
+ private val pendingTokenRequests =
23
+ ConcurrentHashMap<String, kotlin.coroutines.Continuation<String>>()
24
+
25
+ fun connectUser(params: ReadableMap, promise: Promise) {
26
+ coroutineScope.launch {
27
+ try {
28
+ val clientUser = parseClientUser(params)
29
+
30
+ OctopusSDK.connectUser(
31
+ user = clientUser,
32
+ tokenProvider = { requestTokenFromRN() }
33
+ )
34
+
35
+ promise.resolve(null)
36
+ } catch (e: Exception) {
37
+ promise.reject("CONNECT_USER_ERROR", "Failed to connect user: ${e.message}", e)
38
+ }
39
+ }
40
+ }
41
+
42
+ fun disconnectUser(promise: Promise) {
43
+ coroutineScope.launch {
44
+ try {
45
+ OctopusSDK.disconnectUser()
46
+
47
+ // Cancel all pending token requests
48
+ for ((_, continuation) in pendingTokenRequests) {
49
+ continuation.resumeWithException(CancellationException("User disconnected"))
50
+ }
51
+ pendingTokenRequests.clear()
52
+
53
+ promise.resolve(null)
54
+ } catch (e: Exception) {
55
+ promise.reject("DISCONNECT_USER_ERROR", "Failed to disconnect user: ${e.message}", e)
56
+ }
57
+ }
58
+ }
59
+
60
+ fun completeTokenRequest(requestId: String, token: String) {
61
+ val continuation = pendingTokenRequests.remove(requestId)
62
+ continuation?.resume(token)
63
+ }
64
+
65
+ fun cancelTokenRequest(requestId: String) {
66
+ val continuation = pendingTokenRequests.remove(requestId)
67
+ continuation?.resumeWithException(CancellationException("Token request cancelled"))
68
+ }
69
+
70
+ private suspend fun requestTokenFromRN(): String {
71
+ val requestId = UUID.randomUUID().toString()
72
+
73
+ return suspendCancellableCoroutine { continuation ->
74
+ pendingTokenRequests[requestId] = continuation
75
+
76
+ continuation.invokeOnCancellation {
77
+ pendingTokenRequests.remove(requestId)
78
+ }
79
+
80
+ eventEmitter.emitUserTokenRequest(requestId)
81
+ }
82
+ }
83
+
84
+ private fun parseClientUser(params: ReadableMap): ClientUser {
85
+ val userId = params.getString("userId")
86
+ ?: throw IllegalArgumentException("Missing user id")
87
+
88
+ val profile = parseUserProfile(params.getMap("profile"))
89
+
90
+ return ClientUser(
91
+ userId = userId,
92
+ profile = profile
93
+ )
94
+ }
95
+
96
+ private fun parseUserProfile(profileParams: ReadableMap?): ClientUser.Profile {
97
+ if (profileParams == null) {
98
+ return ClientUser.Profile()
99
+ }
100
+
101
+ val ageInformation = if (profileParams.hasKey("legalAgeReached")) {
102
+ if (profileParams.getBoolean("legalAgeReached")) {
103
+ AgeInformation.LegalAgeReached
104
+ } else {
105
+ AgeInformation.Underage
106
+ }
107
+ } else null
108
+
109
+ val profilePicture = profileParams.getString("profilePicture")?.let { pictureUrl ->
110
+ if (pictureUrl.startsWith("http://") || pictureUrl.startsWith("https://")) {
111
+ Image.Remote(pictureUrl)
112
+ } else {
113
+ Image.Local(pictureUrl)
114
+ }
115
+ }
116
+
117
+ return ClientUser.Profile(
118
+ nickname = profileParams.getString("username"),
119
+ bio = profileParams.getString("biography"),
120
+ ageInformation = ageInformation,
121
+ avatar = profilePicture
122
+ )
123
+ }
124
+ }
@@ -0,0 +1,71 @@
1
+ package com.octopuscommunity.octopusreactnativesdk
2
+
3
+ import android.content.BroadcastReceiver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.content.IntentFilter
7
+ import android.os.Build
8
+ import android.os.Bundle
9
+ import androidx.activity.ComponentActivity
10
+ import androidx.activity.compose.setContent
11
+ import androidx.compose.foundation.layout.Box
12
+ import androidx.compose.foundation.layout.fillMaxSize
13
+ import androidx.compose.runtime.Composable
14
+ import androidx.compose.ui.Modifier
15
+ import androidx.navigation.compose.NavHost
16
+ import androidx.navigation.compose.rememberNavController
17
+ import com.octopuscommunity.sdk.ui.OctopusDestination
18
+ import com.octopuscommunity.sdk.ui.octopusComposables
19
+
20
+ class OctopusUIActivity : ComponentActivity() {
21
+ private val closeUIReceiver = object : BroadcastReceiver() {
22
+ override fun onReceive(context: Context?, intent: Intent?) {
23
+ finish()
24
+ }
25
+ }
26
+
27
+ override fun onCreate(savedInstanceState: Bundle?) {
28
+ super.onCreate(savedInstanceState)
29
+ registerCloseUIReceiver()
30
+ setContent {
31
+ OctopusUI()
32
+ }
33
+ }
34
+
35
+ override fun onDestroy() {
36
+ super.onDestroy()
37
+ unregisterReceiver(closeUIReceiver)
38
+ }
39
+
40
+ private fun registerCloseUIReceiver() {
41
+ val intentFilter = IntentFilter(OctopusUIController.CLOSE_UI_ACTION)
42
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
43
+ registerReceiver(closeUIReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
44
+ } else {
45
+ @Suppress("UnspecifiedRegisterReceiverFlag")
46
+ registerReceiver(closeUIReceiver, intentFilter)
47
+ }
48
+ }
49
+ }
50
+
51
+ @Composable
52
+ private fun OctopusUI() {
53
+ Box(modifier = Modifier.fillMaxSize()) {
54
+ val navController = rememberNavController()
55
+
56
+ NavHost(
57
+ navController = navController,
58
+ startDestination = OctopusDestination.Home
59
+ ) {
60
+ octopusComposables(
61
+ navController = navController,
62
+ onNavigateToLogin = {
63
+ OctopusEventEmitter.instance?.emitLoginRequired()
64
+ },
65
+ onNavigateToProfileEdit = { fieldToEdit ->
66
+ OctopusEventEmitter.instance?.emitEditUser(fieldToEdit)
67
+ }
68
+ )
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,34 @@
1
+ package com.octopuscommunity.octopusreactnativesdk
2
+
3
+ import android.content.Intent
4
+ import com.facebook.react.bridge.Promise
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+
7
+ class OctopusUIController(private val reactContext: ReactApplicationContext) {
8
+
9
+ fun openUI(promise: Promise) {
10
+ try {
11
+ val intent = Intent(reactContext, OctopusUIActivity::class.java)
12
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
13
+ reactContext.startActivity(intent)
14
+ promise.resolve(null)
15
+ } catch (e: Exception) {
16
+ promise.reject("OPEN_UI_ERROR", "Failed to open Octopus UI", e)
17
+ }
18
+ }
19
+
20
+ fun closeUI(promise: Promise) {
21
+ try {
22
+ val intent = Intent(CLOSE_UI_ACTION)
23
+ intent.setPackage(reactContext.packageName)
24
+ reactContext.sendBroadcast(intent)
25
+ promise.resolve(null)
26
+ } catch (e: Exception) {
27
+ promise.reject("CLOSE_UI_ERROR", "Failed to close Octopus UI", e)
28
+ }
29
+ }
30
+
31
+ companion object {
32
+ const val CLOSE_UI_ACTION = "com.octopuscommunity.octopusreactnativesdk.CLOSE_UI"
33
+ }
34
+ }
@@ -0,0 +1,39 @@
1
+ package com.octopuscommunity.octopusreactnativesdk
2
+
3
+ import com.facebook.react.bridge.ReadableArray
4
+ import com.octopuscommunity.sdk.domain.model.ProfileField
5
+
6
+ object ProfileFieldMapper {
7
+
8
+ fun fromReactNativeArray(appManagedFields: ReadableArray?): Set<ProfileField> {
9
+ if (appManagedFields == null) {
10
+ return emptySet()
11
+ }
12
+
13
+ val profileFields = mutableSetOf<ProfileField>()
14
+ for (i in 0 until appManagedFields.size()) {
15
+ fromReactNativeString(appManagedFields.getString(i))?.let { profileField ->
16
+ profileFields.add(profileField)
17
+ }
18
+ }
19
+ return profileFields
20
+ }
21
+
22
+ fun fromReactNativeString(fieldName: String?): ProfileField? {
23
+ return when (fieldName) {
24
+ "username" -> ProfileField.NICKNAME
25
+ "biography" -> ProfileField.BIO
26
+ "profilePicture" -> ProfileField.AVATAR
27
+ else -> null
28
+ }
29
+ }
30
+
31
+ fun toReactNativeString(profileField: ProfileField?): String? {
32
+ return when (profileField) {
33
+ ProfileField.NICKNAME -> "username"
34
+ ProfileField.BIO -> "biography"
35
+ ProfileField.AVATAR -> "profilePicture"
36
+ else -> null
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,44 @@
1
+ import React
2
+ import Octopus
3
+
4
+ class OctopusEventManager {
5
+ private weak var eventEmitter: RCTEventEmitter?
6
+ private var hasListeners = false
7
+
8
+ init(eventEmitter: RCTEventEmitter) {
9
+ self.eventEmitter = eventEmitter
10
+ }
11
+
12
+ func emitLoginRequired() {
13
+ if hasListeners {
14
+ eventEmitter?.sendEvent(withName: "loginRequired", body: nil)
15
+ }
16
+ }
17
+
18
+ func emitEditUser(profileField: ConnectionMode.SSOConfiguration.ProfileField?) {
19
+ if hasListeners {
20
+ let fieldToEdit = ProfileFieldMapper.toReactNativeString(profileField)
21
+ let eventBody = ["fieldToEdit": fieldToEdit]
22
+ eventEmitter?.sendEvent(withName: "editUser", body: eventBody)
23
+ }
24
+ }
25
+
26
+ func emitUserTokenRequest(requestId: String) {
27
+ if hasListeners {
28
+ let eventBody = ["requestId": requestId]
29
+ eventEmitter?.sendEvent(withName: "userTokenRequest", body: eventBody)
30
+ }
31
+ }
32
+
33
+ func startObserving() {
34
+ hasListeners = true
35
+ }
36
+
37
+ func stopObserving() {
38
+ hasListeners = false
39
+ }
40
+
41
+ func supportedEvents() -> [String] {
42
+ return ["loginRequired", "editUser", "userTokenRequest"]
43
+ }
44
+ }
@@ -0,0 +1,3 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTViewManager.h>
3
+ #import <React/RCTEventEmitter.h>
@@ -0,0 +1,37 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+ @interface RCT_EXTERN_MODULE(OctopusReactNativeSdk, RCTEventEmitter)
5
+
6
+ RCT_EXTERN_METHOD(initialize:(NSDictionary *)options
7
+ withResolver:(RCTPromiseResolveBlock)resolve
8
+ withRejecter:(RCTPromiseRejectBlock)reject)
9
+
10
+ RCT_EXTERN_METHOD(openUI:(RCTPromiseResolveBlock)resolve
11
+ withRejecter:(RCTPromiseRejectBlock)reject)
12
+
13
+ RCT_EXTERN_METHOD(closeUI:(RCTPromiseResolveBlock)resolve
14
+ withRejecter:(RCTPromiseRejectBlock)reject)
15
+
16
+ RCT_EXTERN_METHOD(connectUser:(NSDictionary *)params
17
+ withResolver:(RCTPromiseResolveBlock)resolve
18
+ withRejecter:(RCTPromiseRejectBlock)reject)
19
+
20
+ RCT_EXTERN_METHOD(completeUserTokenRequest:(NSString *)requestId
21
+ withToken:(NSString *)token
22
+ withResolver:(RCTPromiseResolveBlock)resolve
23
+ withRejecter:(RCTPromiseRejectBlock)reject)
24
+
25
+ RCT_EXTERN_METHOD(cancelUserTokenRequest:(NSString *)requestId
26
+ withResolver:(RCTPromiseResolveBlock)resolve
27
+ withRejecter:(RCTPromiseRejectBlock)reject)
28
+
29
+ RCT_EXTERN_METHOD(disconnectUser:(RCTPromiseResolveBlock)resolve
30
+ withRejecter:(RCTPromiseRejectBlock)reject)
31
+
32
+ + (BOOL)requiresMainQueueSetup
33
+ {
34
+ return NO;
35
+ }
36
+
37
+ @end
@@ -0,0 +1,148 @@
1
+ import Octopus
2
+ import OctopusUI
3
+ import SwiftUI
4
+ import UIKit
5
+
6
+ @objc(OctopusReactNativeSdk)
7
+ class OctopusReactNativeSdk: RCTEventEmitter {
8
+
9
+ // MARK: - Properties
10
+
11
+ private var octopusSDK: OctopusSDK?
12
+ private lazy var uiManager = OctopusUIManager()
13
+ private lazy var eventManager = OctopusEventManager(eventEmitter: self)
14
+ private let sdkInitializer = OctopusSDKInitializer()
15
+ private var ssoAuthenticator: OctopusSSOAuthenticator?
16
+
17
+ // MARK: - Initialization
18
+
19
+ @objc(initialize:withResolver:withRejecter:)
20
+ func initialize(options: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
21
+ do {
22
+ self.octopusSDK = try sdkInitializer.initialize(options: options, eventManager: eventManager)
23
+ self.ssoAuthenticator = OctopusSSOAuthenticator(octopusSDK: self.octopusSDK!, eventManager: eventManager)
24
+ resolve(nil)
25
+ } catch {
26
+ reject("INITIALIZE_ERROR", "Failed to initialize Octopus SDK: \(error.localizedDescription)", error)
27
+ }
28
+ }
29
+
30
+ // MARK: - User authentication
31
+
32
+ @objc(connectUser:withResolver:withRejecter:)
33
+ func connectUser(params: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
34
+ guard let authenticator = ssoAuthenticator else {
35
+ reject("CONNECT_USER_ERROR", "SDK not initialized. Call initialize() first.", nil)
36
+ return
37
+ }
38
+
39
+ Task {
40
+ do {
41
+ try await authenticator.connectUser(params: params)
42
+ resolve(nil)
43
+ } catch {
44
+ reject("CONNECT_USER_ERROR", "Failed to connect user: \(error.localizedDescription)", error)
45
+ }
46
+ }
47
+
48
+ }
49
+
50
+ @objc(completeUserTokenRequest:withToken:withResolver:withRejecter:)
51
+ func completeUserTokenRequest(requestId: String, token: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
52
+ guard let authenticator = ssoAuthenticator else {
53
+ reject("PROVIDE_TOKEN_ERROR", "SDK not initialized", nil)
54
+ return
55
+ }
56
+
57
+ authenticator.completeTokenRequest(requestId: requestId, token: token)
58
+ resolve(nil)
59
+ }
60
+
61
+ @objc(cancelUserTokenRequest:withResolver:withRejecter:)
62
+ func cancelUserTokenRequest(requestId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
63
+ guard let authenticator = ssoAuthenticator else {
64
+ reject("PROVIDE_TOKEN_ERROR", "SDK not initialized", nil)
65
+ return
66
+ }
67
+
68
+ authenticator.cancelTokenRequest(requestId: requestId)
69
+ resolve(nil)
70
+ }
71
+
72
+ @objc(disconnectUser:withRejecter:)
73
+ func disconnectUser(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
74
+ guard let authenticator = ssoAuthenticator else {
75
+ reject("DISCONNECT_USER_ERROR", "SDK not initialized. Call initialize() first.", nil)
76
+ return
77
+ }
78
+
79
+ do {
80
+ try authenticator.disconnectUser()
81
+ resolve(nil)
82
+ } catch {
83
+ reject("DISCONNECT_USER_ERROR", "Failed to disconnect user: \(error.localizedDescription)", error)
84
+ }
85
+
86
+ }
87
+
88
+ // MARK: - UI management
89
+
90
+ @objc(openUI:withRejecter:)
91
+ func openUI(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
92
+ guard let octopus = octopusSDK else {
93
+ reject("OPEN_UI_ERROR", "SDK not initialized. Call initialize() first.", nil)
94
+ return
95
+ }
96
+
97
+ DispatchQueue.main.async {
98
+ do {
99
+ try self.uiManager.openUI(octopus: octopus)
100
+ resolve(nil)
101
+ } catch {
102
+ reject("OPEN_UI_ERROR", error.localizedDescription, error)
103
+ }
104
+ }
105
+ }
106
+
107
+ @objc(closeUI:withRejecter:)
108
+ func closeUI(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
109
+ DispatchQueue.main.async {
110
+ do {
111
+ try self.uiManager.closeUI()
112
+ resolve(nil)
113
+ } catch {
114
+ reject("CLOSE_UI_ERROR", error.localizedDescription, error)
115
+ }
116
+ }
117
+ }
118
+
119
+ // MARK: - Lifecycle management
120
+
121
+ deinit {
122
+ cleanup()
123
+ }
124
+
125
+ private func cleanup() {
126
+ uiManager.cleanup()
127
+ octopusSDK = nil
128
+ }
129
+
130
+ @objc override func invalidate() {
131
+ cleanup()
132
+ super.invalidate()
133
+ }
134
+
135
+ // MARK: - RCTEventEmitter overrides
136
+
137
+ @objc override func supportedEvents() -> [String]! {
138
+ return eventManager.supportedEvents()
139
+ }
140
+
141
+ @objc override func startObserving() {
142
+ eventManager.startObserving()
143
+ }
144
+
145
+ @objc override func stopObserving() {
146
+ eventManager.stopObserving()
147
+ }
148
+ }
@@ -0,0 +1,62 @@
1
+ import Octopus
2
+
3
+ class OctopusSDKInitializer {
4
+ func initialize(options: [String: Any], eventManager: OctopusEventManager) throws -> OctopusSDK {
5
+ guard let apiKey = options["apiKey"] as? String else {
6
+ throw InitializationError.missingAPIKey
7
+ }
8
+
9
+ guard let connectionModeMap = options["connectionMode"] as? [String: Any] else {
10
+ throw InitializationError.missingConnectionMode
11
+ }
12
+
13
+ let connectionMode = try parseConnectionMode(from: connectionModeMap, eventManager: eventManager)
14
+ return try OctopusSDK(apiKey: apiKey, connectionMode: connectionMode)
15
+ }
16
+
17
+ private func parseConnectionMode(from connectionModeMap: [String: Any], eventManager: OctopusEventManager) throws -> ConnectionMode {
18
+ let connectionModeType = connectionModeMap["type"] as? String
19
+
20
+ switch connectionModeType {
21
+ case "sso":
22
+ return try createSSOConnectionMode(from: connectionModeMap, eventManager: eventManager)
23
+ case "octopus":
24
+ return .octopus(deepLink: nil)
25
+ default:
26
+ throw InitializationError.invalidConnectionModeType
27
+ }
28
+ }
29
+
30
+ private func createSSOConnectionMode(from connectionModeMap: [String: Any], eventManager: OctopusEventManager) throws -> ConnectionMode {
31
+ let appManagedFields = ProfileFieldMapper.fromReactNativeArray(connectionModeMap["appManagedFields"] as? [String])
32
+
33
+ return .sso(
34
+ .init(
35
+ appManagedFields: appManagedFields,
36
+ loginRequired: {
37
+ eventManager.emitLoginRequired()
38
+ },
39
+ modifyUser: { profileField in
40
+ eventManager.emitEditUser(profileField: profileField)
41
+ }
42
+ )
43
+ )
44
+ }
45
+ }
46
+
47
+ enum InitializationError: Error, LocalizedError {
48
+ case missingAPIKey
49
+ case missingConnectionMode
50
+ case invalidConnectionModeType
51
+
52
+ var errorDescription: String? {
53
+ switch self {
54
+ case .missingAPIKey:
55
+ return "API key is required"
56
+ case .missingConnectionMode:
57
+ return "Connection mode is required"
58
+ case .invalidConnectionModeType:
59
+ return "Invalid connection mode type"
60
+ }
61
+ }
62
+ }