@developer_tribe/react-native-comnyx 0.15.0 → 0.16.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 (185) hide show
  1. package/Comnyx.podspec +10 -2
  2. package/README.md +50 -0
  3. package/android/build.gradle +1 -0
  4. package/android/consumer-rules.pro +23 -0
  5. package/android/generated/java/com/comnyx/NativeComnyxSpec.java +46 -0
  6. package/android/generated/jni/RNComnyxSpec-generated.cpp +23 -1
  7. package/android/generated/jni/RNComnyxSpec.h +7 -0
  8. package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI-generated.cpp +21 -0
  9. package/android/generated/jni/react/renderer/components/RNComnyxSpec/RNComnyxSpecJSI.h +70 -0
  10. package/android/src/main/AndroidManifest.xml +11 -1
  11. package/android/src/main/AndroidManifestNew.xml +11 -1
  12. package/android/src/main/java/com/comnyx/ComnyxMediaPickerModule.kt +91 -51
  13. package/android/src/main/java/com/comnyx/ComnyxModule.kt +7 -0
  14. package/android/src/main/java/com/comnyx/src/messaging/firebase/FirebaseMessagingService.kt +4 -6
  15. package/android/src/main/res/xml/comnyx_file_paths.xml +12 -0
  16. package/ios/APNService.swift +9 -9
  17. package/ios/Comnyx.swift +17 -8
  18. package/ios/ComnyxMediaPicker.swift +47 -26
  19. package/ios/ComnyxMessaging.swift +2 -0
  20. package/ios/PrivacyInfo.xcprivacy +32 -0
  21. package/ios/comnyx_post_install.rb +25 -0
  22. package/ios/generated/RCTAppDependencyProvider.h +25 -0
  23. package/ios/generated/RCTAppDependencyProvider.mm +55 -0
  24. package/ios/generated/RCTModulesConformingToProtocolsProvider.h +18 -0
  25. package/ios/generated/RCTModulesConformingToProtocolsProvider.mm +33 -0
  26. package/ios/generated/RCTThirdPartyComponentsProvider.h +16 -0
  27. package/ios/generated/RCTThirdPartyComponentsProvider.mm +23 -0
  28. package/ios/generated/RNComnyxSpec/RNComnyxSpec-generated.mm +53 -0
  29. package/ios/generated/RNComnyxSpec/RNComnyxSpec.h +67 -0
  30. package/ios/generated/RNComnyxSpecJSI-generated.cpp +38 -0
  31. package/ios/generated/RNComnyxSpecJSI.h +89 -0
  32. package/ios/generated/ReactAppDependencyProvider.podspec +34 -0
  33. package/lib/commonjs/NativeComnyxMediaPicker.js +19 -0
  34. package/lib/commonjs/NativeComnyxMediaPicker.js.map +1 -1
  35. package/lib/commonjs/api/conversations.js +6 -6
  36. package/lib/commonjs/api/conversations.js.map +1 -1
  37. package/lib/commonjs/api/customers.js +3 -2
  38. package/lib/commonjs/api/customers.js.map +1 -1
  39. package/lib/commonjs/api/media.js +20 -6
  40. package/lib/commonjs/api/media.js.map +1 -1
  41. package/lib/commonjs/api/messages.js +3 -2
  42. package/lib/commonjs/api/messages.js.map +1 -1
  43. package/lib/commonjs/components/ChatList.js +93 -45
  44. package/lib/commonjs/components/ChatList.js.map +1 -1
  45. package/lib/commonjs/components/ComnyxErrorBoundary.js +92 -0
  46. package/lib/commonjs/components/ComnyxErrorBoundary.js.map +1 -0
  47. package/lib/commonjs/components/CustomerForm.js +2 -2
  48. package/lib/commonjs/components/CustomerForm.js.map +1 -1
  49. package/lib/commonjs/components/MediaMessageItem.js +4 -3
  50. package/lib/commonjs/components/MediaMessageItem.js.map +1 -1
  51. package/lib/commonjs/components/MessageInput.js +63 -13
  52. package/lib/commonjs/components/MessageInput.js.map +1 -1
  53. package/lib/commonjs/components/MessageItem.js +1 -1
  54. package/lib/commonjs/components/MessageItem.js.map +1 -1
  55. package/lib/commonjs/hooks/usePolling.js +25 -21
  56. package/lib/commonjs/hooks/usePolling.js.map +1 -1
  57. package/lib/commonjs/hooks/useThemeColors.js +12 -1
  58. package/lib/commonjs/hooks/useThemeColors.js.map +1 -1
  59. package/lib/commonjs/index.js.map +1 -1
  60. package/lib/commonjs/notifications/initializeNotifications.js +19 -16
  61. package/lib/commonjs/notifications/initializeNotifications.js.map +1 -1
  62. package/lib/commonjs/register/Accumulator.js +19 -6
  63. package/lib/commonjs/register/Accumulator.js.map +1 -1
  64. package/lib/commonjs/register/collectData.js +1 -1
  65. package/lib/commonjs/register/collectData.js.map +1 -1
  66. package/lib/commonjs/store/store.js +6 -0
  67. package/lib/commonjs/store/store.js.map +1 -1
  68. package/lib/commonjs/support/ComnyxSupport.js +60 -15
  69. package/lib/commonjs/support/ComnyxSupport.js.map +1 -1
  70. package/lib/commonjs/support/SupportConfigContext.js +24 -0
  71. package/lib/commonjs/support/SupportConfigContext.js.map +1 -0
  72. package/lib/commonjs/types/Theme.js +30 -2
  73. package/lib/commonjs/types/Theme.js.map +1 -1
  74. package/lib/commonjs/version.js +1 -1
  75. package/lib/module/NativeComnyxMediaPicker.js +18 -0
  76. package/lib/module/NativeComnyxMediaPicker.js.map +1 -1
  77. package/lib/module/api/conversations.js +6 -6
  78. package/lib/module/api/conversations.js.map +1 -1
  79. package/lib/module/api/customers.js +3 -2
  80. package/lib/module/api/customers.js.map +1 -1
  81. package/lib/module/api/media.js +21 -6
  82. package/lib/module/api/media.js.map +1 -1
  83. package/lib/module/api/messages.js +3 -2
  84. package/lib/module/api/messages.js.map +1 -1
  85. package/lib/module/components/ChatList.js +94 -46
  86. package/lib/module/components/ChatList.js.map +1 -1
  87. package/lib/module/components/ComnyxErrorBoundary.js +87 -0
  88. package/lib/module/components/ComnyxErrorBoundary.js.map +1 -0
  89. package/lib/module/components/CustomerForm.js +2 -2
  90. package/lib/module/components/CustomerForm.js.map +1 -1
  91. package/lib/module/components/MediaMessageItem.js +4 -3
  92. package/lib/module/components/MediaMessageItem.js.map +1 -1
  93. package/lib/module/components/MessageInput.js +64 -14
  94. package/lib/module/components/MessageInput.js.map +1 -1
  95. package/lib/module/components/MessageItem.js +1 -1
  96. package/lib/module/components/MessageItem.js.map +1 -1
  97. package/lib/module/hooks/usePolling.js +25 -21
  98. package/lib/module/hooks/usePolling.js.map +1 -1
  99. package/lib/module/hooks/useThemeColors.js +13 -2
  100. package/lib/module/hooks/useThemeColors.js.map +1 -1
  101. package/lib/module/index.js +0 -1
  102. package/lib/module/index.js.map +1 -1
  103. package/lib/module/notifications/initializeNotifications.js +19 -16
  104. package/lib/module/notifications/initializeNotifications.js.map +1 -1
  105. package/lib/module/register/Accumulator.js +19 -6
  106. package/lib/module/register/Accumulator.js.map +1 -1
  107. package/lib/module/register/collectData.js +1 -1
  108. package/lib/module/register/collectData.js.map +1 -1
  109. package/lib/module/store/store.js +6 -0
  110. package/lib/module/store/store.js.map +1 -1
  111. package/lib/module/support/ComnyxSupport.js +61 -16
  112. package/lib/module/support/ComnyxSupport.js.map +1 -1
  113. package/lib/module/support/SupportConfigContext.js +19 -0
  114. package/lib/module/support/SupportConfigContext.js.map +1 -0
  115. package/lib/module/types/Theme.js +30 -2
  116. package/lib/module/types/Theme.js.map +1 -1
  117. package/lib/module/version.js +1 -1
  118. package/lib/typescript/src/NativeComnyxMediaPicker.d.ts +9 -0
  119. package/lib/typescript/src/NativeComnyxMediaPicker.d.ts.map +1 -1
  120. package/lib/typescript/src/api/conversations.d.ts +2 -2
  121. package/lib/typescript/src/api/conversations.d.ts.map +1 -1
  122. package/lib/typescript/src/api/customers.d.ts +1 -1
  123. package/lib/typescript/src/api/customers.d.ts.map +1 -1
  124. package/lib/typescript/src/api/media.d.ts +3 -3
  125. package/lib/typescript/src/api/media.d.ts.map +1 -1
  126. package/lib/typescript/src/api/messages.d.ts +1 -1
  127. package/lib/typescript/src/api/messages.d.ts.map +1 -1
  128. package/lib/typescript/src/components/ChatList.d.ts.map +1 -1
  129. package/lib/typescript/src/components/ComnyxErrorBoundary.d.ts +18 -0
  130. package/lib/typescript/src/components/ComnyxErrorBoundary.d.ts.map +1 -0
  131. package/lib/typescript/src/components/MediaMessageItem.d.ts.map +1 -1
  132. package/lib/typescript/src/components/MessageInput.d.ts.map +1 -1
  133. package/lib/typescript/src/components/MessageItem.d.ts.map +1 -1
  134. package/lib/typescript/src/hooks/usePolling.d.ts.map +1 -1
  135. package/lib/typescript/src/hooks/useThemeColors.d.ts.map +1 -1
  136. package/lib/typescript/src/index.d.ts +2 -0
  137. package/lib/typescript/src/index.d.ts.map +1 -1
  138. package/lib/typescript/src/notifications/initializeNotifications.d.ts.map +1 -1
  139. package/lib/typescript/src/register/Accumulator.d.ts.map +1 -1
  140. package/lib/typescript/src/register/collectData.d.ts +4 -1
  141. package/lib/typescript/src/register/collectData.d.ts.map +1 -1
  142. package/lib/typescript/src/store/store.d.ts +6 -2
  143. package/lib/typescript/src/store/store.d.ts.map +1 -1
  144. package/lib/typescript/src/support/ComnyxSupport.d.ts +56 -2
  145. package/lib/typescript/src/support/ComnyxSupport.d.ts.map +1 -1
  146. package/lib/typescript/src/support/SupportConfigContext.d.ts +58 -0
  147. package/lib/typescript/src/support/SupportConfigContext.d.ts.map +1 -0
  148. package/lib/typescript/src/support/index.d.ts +1 -0
  149. package/lib/typescript/src/support/index.d.ts.map +1 -1
  150. package/lib/typescript/src/types/Conversation.d.ts +2 -2
  151. package/lib/typescript/src/types/Conversation.d.ts.map +1 -1
  152. package/lib/typescript/src/types/Customer.d.ts +1 -1
  153. package/lib/typescript/src/types/Customer.d.ts.map +1 -1
  154. package/lib/typescript/src/types/MessageResponse.d.ts +7 -4
  155. package/lib/typescript/src/types/MessageResponse.d.ts.map +1 -1
  156. package/lib/typescript/src/types/Theme.d.ts +26 -0
  157. package/lib/typescript/src/types/Theme.d.ts.map +1 -1
  158. package/lib/typescript/src/version.d.ts +1 -1
  159. package/package.json +12 -25
  160. package/src/NativeComnyxMediaPicker.ts +18 -0
  161. package/src/api/conversations.ts +6 -4
  162. package/src/api/customers.ts +3 -1
  163. package/src/api/media.ts +32 -10
  164. package/src/api/messages.ts +3 -1
  165. package/src/components/ChatList.tsx +115 -55
  166. package/src/components/ComnyxErrorBoundary.tsx +91 -0
  167. package/src/components/CustomerForm.tsx +2 -2
  168. package/src/components/MediaMessageItem.tsx +10 -3
  169. package/src/components/MessageInput.tsx +89 -16
  170. package/src/components/MessageItem.tsx +12 -13
  171. package/src/hooks/usePolling.ts +21 -11
  172. package/src/hooks/useThemeColors.ts +11 -2
  173. package/src/index.ts +12 -0
  174. package/src/notifications/initializeNotifications.ts +22 -20
  175. package/src/register/Accumulator.ts +26 -9
  176. package/src/register/collectData.ts +10 -2
  177. package/src/store/store.ts +11 -3
  178. package/src/support/ComnyxSupport.tsx +128 -22
  179. package/src/support/SupportConfigContext.tsx +79 -0
  180. package/src/support/index.ts +7 -0
  181. package/src/types/Conversation.ts +2 -2
  182. package/src/types/Customer.ts +1 -2
  183. package/src/types/MessageResponse.ts +4 -4
  184. package/src/types/Theme.ts +38 -0
  185. package/src/version.ts +1 -1
package/Comnyx.podspec CHANGED
@@ -11,13 +11,21 @@ Pod::Spec.new do |s|
11
11
  s.authors = package["author"]
12
12
  s.swift_version = "5.9"
13
13
 
14
- s.platforms = { :ios => min_ios_version_supported }
14
+ # Explicit minimum target. The podspec used to rely on
15
+ # `min_ios_version_supported`, which is only defined when React Native's
16
+ # cocoapods helpers are loaded. If a host builds with a different setup that
17
+ # helper is undefined and the podspec fails to parse.
18
+ s.platforms = { :ios => '14.0' }
15
19
  s.source = { :git => "https://www.npmjs.com.git", :tag => "#{s.version}" }
16
20
 
17
21
  s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
18
-
22
+
19
23
  # Exclude generated files to avoid duplicate definitions
20
24
  s.exclude_files = "ios/generated/**/*"
25
+
26
+ s.resource_bundles = {
27
+ 'Comnyx_Privacy' => ['ios/PrivacyInfo.xcprivacy']
28
+ }
21
29
 
22
30
  # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
23
31
  # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
package/README.md CHANGED
@@ -31,6 +31,37 @@ npx react-native run-android
31
31
 
32
32
  **Note:** If you don't install `react-native-mmkv`, the SDK will use in-memory storage as a fallback. This means your data will not persist between app restarts.
33
33
 
34
+ ### iOS Podfile (required for React Native 0.85+)
35
+
36
+ React Native 0.85 ships React-Core as a prebuilt XCFramework whose umbrella header is not compatible with Xcode 26's strict Clang explicit modules. Without the patch below, `pod install` succeeds but `xcodebuild` fails while compiling the React-Core umbrella.
37
+
38
+ Comnyx ships a helper that applies the required build setting to every pod target. Wire it into your `ios/Podfile`:
39
+
40
+ ```ruby
41
+ require_relative '../node_modules/@developer_tribe/react-native-comnyx/ios/comnyx_post_install'
42
+
43
+ # ...
44
+
45
+ target 'YourApp' do
46
+ config = use_native_modules!
47
+ use_react_native!(:path => config[:reactNativePath], :app_path => "#{Pod::Config.instance.installation_root}/..")
48
+
49
+ post_install do |installer|
50
+ comnyx_post_install(installer)
51
+
52
+ react_native_post_install(installer, config[:reactNativePath], :mac_catalyst_enabled => false)
53
+ end
54
+ end
55
+ ```
56
+
57
+ Then reinstall pods:
58
+
59
+ ```bash
60
+ cd ios && pod install && cd ..
61
+ ```
62
+
63
+ The helper is a no-op on React Native < 0.85, so it is safe to add before upgrading.
64
+
34
65
  ## Basic Setup (Required)
35
66
 
36
67
  ### 1. Initialization and Login
@@ -77,6 +108,25 @@ const SupportScreen = () => {
77
108
  };
78
109
  ```
79
110
 
111
+ ### 3. Media Attachments in Support (iOS Info.plist)
112
+
113
+ The support UI lets users attach images and videos. On Android the system
114
+ Photo Picker (Android 13+) or the document picker (older) handles this
115
+ permission-free. On iOS the SDK uses `PHPicker` on iOS 14+ (no permission
116
+ needed) and falls back to `UIImagePickerController` on iOS 13.
117
+
118
+ If you ship iOS 13 support, add the following to your host app's
119
+ `Info.plist` so the fallback works without crashing:
120
+
121
+ ```xml
122
+ <key>NSPhotoLibraryUsageDescription</key>
123
+ <string>Needed to attach photos and videos to support conversations.</string>
124
+ ```
125
+
126
+ If your deployment target is iOS 14+, this key is not strictly required but
127
+ App Store reviewers often expect it whenever `PHPickerViewController` is
128
+ linked.
129
+
80
130
  ## Push Notifications Setup (Optional)
81
131
 
82
132
  ### 1. Account Prerequisites
@@ -48,6 +48,7 @@ android {
48
48
  defaultConfig {
49
49
  minSdkVersion getExtOrIntegerDefault("minSdkVersion")
50
50
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
51
+ consumerProguardFiles "consumer-rules.pro"
51
52
  }
52
53
 
53
54
  buildFeatures {
@@ -0,0 +1,23 @@
1
+ # ProGuard / R8 rules bundled with the Comnyx RN library. These are applied
2
+ # automatically by host apps when they consume the library's AAR.
3
+
4
+ # React Native bridge uses reflection to resolve @ReactMethod and the
5
+ # generated TurboModule spec. Keep the module, package, and codegen spec.
6
+ -keep class com.comnyx.ComnyxModule { *; }
7
+ -keep class com.comnyx.ComnyxMediaPickerModule { *; }
8
+ -keep class com.comnyx.ComnyxPackage { *; }
9
+ -keep class com.comnyx.NativeComnyxSpec { *; }
10
+
11
+ # Firebase messaging reflectively instantiates the overridden service and
12
+ # calls its lifecycle methods.
13
+ -keep class com.comnyx.messaging.ComnyxFirebaseMessagingService { *; }
14
+
15
+ # Keep the video player activity referenced from manifest by name.
16
+ -keep class com.comnyx.VideoPlayerActivity { *; }
17
+
18
+ # Methods reached via @ReactMethod annotations. The annotation itself is a
19
+ # runtime one; keep everything it marks.
20
+ -keep @com.facebook.react.bridge.ReactMethod class * { *; }
21
+ -keepclassmembers class * {
22
+ @com.facebook.react.bridge.ReactMethod <methods>;
23
+ }
@@ -0,0 +1,46 @@
1
+
2
+ /**
3
+ * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
4
+ *
5
+ * Do not edit this file as changes may cause incorrect behavior and will be lost
6
+ * once the code is regenerated.
7
+ *
8
+ * @generated by codegen project: GenerateModuleJavaSpec.js
9
+ *
10
+ * @nolint
11
+ */
12
+
13
+ package com.comnyx;
14
+
15
+ import com.facebook.proguard.annotations.DoNotStrip;
16
+ import com.facebook.react.bridge.Promise;
17
+ import com.facebook.react.bridge.ReactApplicationContext;
18
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
19
+ import com.facebook.react.bridge.ReactMethod;
20
+ import com.facebook.react.turbomodule.core.interfaces.TurboModule;
21
+ import javax.annotation.Nonnull;
22
+
23
+ public abstract class NativeComnyxSpec extends ReactContextBaseJavaModule implements TurboModule {
24
+ public static final String NAME = "Comnyx";
25
+
26
+ public NativeComnyxSpec(ReactApplicationContext reactContext) {
27
+ super(reactContext);
28
+ }
29
+
30
+ @Override
31
+ public @Nonnull String getName() {
32
+ return NAME;
33
+ }
34
+
35
+ @ReactMethod
36
+ @DoNotStrip
37
+ public abstract void initialize(Promise promise);
38
+
39
+ @ReactMethod
40
+ @DoNotStrip
41
+ public abstract void checkOptIn(Promise promise);
42
+
43
+ @ReactMethod
44
+ @DoNotStrip
45
+ public abstract void optIn(Promise promise);
46
+ }
@@ -12,10 +12,32 @@
12
12
 
13
13
  namespace facebook::react {
14
14
 
15
+ static facebook::jsi::Value __hostFunction_NativeComnyxSpecJSI_initialize(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
16
+ static jmethodID cachedMethodId = nullptr;
17
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "initialize", "(Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
18
+ }
15
19
 
20
+ static facebook::jsi::Value __hostFunction_NativeComnyxSpecJSI_checkOptIn(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
21
+ static jmethodID cachedMethodId = nullptr;
22
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "checkOptIn", "(Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
23
+ }
16
24
 
17
- std::shared_ptr<TurboModule> RNComnyxSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
25
+ static facebook::jsi::Value __hostFunction_NativeComnyxSpecJSI_optIn(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
26
+ static jmethodID cachedMethodId = nullptr;
27
+ return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, PromiseKind, "optIn", "(Lcom/facebook/react/bridge/Promise;)V", args, count, cachedMethodId);
28
+ }
18
29
 
30
+ NativeComnyxSpecJSI::NativeComnyxSpecJSI(const JavaTurboModule::InitParams &params)
31
+ : JavaTurboModule(params) {
32
+ methodMap_["initialize"] = MethodMetadata {0, __hostFunction_NativeComnyxSpecJSI_initialize};
33
+ methodMap_["checkOptIn"] = MethodMetadata {0, __hostFunction_NativeComnyxSpecJSI_checkOptIn};
34
+ methodMap_["optIn"] = MethodMetadata {0, __hostFunction_NativeComnyxSpecJSI_optIn};
35
+ }
36
+
37
+ std::shared_ptr<TurboModule> RNComnyxSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
38
+ if (moduleName == "Comnyx") {
39
+ return std::make_shared<NativeComnyxSpecJSI>(params);
40
+ }
19
41
  return nullptr;
20
42
  }
21
43
 
@@ -16,6 +16,13 @@
16
16
 
17
17
  namespace facebook::react {
18
18
 
19
+ /**
20
+ * JNI C++ class for module 'NativeComnyx'
21
+ */
22
+ class JSI_EXPORT NativeComnyxSpecJSI : public JavaTurboModule {
23
+ public:
24
+ NativeComnyxSpecJSI(const JavaTurboModule::InitParams &params);
25
+ };
19
26
 
20
27
 
21
28
  JSI_EXPORT
@@ -11,7 +11,28 @@
11
11
 
12
12
  namespace facebook::react {
13
13
 
14
+ static jsi::Value __hostFunction_NativeComnyxCxxSpecJSI_initialize(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
15
+ return static_cast<NativeComnyxCxxSpecJSI *>(&turboModule)->initialize(
16
+ rt
17
+ );
18
+ }
19
+ static jsi::Value __hostFunction_NativeComnyxCxxSpecJSI_checkOptIn(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
20
+ return static_cast<NativeComnyxCxxSpecJSI *>(&turboModule)->checkOptIn(
21
+ rt
22
+ );
23
+ }
24
+ static jsi::Value __hostFunction_NativeComnyxCxxSpecJSI_optIn(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
25
+ return static_cast<NativeComnyxCxxSpecJSI *>(&turboModule)->optIn(
26
+ rt
27
+ );
28
+ }
14
29
 
30
+ NativeComnyxCxxSpecJSI::NativeComnyxCxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker)
31
+ : TurboModule("Comnyx", jsInvoker) {
32
+ methodMap_["initialize"] = MethodMetadata {0, __hostFunction_NativeComnyxCxxSpecJSI_initialize};
33
+ methodMap_["checkOptIn"] = MethodMetadata {0, __hostFunction_NativeComnyxCxxSpecJSI_checkOptIn};
34
+ methodMap_["optIn"] = MethodMetadata {0, __hostFunction_NativeComnyxCxxSpecJSI_optIn};
35
+ }
15
36
 
16
37
 
17
38
  } // namespace facebook::react
@@ -15,5 +15,75 @@
15
15
  namespace facebook::react {
16
16
 
17
17
 
18
+ class JSI_EXPORT NativeComnyxCxxSpecJSI : public TurboModule {
19
+ protected:
20
+ NativeComnyxCxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);
21
+
22
+ public:
23
+ virtual jsi::Value initialize(jsi::Runtime &rt) = 0;
24
+ virtual jsi::Value checkOptIn(jsi::Runtime &rt) = 0;
25
+ virtual jsi::Value optIn(jsi::Runtime &rt) = 0;
26
+
27
+ };
28
+
29
+ template <typename T>
30
+ class JSI_EXPORT NativeComnyxCxxSpec : public TurboModule {
31
+ public:
32
+ jsi::Value create(jsi::Runtime &rt, const jsi::PropNameID &propName) override {
33
+ return delegate_.create(rt, propName);
34
+ }
35
+
36
+ std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& runtime) override {
37
+ return delegate_.getPropertyNames(runtime);
38
+ }
39
+
40
+ static constexpr std::string_view kModuleName = "Comnyx";
41
+
42
+ protected:
43
+ NativeComnyxCxxSpec(std::shared_ptr<CallInvoker> jsInvoker)
44
+ : TurboModule(std::string{NativeComnyxCxxSpec::kModuleName}, jsInvoker),
45
+ delegate_(reinterpret_cast<T*>(this), jsInvoker) {}
46
+
47
+
48
+ private:
49
+ class Delegate : public NativeComnyxCxxSpecJSI {
50
+ public:
51
+ Delegate(T *instance, std::shared_ptr<CallInvoker> jsInvoker) :
52
+ NativeComnyxCxxSpecJSI(std::move(jsInvoker)), instance_(instance) {
53
+
54
+ }
55
+
56
+ jsi::Value initialize(jsi::Runtime &rt) override {
57
+ static_assert(
58
+ bridging::getParameterCount(&T::initialize) == 1,
59
+ "Expected initialize(...) to have 1 parameters");
60
+
61
+ return bridging::callFromJs<jsi::Value>(
62
+ rt, &T::initialize, jsInvoker_, instance_);
63
+ }
64
+ jsi::Value checkOptIn(jsi::Runtime &rt) override {
65
+ static_assert(
66
+ bridging::getParameterCount(&T::checkOptIn) == 1,
67
+ "Expected checkOptIn(...) to have 1 parameters");
68
+
69
+ return bridging::callFromJs<jsi::Value>(
70
+ rt, &T::checkOptIn, jsInvoker_, instance_);
71
+ }
72
+ jsi::Value optIn(jsi::Runtime &rt) override {
73
+ static_assert(
74
+ bridging::getParameterCount(&T::optIn) == 1,
75
+ "Expected optIn(...) to have 1 parameters");
76
+
77
+ return bridging::callFromJs<jsi::Value>(
78
+ rt, &T::optIn, jsInvoker_, instance_);
79
+ }
80
+
81
+ private:
82
+ friend class NativeComnyxCxxSpec;
83
+ T *instance_;
84
+ };
85
+
86
+ Delegate delegate_;
87
+ };
18
88
 
19
89
  } // namespace facebook::react
@@ -5,7 +5,7 @@
5
5
  <service
6
6
  android:name="com.comnyx.messaging.ComnyxFirebaseMessagingService"
7
7
  android:priority="-1500"
8
- android:exported="true">
8
+ android:exported="false">
9
9
  <intent-filter>
10
10
  <action android:name="com.google.firebase.MESSAGING_EVENT" />
11
11
  </intent-filter>
@@ -14,5 +14,15 @@
14
14
  android:name="com.comnyx.VideoPlayerActivity"
15
15
  android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
16
16
  android:exported="false" />
17
+
18
+ <provider
19
+ android:name="androidx.core.content.FileProvider"
20
+ android:authorities="${applicationId}.comnyx.fileprovider"
21
+ android:exported="false"
22
+ android:grantUriPermissions="true">
23
+ <meta-data
24
+ android:name="android.support.FILE_PROVIDER_PATHS"
25
+ android:resource="@xml/comnyx_file_paths" />
26
+ </provider>
17
27
  </application>
18
28
  </manifest>
@@ -3,7 +3,7 @@
3
3
  <service
4
4
  android:name="com.comnyx.messaging.ComnyxFirebaseMessagingService"
5
5
  android:priority="-1500"
6
- android:exported="true">
6
+ android:exported="false">
7
7
  <intent-filter>
8
8
  <action android:name="com.google.firebase.MESSAGING_EVENT" />
9
9
  </intent-filter>
@@ -12,5 +12,15 @@
12
12
  android:name="com.comnyx.VideoPlayerActivity"
13
13
  android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
14
14
  android:exported="false" />
15
+
16
+ <provider
17
+ android:name="androidx.core.content.FileProvider"
18
+ android:authorities="${applicationId}.comnyx.fileprovider"
19
+ android:exported="false"
20
+ android:grantUriPermissions="true">
21
+ <meta-data
22
+ android:name="android.support.FILE_PROVIDER_PATHS"
23
+ android:resource="@xml/comnyx_file_paths" />
24
+ </provider>
15
25
  </application>
16
26
  </manifest>
@@ -5,12 +5,13 @@ import android.content.Intent
5
5
  import android.graphics.Bitmap
6
6
  import android.graphics.BitmapFactory
7
7
  import android.net.Uri
8
+ import android.os.Build
9
+ import android.provider.MediaStore
8
10
  import android.provider.OpenableColumns
9
- import android.webkit.MimeTypeMap
11
+ import androidx.core.content.FileProvider
10
12
  import com.facebook.react.bridge.*
11
13
  import java.io.File
12
14
  import java.io.FileOutputStream
13
- import java.io.InputStream
14
15
  import android.media.MediaMetadataRetriever
15
16
 
16
17
  class ComnyxMediaPickerModule(reactContext: ReactApplicationContext) :
@@ -25,6 +26,8 @@ class ComnyxMediaPickerModule(reactContext: ReactApplicationContext) :
25
26
  private const val COMPRESS_QUALITY = 80
26
27
  }
27
28
 
29
+ private enum class PickerFilter { IMAGE, VIDEO, ANY }
30
+
28
31
  private var promise: Promise? = null
29
32
 
30
33
  private fun rejectPendingPromise() {
@@ -38,65 +41,59 @@ class ComnyxMediaPickerModule(reactContext: ReactApplicationContext) :
38
41
 
39
42
  override fun getName(): String = NAME
40
43
 
41
-
42
-
43
- // MARK: - Pick Media (both images and videos)
44
-
45
- @ReactMethod
46
- fun pickMedia(promise: Promise) {
47
- rejectPendingPromise()
48
- this.promise = promise
49
- val activity = reactApplicationContext.currentActivity
50
- if (activity == null) {
51
- promise.reject("NO_ACTIVITY", "Activity is not available")
52
- return
53
- }
54
- val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
55
- type = "*/*"
56
- putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*"))
57
- putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
58
- addCategory(Intent.CATEGORY_OPENABLE)
44
+ /**
45
+ * On Android 13+ use the system Photo Picker (MediaStore.ACTION_PICK_IMAGES)
46
+ * which needs no runtime permission and is the recommended Google flow.
47
+ * Fall back to ACTION_GET_CONTENT on older releases.
48
+ */
49
+ private fun buildPickerIntent(filter: PickerFilter): Intent {
50
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
51
+ Intent(MediaStore.ACTION_PICK_IMAGES).apply {
52
+ when (filter) {
53
+ PickerFilter.IMAGE -> type = "image/*"
54
+ PickerFilter.VIDEO -> type = "video/*"
55
+ PickerFilter.ANY -> {
56
+ // No type set: picker shows both images and videos.
57
+ }
58
+ }
59
+ putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit())
60
+ }
61
+ } else {
62
+ Intent(Intent.ACTION_GET_CONTENT).apply {
63
+ when (filter) {
64
+ PickerFilter.IMAGE -> type = "image/*"
65
+ PickerFilter.VIDEO -> type = "video/*"
66
+ PickerFilter.ANY -> {
67
+ type = "*/*"
68
+ putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*"))
69
+ }
70
+ }
71
+ putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
72
+ addCategory(Intent.CATEGORY_OPENABLE)
73
+ }
59
74
  }
60
- activity.startActivityForResult(intent, REQUEST_MEDIA)
61
75
  }
62
76
 
63
- // MARK: - Pick Image Only
64
-
65
- @ReactMethod
66
- fun pickImage(promise: Promise) {
77
+ private fun launchPicker(filter: PickerFilter, requestCode: Int, promise: Promise) {
67
78
  rejectPendingPromise()
68
79
  this.promise = promise
69
80
  val activity = reactApplicationContext.currentActivity
70
81
  if (activity == null) {
71
82
  promise.reject("NO_ACTIVITY", "Activity is not available")
83
+ this.promise = null
72
84
  return
73
85
  }
74
- val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
75
- type = "image/*"
76
- putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
77
- addCategory(Intent.CATEGORY_OPENABLE)
78
- }
79
- activity.startActivityForResult(intent, REQUEST_IMAGE)
86
+ activity.startActivityForResult(buildPickerIntent(filter), requestCode)
80
87
  }
81
88
 
82
- // MARK: - Pick Video Only
89
+ @ReactMethod
90
+ fun pickMedia(promise: Promise) = launchPicker(PickerFilter.ANY, REQUEST_MEDIA, promise)
83
91
 
84
92
  @ReactMethod
85
- fun pickVideo(promise: Promise) {
86
- rejectPendingPromise()
87
- this.promise = promise
88
- val activity = reactApplicationContext.currentActivity
89
- if (activity == null) {
90
- promise.reject("NO_ACTIVITY", "Activity is not available")
91
- return
92
- }
93
- val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
94
- type = "video/*"
95
- putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
96
- addCategory(Intent.CATEGORY_OPENABLE)
97
- }
98
- activity.startActivityForResult(intent, REQUEST_VIDEO)
99
- }
93
+ fun pickImage(promise: Promise) = launchPicker(PickerFilter.IMAGE, REQUEST_IMAGE, promise)
94
+
95
+ @ReactMethod
96
+ fun pickVideo(promise: Promise) = launchPicker(PickerFilter.VIDEO, REQUEST_VIDEO, promise)
100
97
 
101
98
  // MARK: - Generate Thumbnail from Video URL
102
99
 
@@ -308,14 +305,37 @@ class ComnyxMediaPickerModule(reactContext: ReactApplicationContext) :
308
305
  }
309
306
  }
310
307
 
308
+ /**
309
+ * Returns a content:// URI for a cached media file, backed by the
310
+ * Comnyx FileProvider declared in AndroidManifest.xml. Use this whenever
311
+ * a URI is about to cross the process boundary (e.g. ACTION_VIEW,
312
+ * ACTION_SEND) — file:// URIs throw FileUriExposedException on
313
+ * Android 7+.
314
+ */
315
+ @ReactMethod
316
+ fun getShareableUri(fileUri: String, promise: Promise) {
317
+ try {
318
+ val path = fileUri.removePrefix("file://")
319
+ val file = File(path)
320
+ if (!file.exists()) {
321
+ promise.reject("FILE_NOT_FOUND", "File does not exist: $path")
322
+ return
323
+ }
324
+ val authority = "${reactApplicationContext.packageName}.comnyx.fileprovider"
325
+ val uri = FileProvider.getUriForFile(reactApplicationContext, authority, file)
326
+ promise.resolve(uri.toString())
327
+ } catch (e: Exception) {
328
+ promise.reject("SHAREABLE_URI_ERROR", e.message, e)
329
+ }
330
+ }
331
+
311
332
  @ReactMethod
312
333
  fun deleteTempFile(uri: String, promise: Promise) {
313
334
  Thread {
314
335
  try {
315
- val path = uri.removePrefix("file://")
316
- val file = File(path)
317
- if (file.exists()) {
318
- file.delete()
336
+ val resolved = resolveCachePath(uri)
337
+ if (resolved != null && resolved.exists()) {
338
+ resolved.delete()
319
339
  }
320
340
  promise.resolve(null)
321
341
  } catch (e: Exception) {
@@ -324,6 +344,26 @@ class ComnyxMediaPickerModule(reactContext: ReactApplicationContext) :
324
344
  }.start()
325
345
  }
326
346
 
347
+ /**
348
+ * Resolve either a file:// URI or a content:// URI served by this
349
+ * library's FileProvider back to a File inside the app's cache dir.
350
+ * Returns null for any URI that does not point into our cache.
351
+ */
352
+ private fun resolveCachePath(uri: String): File? {
353
+ if (uri.startsWith("file://")) {
354
+ return File(uri.removePrefix("file://"))
355
+ }
356
+ if (uri.startsWith("content://")) {
357
+ val parsed = Uri.parse(uri)
358
+ val expectedAuthority =
359
+ "${reactApplicationContext.packageName}.comnyx.fileprovider"
360
+ if (parsed.authority != expectedAuthority) return null
361
+ val name = parsed.lastPathSegment ?: return null
362
+ return File(reactApplicationContext.cacheDir, name)
363
+ }
364
+ return null
365
+ }
366
+
327
367
  @ReactMethod
328
368
  fun cleanupTempFiles(promise: Promise) {
329
369
  Thread {
@@ -196,6 +196,13 @@ class ComnyxModule(reactContext: ReactApplicationContext) :
196
196
  return false
197
197
  }
198
198
 
199
+ internal fun emitTokenInit(token: String) {
200
+ Logger.v("emitTokenInit forwarded", NAME)
201
+ val params = Arguments.createMap()
202
+ params.putString("token", token)
203
+ sendEvent(reactApplicationContext, "TOKEN_INIT", params)
204
+ }
205
+
199
206
  fun onNotificationShown(params: WritableMap) {
200
207
  Logger.v("onNotificationShown event forwarded", NAME)
201
208
  sendEvent(reactApplicationContext, "NOTIFICATION_RECEIVED", params)
@@ -36,12 +36,10 @@ open class ComnyxFirebaseMessagingService : FirebaseMessagingService() {
36
36
 
37
37
  override fun onNewToken(token: String) {
38
38
  Logger.v("onNewToken received", "Comnyx")
39
- sendRegistrationToServer(token)
40
- }
41
-
42
- private fun sendRegistrationToServer(token: String) {
43
- //TODO: send token to server
44
- Logger.i("sendRegistrationTokenToServer called", "Comnyx")
39
+ // Forward refreshed token to JS via TOKEN_INIT; JS layer forwards to backend.
40
+ // If the module isn't attached yet (e.g. app not foregrounded), JS will fetch
41
+ // the current token via initialize() when it wakes up.
42
+ ComnyxModule.__self?.emitTokenInit(token)
45
43
  }
46
44
 
47
45
 
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!--
3
+ Paths exposed by the Comnyx FileProvider. The picker writes image/video
4
+ temp files to Context.cacheDir; expose that directory so host apps can
5
+ safely obtain content:// URIs (e.g. for ACTION_VIEW / ACTION_SEND on
6
+ Android 7+ where file:// URIs throw FileUriExposedException).
7
+ -->
8
+ <paths>
9
+ <cache-path
10
+ name="comnyx_cache"
11
+ path="." />
12
+ </paths>
@@ -26,25 +26,25 @@ class APNService {
26
26
 
27
27
  func setAPNTokenListener(completion: @escaping (String, String?) -> Void) {
28
28
  self.tokenCompletion = completion
29
- if(self.token != nil){
30
- completion("cache", self.token!)
29
+ if let cachedToken = self.token {
30
+ completion("cache", cachedToken)
31
31
  }
32
-
33
- UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
34
- guard granted else {
32
+
33
+ UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
34
+ guard granted, let self = self else {
35
35
  return
36
36
  }
37
-
38
- DispatchQueue.main.async {
37
+
38
+ DispatchQueue.main.async { [weak self] in
39
+ guard let self = self else { return }
39
40
  UIApplication.shared.registerForRemoteNotifications()
40
- // Register for token notifications
41
41
  NotificationCenter.default.addObserver(
42
42
  self,
43
43
  selector: #selector(self.didRegisterForRemoteNotifications),
44
44
  name: NSNotification.Name("RCTRemoteNotificationRegistered"),
45
45
  object: nil
46
46
  )
47
-
47
+
48
48
  NotificationCenter.default.addObserver(
49
49
  self,
50
50
  selector: #selector(self.didFailToRegisterForRemoteNotifications),