react-native-instantpay-code-push 1.1.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 (87) hide show
  1. package/InstantpayCodePush.podspec +20 -0
  2. package/LICENSE +20 -0
  3. package/README.md +158 -0
  4. package/android/build.gradle +91 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/com/instantpaycodepush/BundleFileStorageService.kt +835 -0
  8. package/android/src/main/java/com/instantpaycodepush/BundleMetadata.kt +249 -0
  9. package/android/src/main/java/com/instantpaycodepush/CommonHelper.kt +39 -0
  10. package/android/src/main/java/com/instantpaycodepush/DecompressService.kt +85 -0
  11. package/android/src/main/java/com/instantpaycodepush/DecompressionStrategy.kt +24 -0
  12. package/android/src/main/java/com/instantpaycodepush/FileManagerService.kt +105 -0
  13. package/android/src/main/java/com/instantpaycodepush/HashUtils.kt +50 -0
  14. package/android/src/main/java/com/instantpaycodepush/InstantpayCodePushModule.kt +182 -0
  15. package/android/src/main/java/com/instantpaycodepush/InstantpayCodePushPackage.kt +33 -0
  16. package/android/src/main/java/com/instantpaycodepush/IpayCodePush.kt +101 -0
  17. package/android/src/main/java/com/instantpaycodepush/IpayCodePushException.kt +135 -0
  18. package/android/src/main/java/com/instantpaycodepush/IpayCodePushImpl.kt +329 -0
  19. package/android/src/main/java/com/instantpaycodepush/OkHttpDownloadService.kt +283 -0
  20. package/android/src/main/java/com/instantpaycodepush/ReactIntegrationManager.kt +141 -0
  21. package/android/src/main/java/com/instantpaycodepush/ReactIntegrationManagerBase.kt +35 -0
  22. package/android/src/main/java/com/instantpaycodepush/SignatureVerifier.kt +354 -0
  23. package/android/src/main/java/com/instantpaycodepush/VersionedPreferencesService.kt +70 -0
  24. package/android/src/main/java/com/instantpaycodepush/ZipDecompressionStrategy.kt +198 -0
  25. package/ios/InstantpayCodePush.h +5 -0
  26. package/ios/InstantpayCodePush.mm +21 -0
  27. package/lib/module/DefaultResolver.js +34 -0
  28. package/lib/module/DefaultResolver.js.map +1 -0
  29. package/lib/module/NativeInstantpayCodePush.js +5 -0
  30. package/lib/module/NativeInstantpayCodePush.js.map +1 -0
  31. package/lib/module/checkForUpdate.js +68 -0
  32. package/lib/module/checkForUpdate.js.map +1 -0
  33. package/lib/module/error.js +137 -0
  34. package/lib/module/error.js.map +1 -0
  35. package/lib/module/fetchUpdateInfo.js +36 -0
  36. package/lib/module/fetchUpdateInfo.js.map +1 -0
  37. package/lib/module/global.d.js +8 -0
  38. package/lib/module/global.d.js.map +1 -0
  39. package/lib/module/hooks/useEventCallback.js +13 -0
  40. package/lib/module/hooks/useEventCallback.js.map +1 -0
  41. package/lib/module/index.js +291 -0
  42. package/lib/module/index.js.map +1 -0
  43. package/lib/module/native.js +233 -0
  44. package/lib/module/native.js.map +1 -0
  45. package/lib/module/package.json +1 -0
  46. package/lib/module/store.js +53 -0
  47. package/lib/module/store.js.map +1 -0
  48. package/lib/module/types.js +62 -0
  49. package/lib/module/types.js.map +1 -0
  50. package/lib/module/wrap.js +171 -0
  51. package/lib/module/wrap.js.map +1 -0
  52. package/lib/typescript/package.json +1 -0
  53. package/lib/typescript/src/DefaultResolver.d.ts +10 -0
  54. package/lib/typescript/src/DefaultResolver.d.ts.map +1 -0
  55. package/lib/typescript/src/NativeInstantpayCodePush.d.ts +100 -0
  56. package/lib/typescript/src/NativeInstantpayCodePush.d.ts.map +1 -0
  57. package/lib/typescript/src/checkForUpdate.d.ts +29 -0
  58. package/lib/typescript/src/checkForUpdate.d.ts.map +1 -0
  59. package/lib/typescript/src/error.d.ts +124 -0
  60. package/lib/typescript/src/error.d.ts.map +1 -0
  61. package/lib/typescript/src/fetchUpdateInfo.d.ts +8 -0
  62. package/lib/typescript/src/fetchUpdateInfo.d.ts.map +1 -0
  63. package/lib/typescript/src/hooks/useEventCallback.d.ts +5 -0
  64. package/lib/typescript/src/hooks/useEventCallback.d.ts.map +1 -0
  65. package/lib/typescript/src/index.d.ts +203 -0
  66. package/lib/typescript/src/index.d.ts.map +1 -0
  67. package/lib/typescript/src/native.d.ts +128 -0
  68. package/lib/typescript/src/native.d.ts.map +1 -0
  69. package/lib/typescript/src/store.d.ts +11 -0
  70. package/lib/typescript/src/store.d.ts.map +1 -0
  71. package/lib/typescript/src/types.d.ts +174 -0
  72. package/lib/typescript/src/types.d.ts.map +1 -0
  73. package/lib/typescript/src/wrap.d.ts +179 -0
  74. package/lib/typescript/src/wrap.d.ts.map +1 -0
  75. package/package.json +174 -0
  76. package/src/DefaultResolver.ts +36 -0
  77. package/src/NativeInstantpayCodePush.ts +111 -0
  78. package/src/checkForUpdate.ts +122 -0
  79. package/src/error.ts +159 -0
  80. package/src/fetchUpdateInfo.ts +47 -0
  81. package/src/global.d.ts +23 -0
  82. package/src/hooks/useEventCallback.ts +30 -0
  83. package/src/index.tsx +379 -0
  84. package/src/native.ts +280 -0
  85. package/src/store.ts +69 -0
  86. package/src/types.ts +227 -0
  87. package/src/wrap.tsx +384 -0
@@ -0,0 +1,182 @@
1
+ package com.instantpaycodepush
2
+
3
+ import com.facebook.react.bridge.Promise
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.facebook.react.module.annotations.ReactModule
6
+ import com.facebook.react.bridge.ReadableMap
7
+ import com.facebook.react.bridge.WritableNativeArray
8
+ import com.facebook.react.bridge.WritableNativeMap
9
+ import com.facebook.react.modules.core.DeviceEventManagerModule
10
+
11
+ import kotlinx.coroutines.CoroutineScope
12
+ import kotlinx.coroutines.Dispatchers
13
+ import kotlinx.coroutines.SupervisorJob
14
+ import kotlinx.coroutines.cancel
15
+ import kotlinx.coroutines.launch
16
+
17
+ import android.os.Handler
18
+ import android.os.Looper
19
+
20
+
21
+ @ReactModule(name = InstantpayCodePushModule.NAME)
22
+ class InstantpayCodePushModule(reactContext: ReactApplicationContext) : NativeInstantpayCodePushSpec(reactContext) {
23
+
24
+ private val mReactApplicationContext: ReactApplicationContext = reactContext
25
+
26
+ // Managed coroutine scope for the module lifecycle
27
+ private val moduleScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
28
+
29
+ companion object {
30
+ const val NAME = "InstantpayCodePush"
31
+ private const val CLASS_TAG = "*InstantpayCodePushModule"
32
+ }
33
+
34
+ override fun invalidate() {
35
+ super.invalidate()
36
+ // Cancel all ongoing coroutines when module is destroyed
37
+ moduleScope.cancel()
38
+ }
39
+
40
+ /**
41
+ * Gets the singleton IpayCodePushImpl instance
42
+ */
43
+ private fun getInstance(): IpayCodePushImpl = IpayCodePush.getInstance(mReactApplicationContext)
44
+
45
+ override fun getName(): String {
46
+ return NAME
47
+ }
48
+
49
+ override fun reload(promise: Promise) {
50
+ moduleScope.launch {
51
+ try {
52
+ val impl = getInstance()
53
+ val currentActivity = mReactApplicationContext.currentActivity
54
+ impl.reload(currentActivity)
55
+ promise.resolve(null)
56
+ } catch (e: Exception) {
57
+ CommonHelper.logPrint(CLASS_TAG, "Failed to reload $e",)
58
+ promise.reject("reload", e)
59
+ }
60
+ }
61
+ }
62
+
63
+ override fun updateBundle(
64
+ params: ReadableMap,
65
+ promise: Promise,
66
+ ) {
67
+ moduleScope.launch {
68
+ try {
69
+ val bundleId = params.getString("bundleId")
70
+ if (bundleId == null || bundleId.isEmpty()) {
71
+ promise.reject("MISSING_BUNDLE_ID", "Missing or empty 'bundleId'")
72
+ return@launch
73
+ }
74
+
75
+ val fileUrl = params.getString("fileUrl")
76
+
77
+ // Validate fileUrl format if provided
78
+ if (fileUrl != null && fileUrl.isNotEmpty()) {
79
+ try {
80
+ java.net.URL(fileUrl)
81
+ } catch (e: java.net.MalformedURLException) {
82
+ promise.reject("INVALID_FILE_URL", "Invalid 'fileUrl' provided: $fileUrl")
83
+ return@launch
84
+ }
85
+ }
86
+
87
+ val fileHash = params.getString("fileHash")
88
+
89
+ val impl = getInstance()
90
+
91
+ impl.updateBundle(
92
+ bundleId,
93
+ fileUrl,
94
+ fileHash,
95
+ ) { progress ->
96
+ // Post to Main thread for React Native event emission
97
+ Handler(Looper.getMainLooper()).post {
98
+ try {
99
+ val progressParams =
100
+ WritableNativeMap().apply {
101
+ putDouble("progress", progress)
102
+ }
103
+
104
+ this@InstantpayCodePushModule
105
+ .mReactApplicationContext
106
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
107
+ ?.emit("onProgress", progressParams)
108
+ } catch (e: Exception) {
109
+ CommonHelper.logPrint(CommonHelper.WARNING_LOG,CLASS_TAG, "Failed to emit progress (bridge may be unavailable): ${e.message}")
110
+ }
111
+ }
112
+ }
113
+ promise.resolve(true)
114
+ } catch (e: IpayCodePushException) {
115
+ CommonHelper.logPrint(CommonHelper.ERROR_LOG,CLASS_TAG, "Catch IpayCodePushException: ${e.message}")
116
+ promise.reject(e.code, e.message)
117
+ } catch (e: Exception) {
118
+ CommonHelper.logPrint(CommonHelper.ERROR_LOG,CLASS_TAG, "Catch Exception: ${e.message}")
119
+ promise.reject("UNKNOWN_ERROR", e.message ?: "An unknown error occurred")
120
+ }
121
+ }
122
+ }
123
+
124
+ override fun getTypedExportedConstants(): Map<String, Any?> {
125
+ val constants: MutableMap<String, Any?> = HashMap()
126
+ constants["MIN_BUNDLE_ID"] = IpayCodePush.getMinBundleId()
127
+ constants["APP_VERSION"] = IpayCodePush.getAppVersion(mReactApplicationContext)
128
+ constants["CHANNEL"] = IpayCodePush.getChannel(mReactApplicationContext)
129
+ constants["FINGERPRINT_HASH"] = IpayCodePush.getFingerprintHash(mReactApplicationContext)
130
+ return constants
131
+ }
132
+
133
+ override fun addListener(
134
+ @Suppress("UNUSED_PARAMETER") eventName: String?,
135
+ ) {
136
+ // No-op
137
+ }
138
+
139
+ override fun removeListeners(
140
+ @Suppress("UNUSED_PARAMETER") count: Double,
141
+ ) {
142
+ // No-op
143
+ }
144
+
145
+ override fun notifyAppReady(params: ReadableMap): WritableNativeMap {
146
+ val result = WritableNativeMap()
147
+ val bundleId = params.getString("bundleId")
148
+ if (bundleId == null) {
149
+ result.putString("status", "STABLE")
150
+ return result
151
+ }
152
+
153
+ val impl = getInstance()
154
+ val statusMap = impl.notifyAppReady(bundleId)
155
+
156
+ result.putString("status", statusMap["status"] as? String ?: "STABLE")
157
+ statusMap["crashedBundleId"]?.let {
158
+ result.putString("crashedBundleId", it as String)
159
+ }
160
+
161
+ return result
162
+ }
163
+
164
+ override fun getCrashHistory(): WritableNativeArray {
165
+ val impl = getInstance()
166
+ val crashHistory = impl.getCrashHistory()
167
+ val result = WritableNativeArray()
168
+ crashHistory.forEach { result.pushString(it) }
169
+ return result
170
+ }
171
+
172
+ override fun clearCrashHistory(): Boolean {
173
+ val impl = getInstance()
174
+ return impl.clearCrashHistory()
175
+ }
176
+
177
+ override fun getBaseURL(): String {
178
+ val impl = getInstance()
179
+ return impl.getBaseURL()
180
+ }
181
+
182
+ }
@@ -0,0 +1,33 @@
1
+ package com.instantpaycodepush
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+ import java.util.HashMap
9
+
10
+ class InstantpayCodePushPackage : BaseReactPackage() {
11
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
12
+ return if (name == InstantpayCodePushModule.NAME) {
13
+ InstantpayCodePushModule(reactContext)
14
+ } else {
15
+ null
16
+ }
17
+ }
18
+
19
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
20
+ return ReactModuleInfoProvider {
21
+ val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
22
+ moduleInfos[InstantpayCodePushModule.NAME] = ReactModuleInfo(
23
+ InstantpayCodePushModule.NAME,
24
+ InstantpayCodePushModule.NAME,
25
+ false, // canOverrideExistingModule
26
+ false, // needsEagerInit
27
+ false, // isCxxModule
28
+ true // isTurboModule
29
+ )
30
+ moduleInfos
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,101 @@
1
+ package com.instantpaycodepush
2
+
3
+ import android.app.Activity
4
+ import android.content.Context
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+
7
+ class IpayCodePush {
8
+
9
+ companion object{
10
+
11
+ @Volatile
12
+ private var instance: IpayCodePushImpl? = null
13
+
14
+ /**
15
+ * Gets or creates the singleton instance
16
+ * Thread-safe double-checked locking
17
+ * @param context Application context
18
+ * @return The singleton IpayCodePushImpl instance
19
+ */
20
+ fun getInstance(context: Context): IpayCodePushImpl =
21
+ instance ?: synchronized(this){
22
+ instance ?: IpayCodePushImpl(context.applicationContext).also {
23
+ instance = it
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Gets the JS bundle file path using the default singleton instance
29
+ * @param context Application context
30
+ * @return The path to the bundle file
31
+ */
32
+ fun getJSBundleFile(context: Context): String = getInstance(context).getJSBundleFile()
33
+
34
+ /**
35
+ * Updates the bundle using the default singleton instance
36
+ * @param context Application context
37
+ * @param bundleId ID of the bundle to update
38
+ * @param fileUrl URL of the bundle file to download (or null to reset)
39
+ * @param fileHash Combined hash string for verification (sig:<signature> or <hex_hash>)
40
+ * @param progressCallback Callback for download progress updates
41
+ * @throws IpayCodePushException if the update fails
42
+ */
43
+ suspend fun updateBundle(
44
+ context: Context,
45
+ bundleId: String,
46
+ fileUrl: String?,
47
+ fileHash: String?,
48
+ progressCallback: (Double) -> Unit,
49
+ ) {
50
+ getInstance(context).updateBundle(bundleId, fileUrl, fileHash, progressCallback)
51
+ }
52
+
53
+ /**
54
+ * Reloads the React Native application using the default singleton instance
55
+ * @param context Application context
56
+ */
57
+ suspend fun reload(context: Context) {
58
+ val currentActivity = getCurrentActivity(context)
59
+ getInstance(context).reload(currentActivity)
60
+ }
61
+
62
+ /**
63
+ * Gets the app version - delegates to IpayCodePushImpl static method
64
+ * @param context Application context
65
+ * @return App version name or null if not available
66
+ */
67
+ fun getAppVersion(context: Context): String? = IpayCodePushImpl.getAppVersion(context)
68
+
69
+ /**
70
+ * Gets the minimum bundle ID - delegates to IpayCodePushImpl static method
71
+ * @return The minimum bundle ID string
72
+ */
73
+ fun getMinBundleId(): String = IpayCodePushImpl.getMinBundleId()
74
+
75
+ /**
76
+ * Gets the current fingerprint hash - delegates to IpayCodePushImpl static method
77
+ * @param context Application context
78
+ * @return The fingerprint hash or null if not set
79
+ */
80
+ fun getFingerprintHash(context: Context): String? = IpayCodePushImpl.getFingerprintHash(context)
81
+
82
+ /**
83
+ * Gets the current update channel - delegates to IpayCodePushImpl static method
84
+ * @param context Application context
85
+ * @return The channel name or null if not set
86
+ */
87
+ fun getChannel(context: Context): String = IpayCodePushImpl.getChannel(context)
88
+
89
+ /**
90
+ * Gets the current activity from ReactApplicationContext
91
+ * @param context Context that might be a ReactApplicationContext
92
+ * @return The current activity or null
93
+ */
94
+ private fun getCurrentActivity(context: Context): Activity? =
95
+ if (context is ReactApplicationContext) {
96
+ context.currentActivity
97
+ } else {
98
+ null
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,135 @@
1
+ package com.instantpaycodepush
2
+
3
+ /**
4
+ * Exception class for Ipay Code Push errors
5
+ * Matches error codes defined in packages/react-native/src/errors.ts
6
+ */
7
+ class IpayCodePushException(
8
+ val code: String,
9
+ message: String,
10
+ cause: Throwable? = null,
11
+ ) : Exception(message, cause){
12
+
13
+ companion object {
14
+ // Parameter validation errors
15
+ fun missingBundleId() =
16
+ IpayCodePushException(
17
+ "MISSING_BUNDLE_ID",
18
+ "Missing or empty 'bundleId'",
19
+ )
20
+
21
+ fun invalidFileUrl() =
22
+ IpayCodePushException(
23
+ "INVALID_FILE_URL",
24
+ "Invalid 'fileUrl' provided",
25
+ )
26
+
27
+ // Bundle storage errors
28
+ fun directoryCreationFailed() =
29
+ IpayCodePushException(
30
+ "DIRECTORY_CREATION_FAILED",
31
+ "Failed to create bundle directory",
32
+ )
33
+
34
+ fun downloadFailed(cause: Throwable? = null) =
35
+ IpayCodePushException(
36
+ "DOWNLOAD_FAILED",
37
+ "Failed to download bundle",
38
+ cause,
39
+ )
40
+
41
+ fun incompleteDownload(
42
+ expectedSize: Long,
43
+ actualSize: Long,
44
+ ) = IpayCodePushException(
45
+ "INCOMPLETE_DOWNLOAD",
46
+ "Download incomplete: received $actualSize bytes, expected $expectedSize bytes",
47
+ )
48
+
49
+ fun extractionFormatError(cause: Throwable? = null) =
50
+ IpayCodePushException(
51
+ "EXTRACTION_FORMAT_ERROR",
52
+ "Invalid or corrupted bundle archive format",
53
+ cause,
54
+ )
55
+
56
+ fun invalidBundle() =
57
+ IpayCodePushException(
58
+ "INVALID_BUNDLE",
59
+ "Bundle missing required platform files (index.ios.bundle or index.android.bundle)",
60
+ )
61
+
62
+ fun insufficientDiskSpace(
63
+ required: Long,
64
+ available: Long,
65
+ ) = IpayCodePushException(
66
+ "INSUFFICIENT_DISK_SPACE",
67
+ "Insufficient disk space: need $required bytes, available $available bytes",
68
+ )
69
+
70
+ fun signatureVerificationFailed(cause: Throwable? = null) =
71
+ IpayCodePushException(
72
+ "SIGNATURE_VERIFICATION_FAILED",
73
+ "Bundle signature verification failed",
74
+ cause,
75
+ )
76
+
77
+ fun moveOperationFailed() =
78
+ IpayCodePushException(
79
+ "MOVE_OPERATION_FAILED",
80
+ "Failed to move bundle files",
81
+ )
82
+
83
+ fun bundleInCrashedHistory(bundleId: String) =
84
+ IpayCodePushException(
85
+ "BUNDLE_IN_CRASHED_HISTORY",
86
+ "Bundle '$bundleId' is in crashed history and cannot be applied",
87
+ )
88
+
89
+ // Signature verification errors
90
+ fun publicKeyNotConfigured() =
91
+ IpayCodePushException(
92
+ "PUBLIC_KEY_NOT_CONFIGURED",
93
+ "Public key not configured for signature verification",
94
+ )
95
+
96
+ fun invalidPublicKeyFormat() =
97
+ IpayCodePushException(
98
+ "INVALID_PUBLIC_KEY_FORMAT",
99
+ "Invalid public key format",
100
+ )
101
+
102
+ fun fileHashMismatch() =
103
+ IpayCodePushException(
104
+ "FILE_HASH_MISMATCH",
105
+ "File hash verification failed",
106
+ )
107
+
108
+ fun fileReadFailed() =
109
+ IpayCodePushException(
110
+ "FILE_READ_FAILED",
111
+ "Failed to read file for verification",
112
+ )
113
+
114
+ fun unsignedNotAllowed() =
115
+ IpayCodePushException(
116
+ "UNSIGNED_NOT_ALLOWED",
117
+ "Unsigned bundles are not allowed",
118
+ )
119
+
120
+ fun securityFrameworkError(cause: Throwable? = null) =
121
+ IpayCodePushException(
122
+ "SECURITY_FRAMEWORK_ERROR",
123
+ "Security framework error occurred",
124
+ cause,
125
+ )
126
+
127
+ // Internal errors
128
+ fun unknownError(cause: Throwable? = null) =
129
+ IpayCodePushException(
130
+ "UNKNOWN_ERROR",
131
+ "An unknown error occurred",
132
+ cause,
133
+ )
134
+ }
135
+ }