@octopus-community/react-native 1.0.8 → 1.9.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.
Files changed (108) hide show
  1. package/OctopusReactNativeSdk.podspec +1 -1
  2. package/README.md +40 -35
  3. package/android/build.gradle +2 -0
  4. package/android/gradle.properties +2 -2
  5. package/android/src/main/AndroidManifest.xml +2 -1
  6. package/android/src/main/AndroidManifestNew.xml +2 -1
  7. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusActivity.kt +56 -0
  8. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusContent.kt +396 -0
  9. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusEventEmitter.kt +22 -0
  10. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusEventSerializer.kt +339 -0
  11. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusReactModule.kt +326 -0
  12. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/{OctopusReactNativeSdkPackage.kt → OctopusReactPackage.kt} +3 -3
  13. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusSDKInitializer.kt +22 -9
  14. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusSSOAuthenticator.kt +5 -15
  15. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusUIController.kt +17 -2
  16. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusUIViewManager.kt +63 -0
  17. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/ProfileFieldMapper.kt +2 -2
  18. package/ios/OctopusEventManager.swift +27 -0
  19. package/ios/OctopusEventSerializer.swift +271 -0
  20. package/ios/OctopusReactNativeSdk.mm +26 -1
  21. package/ios/OctopusReactNativeSdk.swift +216 -2
  22. package/ios/OctopusSSOAuthenticator.swift +1 -5
  23. package/ios/OctopusUIManager.swift +83 -0
  24. package/ios/OctopusUIViewManager.m +7 -0
  25. package/ios/OctopusUIViewManager.swift +37 -0
  26. package/lib/module/OctopusUIView.js +39 -0
  27. package/lib/module/OctopusUIView.js.map +1 -0
  28. package/lib/module/addHasAccessToCommunityListener.js +33 -0
  29. package/lib/module/addHasAccessToCommunityListener.js.map +1 -0
  30. package/lib/module/addNavigateToUrlListener.js +41 -0
  31. package/lib/module/addNavigateToUrlListener.js.map +1 -0
  32. package/lib/module/addNotSeenNotificationsCountListener.js +30 -0
  33. package/lib/module/addNotSeenNotificationsCountListener.js.map +1 -0
  34. package/lib/module/addSDKEventListener.js +48 -0
  35. package/lib/module/addSDKEventListener.js.map +1 -0
  36. package/lib/module/connectUser.js +24 -3
  37. package/lib/module/connectUser.js.map +1 -1
  38. package/lib/module/index.js +12 -0
  39. package/lib/module/index.js.map +1 -1
  40. package/lib/module/initialize.js +9 -12
  41. package/lib/module/initialize.js.map +1 -1
  42. package/lib/module/openUI.js +23 -2
  43. package/lib/module/openUI.js.map +1 -1
  44. package/lib/module/overrideCommunityAccess.js +36 -0
  45. package/lib/module/overrideCommunityAccess.js.map +1 -0
  46. package/lib/module/overrideDefaultLocale.js +75 -0
  47. package/lib/module/overrideDefaultLocale.js.map +1 -0
  48. package/lib/module/trackCommunityAccess.js +33 -0
  49. package/lib/module/trackCommunityAccess.js.map +1 -0
  50. package/lib/module/trackCustomEvent.js +36 -0
  51. package/lib/module/trackCustomEvent.js.map +1 -0
  52. package/lib/module/types/sdkEvents.js +2 -0
  53. package/lib/module/types/sdkEvents.js.map +1 -0
  54. package/lib/module/types/urlOpeningStrategy.js +23 -0
  55. package/lib/module/types/urlOpeningStrategy.js.map +1 -0
  56. package/lib/module/updateNotSeenNotificationsCount.js +33 -0
  57. package/lib/module/updateNotSeenNotificationsCount.js.map +1 -0
  58. package/lib/typescript/src/OctopusUIView.d.ts +32 -0
  59. package/lib/typescript/src/OctopusUIView.d.ts.map +1 -0
  60. package/lib/typescript/src/addHasAccessToCommunityListener.d.ts +27 -0
  61. package/lib/typescript/src/addHasAccessToCommunityListener.d.ts.map +1 -0
  62. package/lib/typescript/src/addNavigateToUrlListener.d.ts +31 -0
  63. package/lib/typescript/src/addNavigateToUrlListener.d.ts.map +1 -0
  64. package/lib/typescript/src/addNotSeenNotificationsCountListener.d.ts +24 -0
  65. package/lib/typescript/src/addNotSeenNotificationsCountListener.d.ts.map +1 -0
  66. package/lib/typescript/src/addSDKEventListener.d.ts +43 -0
  67. package/lib/typescript/src/addSDKEventListener.d.ts.map +1 -0
  68. package/lib/typescript/src/connectUser.d.ts +24 -8
  69. package/lib/typescript/src/connectUser.d.ts.map +1 -1
  70. package/lib/typescript/src/index.d.ts +13 -0
  71. package/lib/typescript/src/index.d.ts.map +1 -1
  72. package/lib/typescript/src/initialize.d.ts +9 -12
  73. package/lib/typescript/src/initialize.d.ts.map +1 -1
  74. package/lib/typescript/src/openUI.d.ts +28 -1
  75. package/lib/typescript/src/openUI.d.ts.map +1 -1
  76. package/lib/typescript/src/overrideCommunityAccess.d.ts +30 -0
  77. package/lib/typescript/src/overrideCommunityAccess.d.ts.map +1 -0
  78. package/lib/typescript/src/overrideDefaultLocale.d.ts +37 -0
  79. package/lib/typescript/src/overrideDefaultLocale.d.ts.map +1 -0
  80. package/lib/typescript/src/trackCommunityAccess.d.ts +27 -0
  81. package/lib/typescript/src/trackCommunityAccess.d.ts.map +1 -0
  82. package/lib/typescript/src/trackCustomEvent.d.ts +30 -0
  83. package/lib/typescript/src/trackCustomEvent.d.ts.map +1 -0
  84. package/lib/typescript/src/types/sdkEvents.d.ts +222 -0
  85. package/lib/typescript/src/types/sdkEvents.d.ts.map +1 -0
  86. package/lib/typescript/src/types/urlOpeningStrategy.d.ts +20 -0
  87. package/lib/typescript/src/types/urlOpeningStrategy.d.ts.map +1 -0
  88. package/lib/typescript/src/updateNotSeenNotificationsCount.d.ts +27 -0
  89. package/lib/typescript/src/updateNotSeenNotificationsCount.d.ts.map +1 -0
  90. package/package.json +2 -1
  91. package/src/OctopusUIView.tsx +57 -0
  92. package/src/addHasAccessToCommunityListener.ts +38 -0
  93. package/src/addNavigateToUrlListener.ts +54 -0
  94. package/src/addNotSeenNotificationsCountListener.ts +35 -0
  95. package/src/addSDKEventListener.ts +49 -0
  96. package/src/connectUser.ts +24 -8
  97. package/src/index.ts +13 -0
  98. package/src/initialize.ts +9 -12
  99. package/src/openUI.ts +32 -2
  100. package/src/overrideCommunityAccess.ts +33 -0
  101. package/src/overrideDefaultLocale.ts +88 -0
  102. package/src/trackCommunityAccess.ts +30 -0
  103. package/src/trackCustomEvent.ts +36 -0
  104. package/src/types/sdkEvents.ts +315 -0
  105. package/src/types/urlOpeningStrategy.ts +20 -0
  106. package/src/updateNotSeenNotificationsCount.ts +30 -0
  107. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusReactNativeSdkModule.kt +0 -155
  108. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusUIActivity.kt +0 -434
@@ -0,0 +1,339 @@
1
+ package com.octopuscommunity.octopusreactnativesdk
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.WritableMap
5
+ import com.octopuscommunity.sdk.domain.model.Gamification
6
+ import com.octopuscommunity.sdk.domain.model.Moderation
7
+ import com.octopuscommunity.sdk.domain.model.OctopusEvent
8
+ import com.octopuscommunity.sdk.domain.model.OctopusItem
9
+
10
+ /**
11
+ * Serializes OctopusEvent objects to WritableMap for React Native bridge.
12
+ */
13
+ object OctopusEventSerializer {
14
+
15
+ fun serializeEvent(event: OctopusEvent): WritableMap? {
16
+ val map = Arguments.createMap()
17
+
18
+ return when (event) {
19
+ is OctopusEvent.PostCreated -> {
20
+ val contentList = event.content.map { content ->
21
+ when (content) {
22
+ OctopusEvent.PostCreated.Content.TEXT -> "text"
23
+ OctopusEvent.PostCreated.Content.IMAGE -> "image"
24
+ OctopusEvent.PostCreated.Content.POLL -> "poll"
25
+ }
26
+ }
27
+ map.putString("type", "postCreated")
28
+ map.putString("postId", event.postId)
29
+ val contentArray = Arguments.createArray()
30
+ contentList.forEach { contentArray.pushString(it) }
31
+ map.putArray("content", contentArray)
32
+ map.putString("topicId", event.topicId)
33
+ map.putInt("textLength", event.textLength)
34
+ map
35
+ }
36
+
37
+ is OctopusEvent.CommentCreated -> {
38
+ map.putString("type", "commentCreated")
39
+ map.putString("commentId", event.commentId)
40
+ map.putString("postId", event.postId)
41
+ map.putInt("textLength", event.textLength)
42
+ map
43
+ }
44
+
45
+ is OctopusEvent.ReplyCreated -> {
46
+ map.putString("type", "replyCreated")
47
+ map.putString("replyId", event.replyId)
48
+ map.putString("commentId", event.commentId)
49
+ map.putInt("textLength", event.textLength)
50
+ map
51
+ }
52
+
53
+ is OctopusEvent.PostDeleted -> {
54
+ map.putString("type", "contentDeleted")
55
+ map.putString("contentId", event.contentId)
56
+ map.putString("contentKind", "post")
57
+ map
58
+ }
59
+
60
+ is OctopusEvent.CommentDeleted -> {
61
+ map.putString("type", "contentDeleted")
62
+ map.putString("contentId", event.contentId)
63
+ map.putString("contentKind", "comment")
64
+ map
65
+ }
66
+
67
+ is OctopusEvent.ReplyDeleted -> {
68
+ map.putString("type", "contentDeleted")
69
+ map.putString("contentId", event.contentId)
70
+ map.putString("contentKind", "reply")
71
+ map
72
+ }
73
+
74
+ is OctopusEvent.ReactionModified -> {
75
+ map.putString("type", "reactionModified")
76
+ map.putString("contentId", event.contentId)
77
+ map.putString("contentKind", serializeContentKind(event.contentKind))
78
+ event.previousReaction?.let {
79
+ map.putString("previousReaction", serializeReactionKind(it))
80
+ }
81
+ event.newReaction?.let {
82
+ map.putString("newReaction", serializeReactionKind(it))
83
+ }
84
+ map
85
+ }
86
+
87
+ is OctopusEvent.PollVote -> {
88
+ map.putString("type", "pollVoted")
89
+ map.putString("contentId", event.contentId)
90
+ map.putString("optionId", event.optionId)
91
+ map
92
+ }
93
+
94
+ is OctopusEvent.ContentReported -> {
95
+ map.putString("type", "contentReported")
96
+ map.putString("contentId", event.contentId)
97
+ val reasonsArray = Arguments.createArray()
98
+ event.reasons.forEach { reason ->
99
+ reasonsArray.pushString(serializeReportReason(reason))
100
+ }
101
+ map.putArray("reasons", reasonsArray)
102
+ map
103
+ }
104
+
105
+ is OctopusEvent.ProfileReported -> {
106
+ map.putString("type", "profileReported")
107
+ map.putString("profileId", event.profileId)
108
+ val reasonsArray = Arguments.createArray()
109
+ event.reasons.forEach { reason ->
110
+ reasonsArray.pushString(serializeReportReason(reason))
111
+ }
112
+ map.putArray("reasons", reasonsArray)
113
+ map
114
+ }
115
+
116
+ is OctopusEvent.GamificationPointsGained -> {
117
+ map.putString("type", "gamificationPointsGained")
118
+ map.putInt("points", event.points)
119
+ map.putString("action", serializeGamificationAction(event.action))
120
+ map
121
+ }
122
+
123
+ is OctopusEvent.GamificationPointsRemoved -> {
124
+ map.putString("type", "gamificationPointsRemoved")
125
+ map.putInt("points", event.points)
126
+ map.putString("action", serializeGamificationPointsRemovedAction(event.action))
127
+ map
128
+ }
129
+
130
+ is OctopusEvent.ScreenDisplayed -> {
131
+ map.putString("type", "screenDisplayed")
132
+ val screenMap = serializeScreen(event)
133
+ map.putMap("screen", screenMap)
134
+ map
135
+ }
136
+
137
+ is OctopusEvent.NotificationClicked -> {
138
+ map.putString("type", "notificationClicked")
139
+ map.putString("notificationId", event.notificationId)
140
+ event.contentId?.let {
141
+ map.putString("contentId", it)
142
+ }
143
+ map
144
+ }
145
+
146
+ is OctopusEvent.PostClicked -> {
147
+ map.putString("type", "postClicked")
148
+ map.putString("postId", event.postId)
149
+ map.putString("source", when (event.source) {
150
+ OctopusEvent.PostClicked.Source.FEED -> "feed"
151
+ OctopusEvent.PostClicked.Source.PROFILE -> "profile"
152
+ })
153
+ map
154
+ }
155
+
156
+ is OctopusEvent.TranslationButtonClicked -> {
157
+ map.putString("type", "translationButtonClicked")
158
+ map.putString("contentId", event.contentId)
159
+ map.putBoolean("viewTranslated", event.viewTranslated)
160
+ map.putString("contentKind", serializeContentKind(event.contentKind))
161
+ map
162
+ }
163
+
164
+ is OctopusEvent.CommentButtonClicked -> {
165
+ map.putString("type", "commentButtonClicked")
166
+ map.putString("postId", event.postId)
167
+ map
168
+ }
169
+
170
+ is OctopusEvent.ReplyButtonClicked -> {
171
+ map.putString("type", "replyButtonClicked")
172
+ map.putString("commentId", event.commentId)
173
+ map
174
+ }
175
+
176
+ is OctopusEvent.SeeRepliesButtonClicked -> {
177
+ map.putString("type", "seeRepliesButtonClicked")
178
+ map.putString("commentId", event.commentId)
179
+ map
180
+ }
181
+
182
+ is OctopusEvent.ProfileModified -> {
183
+ val prev = event.previousProfile
184
+ val new = event.newProfile
185
+ val nicknameUpdated = prev?.nickname != new.nickname
186
+ val bioUpdated = prev?.bio != new.bio
187
+ val pictureUpdated = prev?.picture != new.picture
188
+
189
+ map.putString("type", "profileModified")
190
+ map.putBoolean("nicknameUpdated", nicknameUpdated)
191
+ map.putBoolean("bioUpdated", bioUpdated)
192
+ if (bioUpdated) {
193
+ new.bio?.length?.let { map.putInt("bioLength", it) }
194
+ }
195
+ map.putBoolean("pictureUpdated", pictureUpdated)
196
+ if (pictureUpdated) {
197
+ map.putBoolean("hasPicture", new.picture != null)
198
+ }
199
+ map
200
+ }
201
+
202
+ is OctopusEvent.SessionStarted -> {
203
+ map.putString("type", "sessionStarted")
204
+ map.putString("sessionId", event.sessionId)
205
+ map
206
+ }
207
+
208
+ is OctopusEvent.SessionStopped -> {
209
+ map.putString("type", "sessionStopped")
210
+ map.putString("sessionId", event.sessionId)
211
+ map
212
+ }
213
+
214
+ else -> null
215
+ }
216
+ }
217
+
218
+ private fun serializeContentKind(kind: OctopusItem.ContentKind): String {
219
+ return when (kind) {
220
+ OctopusItem.ContentKind.POST -> "post"
221
+ OctopusItem.ContentKind.COMMENT -> "comment"
222
+ OctopusItem.ContentKind.REPLY -> "reply"
223
+ }
224
+ }
225
+
226
+ private fun serializeReactionKind(kind: OctopusItem.Reaction.Kind): String {
227
+ return when (kind) {
228
+ is OctopusItem.Reaction.Kind.Heart -> "heart"
229
+ is OctopusItem.Reaction.Kind.Joy -> "joy"
230
+ is OctopusItem.Reaction.Kind.MouthOpen -> "mouthOpen"
231
+ is OctopusItem.Reaction.Kind.Clap -> "clap"
232
+ is OctopusItem.Reaction.Kind.Cry -> "cry"
233
+ is OctopusItem.Reaction.Kind.Rage -> "rage"
234
+ is OctopusItem.Reaction.Kind.Unknown -> "unknown"
235
+ }
236
+ }
237
+
238
+ private fun serializeReportReason(reason: Moderation.ReportReason): String {
239
+ return when (reason) {
240
+ is Moderation.ReportReason.HateSpeechOrDiscriminatoryContent -> "hateSpeech"
241
+ is Moderation.ReportReason.ExplicitOrInappropriateContent -> "explicit"
242
+ is Moderation.ReportReason.ViolenceAndTerrorism -> "violence"
243
+ is Moderation.ReportReason.SpamAndScams -> "spam"
244
+ is Moderation.ReportReason.SuicideAndSelfHarm -> "suicide"
245
+ is Moderation.ReportReason.FakeProfilesAndImpersonation -> "fakeProfile"
246
+ is Moderation.ReportReason.ChildExploitationOrAbuse -> "childExploitation"
247
+ is Moderation.ReportReason.IntellectualPropertyViolation -> "intellectualProperty"
248
+ is Moderation.ReportReason.Other -> "other"
249
+ }
250
+ }
251
+
252
+ private fun serializeGamificationAction(action: Gamification.Action): String {
253
+ return when (action) {
254
+ Gamification.Action.POST -> "post"
255
+ Gamification.Action.COMMENT -> "comment"
256
+ Gamification.Action.REPLY -> "reply"
257
+ Gamification.Action.REACTION -> "reaction"
258
+ Gamification.Action.VOTE -> "vote"
259
+ Gamification.Action.POST_COMMENTED -> "postCommented"
260
+ Gamification.Action.PROFILE_COMPLETED -> "profileCompleted"
261
+ Gamification.Action.DAILY_SESSION -> "dailySession"
262
+ }
263
+ }
264
+
265
+ private fun serializeGamificationPointsRemovedAction(action: Gamification.Action): String {
266
+ // In Android SDK, GamificationPointsRemoved uses the same enum values as GamificationPointsGained,
267
+ // but the context (removed vs gained) determines the meaning.
268
+ // Map the action values to their "removed" equivalents
269
+ return when (action) {
270
+ Gamification.Action.POST -> "postDeleted"
271
+ Gamification.Action.COMMENT -> "commentDeleted"
272
+ Gamification.Action.REPLY -> "replyDeleted"
273
+ Gamification.Action.REACTION -> "reactionDeleted"
274
+ // For other actions, use the regular serialization (shouldn't happen for removed events)
275
+ else -> serializeGamificationAction(action)
276
+ }
277
+ }
278
+
279
+ private fun serializeScreen(event: OctopusEvent.ScreenDisplayed): WritableMap {
280
+ val screenMap = Arguments.createMap()
281
+
282
+ when (event) {
283
+ is OctopusEvent.ScreenDisplayed.PostsFeed -> {
284
+ screenMap.putString("type", "postsFeed")
285
+ screenMap.putString("feedId", event.feedId)
286
+ event.relatedTopicId?.let {
287
+ screenMap.putString("relatedTopicId", it)
288
+ }
289
+ }
290
+ is OctopusEvent.ScreenDisplayed.PostDetail -> {
291
+ screenMap.putString("type", "postDetail")
292
+ screenMap.putString("postId", event.postId)
293
+ }
294
+ is OctopusEvent.ScreenDisplayed.CommentDetail -> {
295
+ screenMap.putString("type", "commentDetail")
296
+ screenMap.putString("commentId", event.commentId)
297
+ }
298
+ is OctopusEvent.ScreenDisplayed.CreatePost -> {
299
+ screenMap.putString("type", "createPost")
300
+ }
301
+ is OctopusEvent.ScreenDisplayed.Profile -> {
302
+ screenMap.putString("type", "profile")
303
+ }
304
+ is OctopusEvent.ScreenDisplayed.OtherUserProfile -> {
305
+ screenMap.putString("type", "otherUserProfile")
306
+ screenMap.putString("profileId", event.profileId)
307
+ }
308
+ is OctopusEvent.ScreenDisplayed.EditProfile -> {
309
+ screenMap.putString("type", "editProfile")
310
+ }
311
+ is OctopusEvent.ScreenDisplayed.ReportContent -> {
312
+ screenMap.putString("type", "reportContent")
313
+ }
314
+ is OctopusEvent.ScreenDisplayed.ReportProfile -> {
315
+ screenMap.putString("type", "reportProfile")
316
+ }
317
+ is OctopusEvent.ScreenDisplayed.ValidateNickname -> {
318
+ screenMap.putString("type", "validateNickname")
319
+ }
320
+ is OctopusEvent.ScreenDisplayed.SettingsList -> {
321
+ screenMap.putString("type", "settingsList")
322
+ }
323
+ is OctopusEvent.ScreenDisplayed.SettingsAccount -> {
324
+ screenMap.putString("type", "settingsAccount")
325
+ }
326
+ is OctopusEvent.ScreenDisplayed.SettingsAbout -> {
327
+ screenMap.putString("type", "settingsAbout")
328
+ }
329
+ is OctopusEvent.ScreenDisplayed.ReportExplanation -> {
330
+ screenMap.putString("type", "reportExplanation")
331
+ }
332
+ is OctopusEvent.ScreenDisplayed.DeleteAccount -> {
333
+ screenMap.putString("type", "deleteAccount")
334
+ }
335
+ }
336
+
337
+ return screenMap
338
+ }
339
+ }
@@ -0,0 +1,326 @@
1
+ package com.octopuscommunity.octopusreactnativesdk
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
5
+ import com.facebook.react.bridge.ReactMethod
6
+ import com.facebook.react.bridge.Promise
7
+ import com.facebook.react.bridge.ReadableMap
8
+ import com.facebook.react.bridge.ReadableType
9
+ import com.octopuscommunity.sdk.OctopusSDK
10
+ import com.octopuscommunity.sdk.domain.model.TrackerEvent
11
+ import kotlinx.coroutines.CoroutineScope
12
+ import kotlinx.coroutines.Dispatchers
13
+ import kotlinx.coroutines.Job
14
+ import kotlinx.coroutines.SupervisorJob
15
+ import kotlinx.coroutines.launch
16
+
17
+ class OctopusReactModule(reactContext: ReactApplicationContext) :
18
+ ReactContextBaseJavaModule(reactContext) {
19
+
20
+ private val sdkInitializer = OctopusSDKInitializer()
21
+ private val eventEmitter = OctopusEventEmitter(reactContext)
22
+ private val uiController = OctopusUIController(reactContext)
23
+ private val ssoAuthenticator = OctopusSSOAuthenticator(reactContext, eventEmitter)
24
+ private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
25
+ private var notSeenNotificationsJob: Job? = null
26
+ private var hasAccessToCommunityJob: Job? = null
27
+ private var eventsJob: Job? = null
28
+
29
+ override fun getName(): String = NAME
30
+
31
+ @ReactMethod
32
+ fun initialize(options: ReadableMap, promise: Promise) {
33
+ val success = sdkInitializer.initialize(reactApplicationContext, options, promise)
34
+ // Start observing reactive events after SDK initialization succeeds
35
+ if (success) {
36
+ startObservingReactiveEvents()
37
+ }
38
+ }
39
+
40
+ @ReactMethod
41
+ fun openUI(options: ReadableMap?, promise: Promise) {
42
+ uiController.openUI(options, promise)
43
+ }
44
+
45
+ @ReactMethod
46
+ fun handleUrlStrategy(url: String, strategy: String) {
47
+ if (strategy == "handledByOctopus") {
48
+ uiController.openUrlInBrowser(url)
49
+ }
50
+ }
51
+
52
+ @ReactMethod
53
+ fun closeUI(promise: Promise) {
54
+ uiController.closeUI(promise)
55
+ }
56
+
57
+ @ReactMethod
58
+ fun connectUser(params: ReadableMap, promise: Promise) {
59
+ ssoAuthenticator.connectUser(params, promise)
60
+ }
61
+
62
+ @ReactMethod
63
+ fun disconnectUser(promise: Promise) {
64
+ ssoAuthenticator.disconnectUser(promise)
65
+ }
66
+
67
+ @ReactMethod
68
+ fun completeUserTokenRequest(requestId: String, token: String, promise: Promise) {
69
+ ssoAuthenticator.completeTokenRequest(requestId, token)
70
+ promise.resolve(null)
71
+ }
72
+
73
+ @ReactMethod
74
+ fun cancelUserTokenRequest(requestId: String, promise: Promise) {
75
+ ssoAuthenticator.cancelTokenRequest(requestId)
76
+ promise.resolve(null)
77
+ }
78
+
79
+ @ReactMethod
80
+ fun addListener(eventName: String) {
81
+ eventEmitter.addListener(eventName)
82
+ }
83
+
84
+ @ReactMethod
85
+ fun removeListeners(count: Int) {
86
+ eventEmitter.removeListeners(count)
87
+ }
88
+
89
+ @ReactMethod
90
+ fun updateColorScheme(colorScheme: String?, promise: Promise) {
91
+ // Update the theme config with the new color scheme
92
+ val currentThemeConfig = OctopusThemeManager.getThemeConfig()
93
+ if (currentThemeConfig != null) {
94
+ val updatedThemeConfig = currentThemeConfig.copy(colorScheme = colorScheme)
95
+ OctopusThemeManager.setThemeConfig(updatedThemeConfig)
96
+ }
97
+ promise.resolve(null)
98
+ }
99
+
100
+ @ReactMethod
101
+ fun updateTheme(themeOptions: ReadableMap, promise: Promise) {
102
+ try {
103
+ // Parse theme using the standard initializer
104
+ val themeConfig = sdkInitializer.parseThemeConfig(themeOptions)
105
+ OctopusThemeManager.setThemeConfig(themeConfig)
106
+ promise.resolve(null)
107
+ } catch (e: Exception) {
108
+ promise.reject("UPDATE_THEME_ERROR", "Failed to update theme", e)
109
+ }
110
+ }
111
+
112
+ @ReactMethod
113
+ fun updateNotSeenNotificationsCount(promise: Promise) {
114
+ coroutineScope.launch {
115
+ try {
116
+ OctopusSDK.updateNotSeenNotificationsCount()
117
+ promise.resolve(null)
118
+ } catch (e: Exception) {
119
+ promise.reject("UPDATE_ERROR", e.message ?: "Failed to update notification count", e)
120
+ }
121
+ }
122
+ }
123
+
124
+ @ReactMethod
125
+ fun trackCustomEvent(name: String, properties: ReadableMap?, promise: Promise) {
126
+ val trimmedName = name.trim()
127
+ if (trimmedName.isEmpty()) {
128
+ promise.reject("INVALID_ARGS", "name is required and must be non-empty", null)
129
+ return
130
+ }
131
+ val propsMap = readableMapToStringMap(properties)
132
+ coroutineScope.launch {
133
+ try {
134
+ OctopusSDK.track(
135
+ TrackerEvent.Custom(
136
+ name = trimmedName,
137
+ properties = propsMap.mapValues { TrackerEvent.Custom.Property(it.value) }
138
+ )
139
+ )
140
+ promise.resolve(null)
141
+ } catch (e: Exception) {
142
+ promise.reject("TRACK_ERROR", e.message ?: "Failed to track custom event", e)
143
+ }
144
+ }
145
+ }
146
+
147
+ @ReactMethod
148
+ fun overrideDefaultLocale(
149
+ languageCode: String?,
150
+ countryCode: String?,
151
+ promise: Promise
152
+ ) {
153
+ coroutineScope.launch {
154
+ try {
155
+ val locale = when {
156
+ languageCode != null && countryCode != null ->
157
+ java.util.Locale(languageCode, countryCode)
158
+ languageCode != null -> java.util.Locale(languageCode)
159
+ else -> null
160
+ }
161
+ OctopusSDK.overrideDefaultLocale(locale)
162
+ promise.resolve(null)
163
+ } catch (e: Exception) {
164
+ promise.reject(
165
+ "LOCALE_ERROR",
166
+ e.message ?: "Failed to override default locale",
167
+ e
168
+ )
169
+ }
170
+ }
171
+ }
172
+
173
+ @ReactMethod
174
+ fun overrideCommunityAccess(hasAccess: Boolean, promise: Promise) {
175
+ coroutineScope.launch {
176
+ try {
177
+ OctopusSDK.overrideCommunityAccess(hasAccess)
178
+ promise.resolve(null)
179
+ } catch (e: Exception) {
180
+ promise.reject(
181
+ "OVERRIDE_ERROR",
182
+ e.message ?: "Failed to override community access",
183
+ e
184
+ )
185
+ }
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Tracks community access for analytics only. Does not change the actual access.
191
+ * Use when the app manages its own A/B logic and only needs to report the value.
192
+ */
193
+ @ReactMethod
194
+ fun trackCommunityAccess(hasAccess: Boolean, promise: Promise) {
195
+ try {
196
+ OctopusSDK.trackAccessToCommunity(hasAccess)
197
+ promise.resolve(null)
198
+ } catch (e: Exception) {
199
+ promise.reject(
200
+ "TRACK_ACCESS_ERROR",
201
+ e.message ?: "Failed to track community access",
202
+ e
203
+ )
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Converts a ReadableMap to a Map<String, String>. Only string values are included;
209
+ * other types are skipped so that the result is valid for TrackerEvent.Custom properties.
210
+ */
211
+ private fun readableMapToStringMap(map: ReadableMap?): Map<String, String> {
212
+ if (map == null) return emptyMap()
213
+ val result = mutableMapOf<String, String>()
214
+ val iterator = map.keySetIterator()
215
+ while (iterator.hasNextKey()) {
216
+ val key = iterator.nextKey()
217
+ if (map.getType(key) == ReadableType.String) {
218
+ map.getString(key)?.let { value -> result[key] = value }
219
+ }
220
+ }
221
+ return result
222
+ }
223
+
224
+ private fun parseColor(colorString: String?): String? {
225
+ if (colorString == null) return null
226
+
227
+ return try {
228
+ // Validate that the color string is a valid hex color
229
+ android.graphics.Color.parseColor(colorString)
230
+ // Return the original string if parsing succeeds
231
+ colorString
232
+ } catch (e: IllegalArgumentException) {
233
+ // Invalid color format - return null to skip this color
234
+ null
235
+ }
236
+ }
237
+
238
+ private fun parseFontsConfig(fontsMap: ReadableMap): OctopusFontsConfig? {
239
+ // Use pre-processed configuration from TypeScript layer
240
+ val parsedConfig = fontsMap.getMap("parsedConfig")
241
+ if (parsedConfig != null) {
242
+ return parsePreProcessedFontsConfig(parsedConfig)
243
+ }
244
+
245
+ return null
246
+ }
247
+
248
+ private fun parsePreProcessedFontsConfig(parsedConfig: ReadableMap): OctopusFontsConfig? {
249
+ val textStylesMap = parsedConfig.getMap("textStyles")
250
+ val textStyles = mutableMapOf<String, OctopusTextStyleConfig>()
251
+
252
+ // Parse pre-processed font configuration from TypeScript layer
253
+ textStylesMap?.let { textStylesMap ->
254
+ val textStyleKeys = arrayOf("title1", "title2", "body1", "body2", "caption1", "caption2")
255
+
256
+ textStyleKeys.forEach { key ->
257
+ val textStyleMap = textStylesMap.getMap(key)
258
+ textStyleMap?.let { style ->
259
+ val fontType = style.getString("fontType")
260
+ val fontSize = if (style.hasKey("fontSize")) style.getDouble("fontSize") else Double.NaN
261
+
262
+ if (fontType != null || (!fontSize.isNaN() && fontSize > 0)) {
263
+ textStyles[key] = OctopusTextStyleConfig(
264
+ fontType = fontType,
265
+ fontSize = if (fontSize.isNaN() || fontSize <= 0) null else fontSize
266
+ )
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ // Only create fonts config if we have text styles
273
+ if (textStyles.isNotEmpty()) {
274
+ return OctopusFontsConfig(
275
+ textStyles = textStyles
276
+ )
277
+ }
278
+
279
+ return null
280
+ }
281
+
282
+ private fun startObservingReactiveEvents() {
283
+ startNotSeenNotificationsCollection()
284
+ startHasAccessToCommunityCollection()
285
+ startEventsCollection()
286
+ }
287
+
288
+ private fun startNotSeenNotificationsCollection() {
289
+ notSeenNotificationsJob?.cancel()
290
+ notSeenNotificationsJob = coroutineScope.launch {
291
+ OctopusSDK.notSeenNotificationsCount.collect { count ->
292
+ eventEmitter.emitNotSeenNotificationsCountChanged(count)
293
+ }
294
+ }
295
+ }
296
+
297
+ private fun startHasAccessToCommunityCollection() {
298
+ hasAccessToCommunityJob?.cancel()
299
+ hasAccessToCommunityJob = coroutineScope.launch {
300
+ OctopusSDK.hasAccessToCommunity.collect { hasAccess ->
301
+ eventEmitter.emitHasAccessToCommunityChanged(hasAccess)
302
+ }
303
+ }
304
+ }
305
+
306
+ private fun startEventsCollection() {
307
+ eventsJob?.cancel()
308
+ eventsJob = coroutineScope.launch {
309
+ OctopusSDK.events.collect { event ->
310
+ val eventData = OctopusEventSerializer.serializeEvent(event)
311
+ if (eventData != null) {
312
+ eventEmitter.emitSDKEvent(eventData)
313
+ }
314
+ }
315
+ }
316
+ }
317
+
318
+
319
+ companion object {
320
+ const val NAME = "OctopusReactNativeSdk"
321
+ }
322
+
323
+ init {
324
+ OctopusEventEmitter.instance = eventEmitter
325
+ }
326
+ }
@@ -6,12 +6,12 @@ import com.facebook.react.bridge.ReactApplicationContext
6
6
  import com.facebook.react.uimanager.ViewManager
7
7
 
8
8
 
9
- class OctopusReactNativeSdkPackage : ReactPackage {
9
+ class OctopusReactPackage : ReactPackage {
10
10
  override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11
- return listOf(OctopusReactNativeSdkModule(reactContext))
11
+ return listOf(OctopusReactModule(reactContext))
12
12
  }
13
13
 
14
14
  override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
15
- return emptyList()
15
+ return listOf(OctopusUIViewManager(reactContext))
16
16
  }
17
17
  }