@hot-updater/react-native 0.33.1 → 0.34.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.
@@ -0,0 +1,47 @@
1
+ package com.hotupdater
2
+
3
+ /**
4
+ * Thread-safe programmatic configuration holder for HotUpdater.
5
+ *
6
+ * When values are set here, they take priority over manifest metadata and
7
+ * Android string resources. This enables brownfield/AAR setups where the RN
8
+ * module cannot rely on the host app's manifest/strings.xml for configuration.
9
+ */
10
+ object HotUpdaterConfig {
11
+ @Volatile
12
+ var fingerprintHash: String? = null
13
+
14
+ @Volatile
15
+ var publicKey: String? = null
16
+
17
+ @Volatile
18
+ var channel: String? = null
19
+
20
+ /**
21
+ * When set, this value is used verbatim as the storage isolation key,
22
+ * bypassing the default `HotUpdaterPrefs_{fingerprint}_{appVersion}_{channel}`
23
+ * composition. Use this to keep the OTA cache stable across host app version
24
+ * bumps (e.g. key only by fingerprint + channel).
25
+ */
26
+ @Volatile
27
+ var isolationKey: String? = null
28
+
29
+ fun configure(
30
+ fingerprintHash: String? = null,
31
+ publicKey: String? = null,
32
+ channel: String? = null,
33
+ isolationKey: String? = null,
34
+ ) {
35
+ this.fingerprintHash = fingerprintHash
36
+ this.publicKey = publicKey
37
+ this.channel = channel
38
+ this.isolationKey = isolationKey
39
+ }
40
+
41
+ fun clear() {
42
+ fingerprintHash = null
43
+ publicKey = null
44
+ channel = null
45
+ isolationKey = null
46
+ }
47
+ }
@@ -105,6 +105,10 @@ class HotUpdaterImpl {
105
105
  * @return The isolation key in format: HotUpdaterPrefs_{fingerprintOrVersion}_{channel}
106
106
  */
107
107
  private fun getIsolationKey(context: Context): String {
108
+ // Programmatic override (brownfield/AAR). When set, it is used verbatim
109
+ // so the OTA cache can stay stable across host app version bumps.
110
+ HotUpdaterConfig.isolationKey?.takeIf { it.isNotEmpty() }?.let { return it }
111
+
108
112
  val fingerprintHash = getFingerprintHash(context)
109
113
 
110
114
  // Get app version and channel
@@ -13,9 +13,21 @@ object NativeConfigUtils {
13
13
  context: Context,
14
14
  metaDataKey: String,
15
15
  stringResourceName: String,
16
- ): String? =
17
- getManifestMetaDataString(context, metaDataKey)
16
+ ): String? {
17
+ // Programmatic overrides take priority over manifest metadata and string
18
+ // resources (brownfield/AAR setups, see HotUpdaterConfig).
19
+ val override =
20
+ when (metaDataKey) {
21
+ FINGERPRINT_HASH_META_DATA_KEY -> HotUpdaterConfig.fingerprintHash
22
+ PUBLIC_KEY_META_DATA_KEY -> HotUpdaterConfig.publicKey
23
+ CHANNEL_META_DATA_KEY -> HotUpdaterConfig.channel
24
+ else -> null
25
+ }
26
+ override?.takeIf { it.isNotEmpty() }?.let { return it }
27
+
28
+ return getManifestMetaDataString(context, metaDataKey)
18
29
  ?: getStringResource(context, stringResourceName)
30
+ }
19
31
 
20
32
  private fun getManifestMetaDataString(
21
33
  context: Context,
@@ -99,6 +99,24 @@ class HotUpdater {
99
99
  */
100
100
  fun getChannel(context: Context): String = HotUpdaterImpl.getChannel(context)
101
101
 
102
+ /**
103
+ * Programmatic configuration for brownfield/AAR setups where manifest
104
+ * metadata or string resources in the host app are not desirable.
105
+ */
106
+ fun configure(
107
+ fingerprintHash: String? = null,
108
+ publicKey: String? = null,
109
+ channel: String? = null,
110
+ isolationKey: String? = null,
111
+ ) {
112
+ HotUpdaterConfig.configure(
113
+ fingerprintHash = fingerprintHash,
114
+ publicKey = publicKey,
115
+ channel = channel,
116
+ isolationKey = isolationKey,
117
+ )
118
+ }
119
+
102
120
  /**
103
121
  * Sets the ReactHost for brownfield apps (New Architecture).
104
122
  * Sets the ReactHost for brownfield apps that don't have ReactApplication.
@@ -98,6 +98,24 @@ class HotUpdater {
98
98
  */
99
99
  fun getChannel(context: Context): String = HotUpdaterImpl.getChannel(context)
100
100
 
101
+ /**
102
+ * Programmatic configuration for brownfield/AAR setups where manifest
103
+ * metadata or string resources in the host app are not desirable.
104
+ */
105
+ fun configure(
106
+ fingerprintHash: String? = null,
107
+ publicKey: String? = null,
108
+ channel: String? = null,
109
+ isolationKey: String? = null,
110
+ ) {
111
+ HotUpdaterConfig.configure(
112
+ fingerprintHash = fingerprintHash,
113
+ publicKey = publicKey,
114
+ channel = channel,
115
+ isolationKey = isolationKey,
116
+ )
117
+ }
118
+
101
119
  /**
102
120
  * Sets the ReactHost for brownfield apps (Old Architecture - No-op).
103
121
  * In old architecture, this is a no-op since ReactHost is not available.
@@ -456,6 +456,14 @@ RCT_EXPORT_MODULE();
456
456
  return [[HotUpdater sharedImpl] bundleURLWithBundle:bundle];
457
457
  }
458
458
 
459
+ + (void)configureWithFingerprintHash:(NSString *)fingerprintHash
460
+ publicKey:(NSString *)publicKey
461
+ channel:(NSString *)channel
462
+ isolationKey:(NSString *)isolationKey {
463
+ HotUpdaterConfig *config = [HotUpdaterConfig shared];
464
+ [config configureWithFingerprintHash:fingerprintHash publicKey:publicKey channel:channel isolationKey:isolationKey];
465
+ }
466
+
459
467
  #pragma mark - Progress Updates & Event Emitting (Keep in ObjC Wrapper)
460
468
 
461
469
  - (void)handleDownloadProgress:(NSNotification *)notification {
@@ -0,0 +1,54 @@
1
+ import Foundation
2
+
3
+ /// Thread-safe programmatic configuration holder for HotUpdater.
4
+ ///
5
+ /// When values are set here, they take priority over `Info.plist` (iOS) and
6
+ /// manifest metadata / string resources (Android). This enables brownfield /
7
+ /// prebuilt-framework setups where the RN module cannot rely on the host app's
8
+ /// `Info.plist` for configuration.
9
+ @objcMembers
10
+ public class HotUpdaterConfig: NSObject {
11
+ public static let shared = HotUpdaterConfig()
12
+
13
+ private let lock = NSLock()
14
+
15
+ private var _fingerprintHash: String?
16
+ private var _publicKey: String?
17
+ private var _channel: String?
18
+ private var _isolationKey: String?
19
+
20
+ public var fingerprintHash: String? {
21
+ get { lock.lock(); defer { lock.unlock() }; return _fingerprintHash }
22
+ set { lock.lock(); defer { lock.unlock() }; _fingerprintHash = newValue }
23
+ }
24
+
25
+ public var publicKey: String? {
26
+ get { lock.lock(); defer { lock.unlock() }; return _publicKey }
27
+ set { lock.lock(); defer { lock.unlock() }; _publicKey = newValue }
28
+ }
29
+
30
+ public var channel: String? {
31
+ get { lock.lock(); defer { lock.unlock() }; return _channel }
32
+ set { lock.lock(); defer { lock.unlock() }; _channel = newValue }
33
+ }
34
+
35
+ /// When set, used verbatim as the storage isolation key, bypassing the default
36
+ /// `hotupdater_{fingerprint}_{appVersion}_{channel}_` composition. Use this to keep
37
+ /// the OTA cache stable across host app version bumps (e.g. key only by fingerprint + channel).
38
+ public var isolationKey: String? {
39
+ get { lock.lock(); defer { lock.unlock() }; return _isolationKey }
40
+ set { lock.lock(); defer { lock.unlock() }; _isolationKey = newValue }
41
+ }
42
+
43
+ @objc(configureWithFingerprintHash:publicKey:channel:isolationKey:)
44
+ public func configureWithFingerprintHash(_ fingerprintHash: String?, publicKey: String?, channel: String?, isolationKey: String?) {
45
+ self.fingerprintHash = fingerprintHash
46
+ self.publicKey = publicKey
47
+ self.channel = channel
48
+ self.isolationKey = isolationKey
49
+ }
50
+
51
+ public func clear() {
52
+ configureWithFingerprintHash(nil, publicKey: nil, channel: nil, isolationKey: nil)
53
+ }
54
+ }
@@ -93,6 +93,9 @@ private func hotUpdaterPerformRecoveryReload() -> Bool {
93
93
  * Returns the app version from main bundle info.
94
94
  */
95
95
  public static var appChannel: String {
96
+ if let override = HotUpdaterConfig.shared.channel, !override.isEmpty {
97
+ return override
98
+ }
96
99
  return Bundle.main.object(forInfoDictionaryKey: "HOT_UPDATER_CHANNEL") as? String ?? Self.DEFAULT_CHANNEL
97
100
  }
98
101
 
@@ -101,8 +104,15 @@ private func hotUpdaterPerformRecoveryReload() -> Bool {
101
104
  * @return The isolation key in format: hotupdater_{fingerprintOrVersion}_{channel}_
102
105
  */
103
106
  public static func getIsolationKey() -> String {
104
- // Get fingerprint hash from Info.plist
105
- let fingerprintHash = Bundle.main.object(forInfoDictionaryKey: "HOT_UPDATER_FINGERPRINT_HASH") as? String
107
+ // Programmatic override (brownfield/XCFramework). When set, it is used verbatim
108
+ // so the OTA cache can stay stable across host app version bumps.
109
+ if let override = HotUpdaterConfig.shared.isolationKey, !override.isEmpty {
110
+ return override
111
+ }
112
+
113
+ // Get fingerprint hash from programmatic override or Info.plist
114
+ let fingerprintHash = HotUpdaterConfig.shared.fingerprintHash
115
+ ?? Bundle.main.object(forInfoDictionaryKey: "HOT_UPDATER_FINGERPRINT_HASH") as? String
106
116
 
107
117
  // Get app version and channel
108
118
  let appVersion = self.appVersion ?? "unknown"
@@ -126,6 +136,9 @@ private func hotUpdaterPerformRecoveryReload() -> Bool {
126
136
  * @return The channel name or nil if not set
127
137
  */
128
138
  public func getChannel() -> String {
139
+ if let override = HotUpdaterConfig.shared.channel, !override.isEmpty {
140
+ return override
141
+ }
129
142
  if let savedChannel = try? preferences.getItem(forKey: Self.CHANNEL_STORAGE_KEY),
130
143
  !savedChannel.isEmpty {
131
144
  return savedChannel
@@ -143,6 +156,9 @@ private func hotUpdaterPerformRecoveryReload() -> Bool {
143
156
  * @return The fingerprint hash or nil if not set
144
157
  */
145
158
  public func getFingerprintHash() -> String? {
159
+ if let override = HotUpdaterConfig.shared.fingerprintHash, !override.isEmpty {
160
+ return override
161
+ }
146
162
  return Bundle.main.object(forInfoDictionaryKey: "HOT_UPDATER_FINGERPRINT_HASH") as? String
147
163
  }
148
164
 
@@ -106,6 +106,9 @@ public class SignatureVerifier {
106
106
  * @return Public key PEM string or nil if not configured
107
107
  */
108
108
  private static func getPublicKeyFromConfig() -> String? {
109
+ if let override = HotUpdaterConfig.shared.publicKey, !override.isEmpty {
110
+ return override
111
+ }
109
112
  guard let publicKeyPEM = Bundle.main.object(forInfoDictionaryKey: "HOT_UPDATER_PUBLIC_KEY") as? String else {
110
113
  NSLog("[SignatureVerifier] HOT_UPDATER_PUBLIC_KEY not found in Info.plist")
111
114
  return nil
@@ -35,6 +35,15 @@
35
35
  */
36
36
  - (NSURL *)bundleURLWithBundle:(NSBundle *)bundle NS_SWIFT_NAME(bundleURL(bundle:));
37
37
 
38
+ /**
39
+ * Programmatic configuration for brownfield / XCFramework setups where
40
+ * Info.plist (host) or string resources (Android) are not desirable.
41
+ */
42
+ + (void)configureWithFingerprintHash:(nullable NSString *)fingerprintHash
43
+ publicKey:(nullable NSString *)publicKey
44
+ channel:(nullable NSString *)channel
45
+ isolationKey:(nullable NSString *)isolationKey;
46
+
38
47
  /**
39
48
  * 다운로드 진행 상황 업데이트 시간을 추적하는 속성
40
49
  */
@@ -4,5 +4,5 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.HOT_UPDATER_SDK_VERSION = void 0;
7
- const HOT_UPDATER_SDK_VERSION = exports.HOT_UPDATER_SDK_VERSION = "0.33.1";
7
+ const HOT_UPDATER_SDK_VERSION = exports.HOT_UPDATER_SDK_VERSION = "0.34.0";
8
8
  //# sourceMappingURL=sdkVersion.js.map
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
 
3
- export const HOT_UPDATER_SDK_VERSION = "0.33.1";
3
+ export const HOT_UPDATER_SDK_VERSION = "0.34.0";
4
4
  //# sourceMappingURL=sdkVersion.js.map
@@ -1,2 +1,2 @@
1
- export declare const HOT_UPDATER_SDK_VERSION = "0.33.1";
1
+ export declare const HOT_UPDATER_SDK_VERSION = "0.34.0";
2
2
  //# sourceMappingURL=sdkVersion.d.ts.map
@@ -1,2 +1,2 @@
1
- export declare const HOT_UPDATER_SDK_VERSION = "0.33.1";
1
+ export declare const HOT_UPDATER_SDK_VERSION = "0.34.0";
2
2
  //# sourceMappingURL=sdkVersion.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hot-updater/react-native",
3
- "version": "0.33.1",
3
+ "version": "0.34.0",
4
4
  "description": "React Native OTA solution for self-hosted",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./lib/commonjs/index.js",
@@ -125,14 +125,14 @@
125
125
  "react-native": "0.79.1",
126
126
  "react-native-builder-bob": "0.41.0",
127
127
  "typescript": "^6.0.2",
128
- "hot-updater": "0.33.1"
128
+ "hot-updater": "0.34.0"
129
129
  },
130
130
  "dependencies": {
131
131
  "use-sync-external-store": "1.5.0",
132
- "@hot-updater/cli-tools": "0.33.1",
133
- "@hot-updater/core": "0.33.1",
134
- "@hot-updater/js": "0.33.1",
135
- "@hot-updater/plugin-core": "0.33.1"
132
+ "@hot-updater/cli-tools": "0.34.0",
133
+ "@hot-updater/plugin-core": "0.34.0",
134
+ "@hot-updater/core": "0.34.0",
135
+ "@hot-updater/js": "0.34.0"
136
136
  },
137
137
  "scripts": {
138
138
  "build": "node ./scripts/sync-sdk-version.mjs run -- bob build && tsc -p plugin/tsconfig.build.json",
package/src/sdkVersion.ts CHANGED
@@ -1 +1 @@
1
- export const HOT_UPDATER_SDK_VERSION = "0.33.1";
1
+ export const HOT_UPDATER_SDK_VERSION = "0.34.0";