@hot-updater/react-native 0.25.10 → 0.25.13

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.
@@ -99,7 +99,7 @@ class HotUpdaterImpl {
99
99
  */
100
100
  private fun getIsolationKey(context: Context): String {
101
101
  // Get fingerprint hash directly from resources
102
- val fingerprintId = context.resources.getIdentifier("hot_updater_fingerprint_hash", "string", context.packageName)
102
+ val fingerprintId = StringResourceUtils.getIdentifier(context, "hot_updater_fingerprint_hash")
103
103
  val fingerprintHash =
104
104
  if (fingerprintId != 0) {
105
105
  context.getString(fingerprintId).takeIf { it.isNotEmpty() }
@@ -141,7 +141,7 @@ class HotUpdaterImpl {
141
141
  }
142
142
 
143
143
  fun getChannel(context: Context): String {
144
- val id = context.resources.getIdentifier("hot_updater_channel", "string", context.packageName)
144
+ val id = StringResourceUtils.getIdentifier(context, "hot_updater_channel")
145
145
  return if (id != 0) {
146
146
  context.getString(id).takeIf { it.isNotEmpty() } ?: DEFAULT_CHANNEL
147
147
  } else {
@@ -215,7 +215,7 @@ class HotUpdaterImpl {
215
215
  * @return The fingerprint hash or null if not set
216
216
  */
217
217
  fun getFingerprintHash(context: Context): String? {
218
- val id = context.resources.getIdentifier("hot_updater_fingerprint_hash", "string", context.packageName)
218
+ val id = StringResourceUtils.getIdentifier(context, "hot_updater_fingerprint_hash")
219
219
  return if (id != 0) {
220
220
  context.getString(id).takeIf { it.isNotEmpty() }
221
221
  } else {
@@ -229,7 +229,7 @@ class HotUpdaterImpl {
229
229
  * @return The fingerprint hash or null if not set
230
230
  */
231
231
  fun getFingerprintHash(): String? {
232
- val id = context.resources.getIdentifier("hot_updater_fingerprint_hash", "string", context.packageName)
232
+ val id = StringResourceUtils.getIdentifier(context, "hot_updater_fingerprint_hash")
233
233
  return if (id != 0) {
234
234
  context.getString(id).takeIf { it.isNotEmpty() }
235
235
  } else {
@@ -242,7 +242,7 @@ class HotUpdaterImpl {
242
242
  * @return The channel name or null if not set
243
243
  */
244
244
  fun getChannel(): String {
245
- val id = context.resources.getIdentifier("hot_updater_channel", "string", context.packageName)
245
+ val id = StringResourceUtils.getIdentifier(context, "hot_updater_channel")
246
246
  return if (id != 0) {
247
247
  context.getString(id).takeIf { it.isNotEmpty() } ?: DEFAULT_CHANNEL
248
248
  } else {
@@ -97,12 +97,7 @@ object SignatureVerifier {
97
97
  * @return Public key PEM string or null if not configured
98
98
  */
99
99
  private fun getPublicKeyFromConfig(context: Context): String? {
100
- val resourceId =
101
- context.resources.getIdentifier(
102
- "hot_updater_public_key",
103
- "string",
104
- context.packageName,
105
- )
100
+ val resourceId = StringResourceUtils.getIdentifier(context, "hot_updater_public_key")
106
101
 
107
102
  if (resourceId == 0) {
108
103
  Log.d(TAG, "hot_updater_public_key not found in strings.xml")
@@ -0,0 +1,45 @@
1
+ package com.hotupdater
2
+
3
+ import android.content.Context
4
+
5
+ /**
6
+ * Utility class for string resource lookup operations
7
+ */
8
+ object StringResourceUtils {
9
+ /**
10
+ * Resolves a string resource ID with namespace fallback.
11
+ * Handles AGP 7.0+ where namespace and applicationId are decoupled.
12
+ * @param context Application context
13
+ * @param resourceName The string resource name to resolve
14
+ * @return The resolved resource ID, or 0 if not found
15
+ */
16
+ fun getIdentifier(
17
+ context: Context,
18
+ resourceName: String,
19
+ ): Int {
20
+ var resourceId =
21
+ context.resources.getIdentifier(
22
+ resourceName,
23
+ "string",
24
+ context.packageName,
25
+ )
26
+
27
+ // Fallback: try namespace derived from Application class package
28
+ if (resourceId == 0) {
29
+ val appClassName = context.applicationInfo.className
30
+ if (appClassName != null) {
31
+ val namespace = appClassName.substringBeforeLast('.')
32
+ if (namespace != context.packageName) {
33
+ resourceId =
34
+ context.resources.getIdentifier(
35
+ resourceName,
36
+ "string",
37
+ namespace,
38
+ )
39
+ }
40
+ }
41
+ }
42
+
43
+ return resourceId
44
+ }
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hot-updater/react-native",
3
- "version": "0.25.10",
3
+ "version": "0.25.13",
4
4
  "description": "React Native OTA solution for self-hosted",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -120,14 +120,14 @@
120
120
  "react-native": "0.79.1",
121
121
  "react-native-builder-bob": "^0.40.10",
122
122
  "typescript": "^5.8.3",
123
- "hot-updater": "0.25.10"
123
+ "hot-updater": "0.25.13"
124
124
  },
125
125
  "dependencies": {
126
126
  "use-sync-external-store": "1.5.0",
127
- "@hot-updater/core": "0.25.10",
128
- "@hot-updater/js": "0.25.10",
129
- "@hot-updater/cli-tools": "0.25.10",
130
- "@hot-updater/plugin-core": "0.25.10"
127
+ "@hot-updater/cli-tools": "0.25.13",
128
+ "@hot-updater/js": "0.25.13",
129
+ "@hot-updater/plugin-core": "0.25.13",
130
+ "@hot-updater/core": "0.25.13"
131
131
  },
132
132
  "scripts": {
133
133
  "build": "bob build && tsc -p plugin/tsconfig.build.json",
@@ -15,6 +15,23 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
16
  exports.transformAndroid = transformAndroid;
17
17
  exports.transformIOS = transformIOS;
18
+ function findMatchingClosingParen(source, openParenIndex) {
19
+ var depth = 0;
20
+ for (var i = openParenIndex; i < source.length; i += 1) {
21
+ var char = source[i];
22
+ if (char === "(") {
23
+ depth += 1;
24
+ continue;
25
+ }
26
+ if (char === ")") {
27
+ depth -= 1;
28
+ if (depth === 0) {
29
+ return i;
30
+ }
31
+ }
32
+ }
33
+ return -1;
34
+ }
18
35
  /**
19
36
  * Helper to add lines if they don't exist, anchored by a specific string.
20
37
  */
@@ -38,7 +55,6 @@ function addLinesOnce(contents, anchor, linesToAdd) {
38
55
  function transformAndroidReactHost(contents) {
39
56
  var kotlinImport = "import com.hotupdater.HotUpdater";
40
57
  var kotlinImportAnchor = "import com.facebook.react.ReactApplication";
41
- var kotlinMethodCheck = "HotUpdater.getJSBundleFile(applicationContext)";
42
58
  // Quick pattern detection: only touch files using getDefaultReactHost
43
59
  // with the new RN 0.82+ parameter style.
44
60
  if (!contents.includes("getDefaultReactHost(") ||
@@ -47,22 +63,25 @@ function transformAndroidReactHost(contents) {
47
63
  }
48
64
  // 1. Ensure HotUpdater import exists (idempotent via addLinesOnce)
49
65
  var result = addLinesOnce(contents, kotlinImportAnchor, [kotlinImport]);
50
- // 2. If jsBundleFilePath is already wired to HotUpdater, do nothing
51
- if (result.includes(kotlinMethodCheck) &&
52
- result.includes("jsBundleFilePath")) {
66
+ var callNeedle = "getDefaultReactHost(";
67
+ var callStartIndex = result.indexOf(callNeedle);
68
+ if (callStartIndex === -1) {
53
69
  return result;
54
70
  }
55
- var lines = result.split("\n");
56
- var callIndex = lines.findIndex(function (line) {
57
- return line.includes("getDefaultReactHost(");
58
- });
59
- if (callIndex === -1) {
71
+ var openParenIndex = callStartIndex + callNeedle.length - 1;
72
+ var closeParenIndex = findMatchingClosingParen(result, openParenIndex);
73
+ if (closeParenIndex === -1) {
60
74
  return result;
61
75
  }
76
+ var callContents = result.slice(callStartIndex, closeParenIndex + 1);
77
+ if (callContents.includes("jsBundleFilePath")) {
78
+ return result;
79
+ }
80
+ var callLines = callContents.split("\n");
62
81
  // Determine the indentation used for parameters (e.g. " ")
63
82
  var paramIndent = "";
64
- for (var i = callIndex + 1; i < lines.length; i += 1) {
65
- var line = lines[i];
83
+ for (var i = 1; i < callLines.length; i += 1) {
84
+ var line = callLines[i];
66
85
  var trimmed = line.trim();
67
86
  if (trimmed.length === 0) {
68
87
  continue;
@@ -78,26 +97,22 @@ function transformAndroidReactHost(contents) {
78
97
  if (!paramIndent) {
79
98
  return result;
80
99
  }
81
- // Find the closing line of the call (a line that is just ")" with indentation).
82
- var closingIndex = -1;
83
- for (var i = callIndex + 1; i < lines.length; i += 1) {
84
- if (lines[i].trim() === ")") {
85
- closingIndex = i;
86
- break;
87
- }
100
+ var closingLineStartIndex = result.lastIndexOf("\n", closeParenIndex);
101
+ var insertionIndex = closingLineStartIndex === -1 ? 0 : closingLineStartIndex + 1;
102
+ var prefix = result.slice(0, insertionIndex);
103
+ var suffix = result.slice(insertionIndex);
104
+ var prevNonWhitespaceIndex = prefix.length - 1;
105
+ while (prevNonWhitespaceIndex >= 0 &&
106
+ /\s/.test(prefix[prevNonWhitespaceIndex])) {
107
+ prevNonWhitespaceIndex -= 1;
88
108
  }
89
- if (closingIndex === -1) {
90
- return result;
91
- }
92
- // Avoid inserting twice if jsBundleFilePath already added somewhere in the call.
93
- for (var i = callIndex; i < closingIndex; i += 1) {
94
- if (lines[i].includes("jsBundleFilePath")) {
95
- return result;
96
- }
109
+ if (prevNonWhitespaceIndex >= 0 &&
110
+ prefix[prevNonWhitespaceIndex] !== "," &&
111
+ prefix[prevNonWhitespaceIndex] !== "(") {
112
+ prefix = "".concat(prefix.slice(0, prevNonWhitespaceIndex + 1), ",").concat(prefix.slice(prevNonWhitespaceIndex + 1));
97
113
  }
98
114
  var jsBundleLine = "".concat(paramIndent, "jsBundleFilePath = HotUpdater.getJSBundleFile(applicationContext),");
99
- lines.splice(closingIndex, 0, jsBundleLine);
100
- return lines.join("\n");
115
+ return "".concat(prefix).concat(jsBundleLine, "\n").concat(suffix);
101
116
  }
102
117
  /**
103
118
  * Android: DefaultReactNativeHost pattern (RN 0.81 / Expo 54).