react-native-update 10.38.4 → 10.39.0-beta.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,13 @@
1
+ arguments=--init-script /var/folders/l6/0fn3x28s5s585ld3p04gsy1h0000gn/T/db3b08fc4a9ef609cb16b96b200fa13e563f396e9bb1ed0905fdab7bc3bc513b.gradle --init-script /var/folders/l6/0fn3x28s5s585ld3p04gsy1h0000gn/T/52cde0cfcf3e28b8b7510e992210d9614505e0911af0c190bd590d7158574963.gradle
2
+ auto.sync=false
3
+ build.scans.enabled=false
4
+ connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(8.9))
5
+ connection.project.dir=
6
+ eclipse.preferences.version=1
7
+ gradle.user.home=
8
+ java.home=/Users/sunny/.sdkman/candidates/java/17.0.9-zulu/zulu-17.jdk/Contents/Home
9
+ jvm.arguments=
10
+ offline.mode=false
11
+ override.workspace.settings=true
12
+ show.console.view=true
13
+ show.executions.view=true
@@ -29,6 +29,8 @@ import java.util.Enumeration;
29
29
  import java.util.Iterator;
30
30
  import java.util.zip.ZipEntry;
31
31
  import java.util.HashMap;
32
+ import java.util.regex.Matcher;
33
+ import java.util.regex.Pattern;
32
34
 
33
35
  import okio.BufferedSink;
34
36
  import okio.BufferedSource;
@@ -285,7 +287,16 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
285
287
  }
286
288
  }
287
289
 
288
- private String findDrawableFallback(String originalToPath, HashMap<String, String> copiesMap, HashMap<String, ZipEntry> availableEntries) {
290
+ // Pattern to strip -vN version qualifiers from resource directory paths
291
+ // e.g., "res/drawable-xxhdpi-v4/img.png" → "res/drawable-xxhdpi/img.png"
292
+ private static final Pattern VERSION_QUALIFIER_PATTERN =
293
+ Pattern.compile("-v\\d+(?=/)");
294
+
295
+ private String normalizeResPath(String path) {
296
+ return VERSION_QUALIFIER_PATTERN.matcher(path).replaceAll("");
297
+ }
298
+
299
+ private String findDrawableFallback(String originalToPath, HashMap<String, String> copiesMap, HashMap<String, ZipEntry> availableEntries, HashMap<String, String> normalizedEntryMap) {
289
300
  // 检查是否是 drawable 路径
290
301
  if (!originalToPath.contains("drawable")) {
291
302
  return null;
@@ -309,13 +320,22 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
309
320
  // 检查这个 key 是否在 copies 映射中
310
321
  if (copiesMap.containsKey(fallbackToPath)) {
311
322
  String fallbackFromPath = copiesMap.get(fallbackToPath);
312
- // 检查对应的 value 路径是否在 APK 中存在
323
+ // 检查对应的 value 路径是否在 APK 中存在(精确匹配)
313
324
  if (availableEntries.containsKey(fallbackFromPath)) {
314
325
  if (UpdateContext.DEBUG) {
315
326
  Log.d("react-native-update", "Found fallback for " + originalToPath + ": " + fallbackToPath + " -> " + fallbackFromPath);
316
327
  }
317
328
  return fallbackFromPath;
318
329
  }
330
+ // 尝试版本限定符无关匹配(APK ↔ AAB 兼容)
331
+ String normalizedFallback = normalizeResPath(fallbackFromPath);
332
+ String actualEntry = normalizedEntryMap.get(normalizedFallback);
333
+ if (actualEntry != null) {
334
+ if (UpdateContext.DEBUG) {
335
+ Log.d("react-native-update", "Found normalized fallback for " + originalToPath + ": " + fallbackToPath + " -> " + actualEntry);
336
+ }
337
+ return actualEntry;
338
+ }
319
339
  }
320
340
  }
321
341
 
@@ -369,6 +389,14 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
369
389
  }
370
390
  }
371
391
 
392
+ // 构建规范化路径映射,用于 APK ↔ AAB 版本限定符无关匹配
393
+ // 例如 "res/drawable-xxhdpi-v4/img.png" → "res/drawable-xxhdpi/img.png"
394
+ HashMap<String, String> normalizedEntryMap = new HashMap<>();
395
+ for (String entryName : availableEntries.keySet()) {
396
+ String normalized = normalizeResPath(entryName);
397
+ normalizedEntryMap.putIfAbsent(normalized, entryName);
398
+ }
399
+
372
400
  // 使用基础 APK 的 ZipFile 作为主要操作对象
373
401
  SafeZipFile zipFile = zipFileMap.get(context.getPackageResourcePath());
374
402
 
@@ -387,7 +415,21 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
387
415
  ZipEntry ze = availableEntries.get(fromPath);
388
416
  String actualSourcePath = fromPath;
389
417
 
390
- // 如果文件不存在,尝试 fallback
418
+ // 如果精确匹配找不到,尝试版本限定符无关匹配(APK ↔ AAB 兼容)
419
+ // 例如 __diff.json 中的 "res/drawable-xxhdpi-v4/img.png" 匹配设备上的 "res/drawable-xxhdpi/img.png"
420
+ if (ze == null) {
421
+ String normalizedFrom = normalizeResPath(fromPath);
422
+ String actualEntry = normalizedEntryMap.get(normalizedFrom);
423
+ if (actualEntry != null) {
424
+ ze = availableEntries.get(actualEntry);
425
+ actualSourcePath = actualEntry;
426
+ if (UpdateContext.DEBUG) {
427
+ Log.d("react-native-update", "Normalized match: " + fromPath + " -> " + actualEntry);
428
+ }
429
+ }
430
+ }
431
+
432
+ // 如果仍然找不到,尝试 drawable 密度降级 fallback
391
433
  if (ze == null) {
392
434
  if (UpdateContext.DEBUG) {
393
435
  Log.d("react-native-update", "File not found in APK: " + fromPath + ", trying fallback");
@@ -405,28 +447,10 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
405
447
  if (UpdateContext.DEBUG) {
406
448
  Log.d("react-native-update", "Found toPath: " + toPath + " for fromPath: " + fromPath);
407
449
  }
408
- String fallbackFromPath = findDrawableFallback(toPath, copiesMap, availableEntries);
450
+ String fallbackFromPath = findDrawableFallback(toPath, copiesMap, availableEntries, normalizedEntryMap);
409
451
  if (fallbackFromPath != null) {
410
452
  ze = availableEntries.get(fallbackFromPath);
411
453
  actualSourcePath = fallbackFromPath;
412
- // 确保 fallback 路径也在 entryToZipFileMap 中
413
- if (!entryToZipFileMap.containsKey(fallbackFromPath)) {
414
- // 查找包含该 fallback 路径的 ZipFile
415
- for (String apkPath : apkPaths) {
416
- SafeZipFile testZipFile = zipFileMap.get(apkPath);
417
- if (testZipFile != null) {
418
- try {
419
- ZipEntry testEntry = testZipFile.getEntry(fallbackFromPath);
420
- if (testEntry != null) {
421
- entryToZipFileMap.put(fallbackFromPath, testZipFile);
422
- break;
423
- }
424
- } catch (Exception e) {
425
- // 继续查找
426
- }
427
- }
428
- }
429
- }
430
454
  if (UpdateContext.DEBUG) {
431
455
  Log.w("react-native-update", "Using fallback: " + fallbackFromPath + " for " + fromPath);
432
456
  }
package/harmony/pushy.har CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-update",
3
- "version": "10.38.4",
3
+ "version": "10.39.0-beta.0",
4
4
  "description": "react-native hot update",
5
5
  "main": "src/index",
6
6
  "scripts": {
@@ -78,4 +78,4 @@
78
78
  "ts-jest": "^29.4.6",
79
79
  "typescript": "^5.6.3"
80
80
  }
81
- }
81
+ }
@@ -91,9 +91,8 @@ Pod::Spec.new do |s|
91
91
  s.source = { :git => 'https://github.com/reactnativecn/react-native-update.git', :tag => '#{s.version}' }
92
92
 
93
93
  s.libraries = 'bz2', 'z'
94
- s.vendored_libraries = 'RCTPushy/libRCTPushy.a'
95
94
  s.pod_target_xcconfig = {
96
- 'USER_HEADER_SEARCH_PATHS' => "#{podspec_dir}/ios",
95
+ 'USER_HEADER_SEARCH_PATHS' => "#{podspec_dir}/ios \"$(PODS_ROOT)/Headers/Public/SSZipArchive\" \"$(PODS_ROOT)/Headers/Public/React-Codegen/RCTPushySpec\"",
97
96
  "DEFINES_MODULE" => "YES"
98
97
  }
99
98
  s.resource = 'ios/pushy_build_time.txt'
package/src/client.ts CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  ClientOptions,
23
23
  EventType,
24
24
  ProgressData,
25
+ UpdateCheckState,
25
26
  UpdateServerConfig,
26
27
  } from './type';
27
28
  import {
@@ -207,6 +208,16 @@ export class Pushy {
207
208
  throw e;
208
209
  }
209
210
  };
211
+ notifyAfterCheckUpdate = (state: UpdateCheckState) => {
212
+ const { afterCheckUpdate } = this.options;
213
+ if (!afterCheckUpdate) {
214
+ return;
215
+ }
216
+ // 这里仅做状态通知,不阻塞原有检查流程
217
+ Promise.resolve(afterCheckUpdate(state)).catch((error: any) => {
218
+ log('afterCheckUpdate failed:', error?.message || error);
219
+ });
220
+ };
210
221
  getCheckUrl = (endpoint: string) => {
211
222
  return `${endpoint}/checkUpdate/${this.options.appKey}`;
212
223
  };
@@ -329,9 +340,11 @@ export class Pushy {
329
340
  };
330
341
  checkUpdate = async (extra?: Record<string, any>) => {
331
342
  if (!this.assertDebug('checkUpdate()')) {
343
+ this.notifyAfterCheckUpdate({ status: 'skipped' });
332
344
  return;
333
345
  }
334
346
  if (!assertWeb()) {
347
+ this.notifyAfterCheckUpdate({ status: 'skipped' });
335
348
  return;
336
349
  }
337
350
  if (
@@ -339,6 +352,7 @@ export class Pushy {
339
352
  (await this.options.beforeCheckUpdate()) === false
340
353
  ) {
341
354
  log('beforeCheckUpdate returned false, skipping check');
355
+ this.notifyAfterCheckUpdate({ status: 'skipped' });
342
356
  return;
343
357
  }
344
358
  const now = Date.now();
@@ -347,7 +361,9 @@ export class Pushy {
347
361
  this.lastChecking &&
348
362
  now - this.lastChecking < 1000 * 5
349
363
  ) {
350
- return await this.lastRespJson;
364
+ const result = await this.lastRespJson;
365
+ this.notifyAfterCheckUpdate({ status: 'completed', result });
366
+ return result;
351
367
  }
352
368
  this.lastChecking = now;
353
369
  const fetchBody = {
@@ -387,6 +403,7 @@ export class Pushy {
387
403
 
388
404
  log('checking result:', result);
389
405
 
406
+ this.notifyAfterCheckUpdate({ status: 'completed', result });
390
407
  return result;
391
408
  } catch (e: any) {
392
409
  this.lastRespJson = previousRespJson;
@@ -396,6 +413,7 @@ export class Pushy {
396
413
  type: 'errorChecking',
397
414
  message: errorMessage,
398
415
  });
416
+ this.notifyAfterCheckUpdate({ status: 'error', error: e });
399
417
  this.throwIfEnabled(e);
400
418
  return previousRespJson ? await previousRespJson : emptyObj;
401
419
  }
package/src/provider.tsx CHANGED
@@ -165,6 +165,7 @@ export const UpdateProvider = ({
165
165
  async ({ extra }: { extra?: Partial<{ toHash: string }> } = {}) => {
166
166
  const now = Date.now();
167
167
  if (lastChecking.current && now - lastChecking.current < 1000) {
168
+ client.notifyAfterCheckUpdate({ status: 'skipped' });
168
169
  return;
169
170
  }
170
171
  lastChecking.current = now;
package/src/type.ts CHANGED
@@ -35,6 +35,13 @@ export interface ProgressData {
35
35
  total: number;
36
36
  }
37
37
 
38
+ // 用于描述一次检查结束后的最终状态,便于业务侧感知成功、跳过或失败
39
+ export interface UpdateCheckState {
40
+ status: 'completed' | 'skipped' | 'error';
41
+ result?: CheckResult;
42
+ error?: Error;
43
+ }
44
+
38
45
  export type EventType =
39
46
  | 'rollback'
40
47
  | 'errorChecking'
@@ -98,6 +105,8 @@ export interface ClientOptions {
98
105
  debug?: boolean;
99
106
  throwError?: boolean;
100
107
  beforeCheckUpdate?: () => Promise<boolean> | boolean;
108
+ // 每次检查结束后都会触发,不影响原有检查流程
109
+ afterCheckUpdate?: (state: UpdateCheckState) => Promise<void> | void;
101
110
  beforeDownloadUpdate?: (info: CheckResult) => Promise<boolean> | boolean;
102
111
  afterDownloadUpdate?: (info: CheckResult) => Promise<boolean> | boolean;
103
112
  onPackageExpired?: (info: CheckResult) => Promise<boolean> | boolean;