react-native-update 10.28.1 → 10.28.3-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.
@@ -1,10 +1,40 @@
1
1
  import ExpoModulesCore
2
- import react_native_update
2
+ import React
3
3
 
4
4
  public final class ExpoPushyReactDelegateHandler: ExpoReactDelegateHandler {
5
- private weak var reactDelegate: ExpoReactDelegate?
6
5
 
7
- public override func bundleURL(reactDelegate: ExpoReactDelegate) -> URL? {
8
- return RCTPushy.bundleURL()
9
- }
6
+ #if EXPO_SUPPORTS_BUNDLEURL
7
+ // This code block compiles only if EXPO_SUPPORTS_BUNDLEURL is defined
8
+ // For expo-modules-core >= 1.12.0
9
+
10
+ // Override bundleURL, which is the primary mechanism for these versions.
11
+ // Expo's default createBridge implementation should respect this.
12
+ override public func bundleURL(reactDelegate: ExpoReactDelegate) -> URL? {
13
+ let bundleURL = RCTPushy.bundleURL()
14
+ print("PushyHandler: Using bundleURL: \(bundleURL?.absoluteString ?? "nil")")
15
+ return bundleURL
16
+ }
17
+
18
+ // No createBridge override needed here, rely on default behavior using the bundleURL override.
19
+
20
+ #else
21
+ // This code block compiles only if EXPO_SUPPORTS_BUNDLEURL is NOT defined
22
+ // For expo-modules-core < 1.12.0
23
+
24
+ // No bundleURL override possible here.
25
+
26
+ // createBridge is the mechanism to customize the URL here.
27
+ // We completely override it and do not call super.
28
+ override public func createBridge(reactDelegate: ExpoReactDelegate, bridgeDelegate: RCTBridgeDelegate, launchOptions: [AnyHashable: Any]?) -> RCTBridge? {
29
+ let bundleURL = RCTPushy.bundleURL()
30
+ // Print the URL being provided to the initializer
31
+ print("PushyHandler: createBridge bundleURL: \(bundleURL?.absoluteString ?? "nil")")
32
+
33
+ // Directly create the bridge using the bundleURL initializer.
34
+ // Pass nil for moduleProvider, assuming default behavior is sufficient.
35
+ // WARNING: If bundleURL is nil, this initialization might fail silently or crash.
36
+ return RCTBridge(bundleURL: bundleURL, moduleProvider: nil, launchOptions: launchOptions)
37
+ }
38
+
39
+ #endif // EXPO_SUPPORTS_BUNDLEURL
10
40
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-update",
3
- "version": "10.28.1",
3
+ "version": "10.28.3-beta.0",
4
4
  "description": "react-native hot update",
5
5
  "main": "src/index",
6
6
  "scripts": {
@@ -1,4 +1,5 @@
1
1
  require 'json'
2
+ require 'rubygems' # Required for version comparison
2
3
 
3
4
  new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
4
5
 
@@ -33,17 +34,45 @@ Pod::Spec.new do |s|
33
34
  s.dependency "React-Core"
34
35
  s.dependency 'SSZipArchive'
35
36
 
36
- project_root = File.expand_path('../../', __dir__)
37
- project_package_json = File.join(project_root, 'package.json')
38
37
  is_expo_project = false
38
+ expo_dependency_added = false
39
+ supports_bundle_url_final = false
39
40
 
40
- if (File.exist?(project_package_json))
41
- package_json = JSON.parse(File.read(project_package_json))
42
- has_expo_dependency = package_json['dependencies'] && package_json['dependencies']['expo']
43
- has_expo_modules_core = Dir.exist?('node_modules/expo-modules-core')
44
- is_expo_project = has_expo_dependency || has_expo_modules_core
45
- if is_expo_project
46
- s.dependency 'ExpoModulesCore'
41
+ # Use CocoaPods mechanism to find Podfile
42
+ begin
43
+ podfile_path = File.join(Pod::Config.instance.installation_root, 'Podfile')
44
+ if File.exist?(podfile_path)
45
+ podfile_content = File.read(podfile_path)
46
+ is_expo_project = podfile_content.include?('use_expo_modules!')
47
+ end
48
+ rescue
49
+ # Silently skip if CocoaPods config is not available
50
+ end
51
+
52
+ if is_expo_project
53
+ s.dependency 'ExpoModulesCore'
54
+ expo_dependency_added = true
55
+
56
+ # Current directory is in node_modules/react-native-update, so parent is node_modules
57
+ expo_core_package_json_path = File.join(__dir__, '..', 'expo-modules-core', 'package.json')
58
+
59
+ if File.exist?(expo_core_package_json_path)
60
+ begin
61
+ core_package_json = JSON.parse(File.read(expo_core_package_json_path))
62
+ installed_version_str = core_package_json['version']
63
+
64
+ if installed_version_str
65
+ begin
66
+ installed_version = Gem::Version.new(installed_version_str)
67
+ target_version = Gem::Version.new('1.12.0')
68
+ supports_bundle_url_final = installed_version >= target_version
69
+ rescue ArgumentError
70
+ # Silently skip version parsing errors
71
+ end
72
+ end
73
+ rescue JSON::ParserError, StandardError
74
+ # Silently skip file reading and parsing errors
75
+ end
47
76
  end
48
77
  end
49
78
 
@@ -62,9 +91,12 @@ Pod::Spec.new do |s|
62
91
  ss.public_header_files = 'ios/RCTPushy/HDiffPatch/**/*.h'
63
92
  end
64
93
 
65
- if is_expo_project
94
+ if expo_dependency_added
66
95
  s.subspec 'Expo' do |ss|
67
96
  ss.source_files = 'ios/Expo/**/*.{h,m,mm,swift}'
97
+ if supports_bundle_url_final
98
+ ss.pod_target_xcconfig = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'EXPO_SUPPORTS_BUNDLEURL' }
99
+ end
68
100
  end
69
101
  end
70
102
 
@@ -79,7 +111,7 @@ Pod::Spec.new do |s|
79
111
  s.pod_target_xcconfig = {
80
112
  "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
81
113
  "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
82
- }
114
+ }.merge(s.pod_target_xcconfig)
83
115
  s.dependency "React-Codegen"
84
116
  s.dependency "RCT-Folly"
85
117
  s.dependency "RCTRequired"
package/src/client.ts CHANGED
@@ -61,6 +61,31 @@ const defaultClientOptions: ClientOptions = {
61
61
  throwError: false,
62
62
  };
63
63
 
64
+ export const sharedState: {
65
+ progressHandlers: Record<string, EmitterSubscription>;
66
+ downloadedHash?: string;
67
+ apkStatus: 'downloading' | 'downloaded' | null;
68
+ marked: boolean;
69
+ applyingUpdate: boolean;
70
+ } = {
71
+ progressHandlers: {},
72
+ downloadedHash: undefined,
73
+ apkStatus: null,
74
+ marked: false,
75
+ applyingUpdate: false,
76
+ };
77
+
78
+ const assertHash = (hash: string) => {
79
+ if (!sharedState.downloadedHash) {
80
+ return;
81
+ }
82
+ if (hash !== sharedState.downloadedHash) {
83
+ log(`use downloaded hash ${sharedState.downloadedHash} first`);
84
+ return;
85
+ }
86
+ return true;
87
+ };
88
+
64
89
  // for China users
65
90
  export class Pushy {
66
91
  options = defaultClientOptions;
@@ -68,13 +93,6 @@ export class Pushy {
68
93
  lastChecking?: number;
69
94
  lastRespJson?: Promise<any>;
70
95
 
71
- static progressHandlers: Record<string, EmitterSubscription> = {};
72
- static downloadedHash?: string;
73
-
74
- static apkStatus: 'downloading' | 'downloaded' | null = null;
75
-
76
- static marked = false;
77
- static applyingUpdate = false;
78
96
  version = cInfo.rnu;
79
97
  loggerPromise = (() => {
80
98
  let resolve: (value?: unknown) => void = () => {};
@@ -152,16 +170,6 @@ export class Pushy {
152
170
  getCheckUrl = (endpoint: string = this.options.server!.main) => {
153
171
  return `${endpoint}/checkUpdate/${this.options.appKey}`;
154
172
  };
155
- static assertHash = (hash: string) => {
156
- if (!this.downloadedHash) {
157
- return;
158
- }
159
- if (hash !== this.downloadedHash) {
160
- log(`use downloaded hash ${Pushy.downloadedHash} first`);
161
- return;
162
- }
163
- return true;
164
- };
165
173
  assertDebug = () => {
166
174
  if (__DEV__ && !this.options.debug) {
167
175
  console.info(
@@ -172,10 +180,10 @@ export class Pushy {
172
180
  return true;
173
181
  };
174
182
  markSuccess = () => {
175
- if (Pushy.marked || __DEV__ || !isFirstTime) {
183
+ if (sharedState.marked || __DEV__ || !isFirstTime) {
176
184
  return;
177
185
  }
178
- Pushy.marked = true;
186
+ sharedState.marked = true;
179
187
  PushyModule.markSuccess();
180
188
  this.report({ type: 'markSuccess' });
181
189
  };
@@ -183,9 +191,9 @@ export class Pushy {
183
191
  if (!assertDev('switchVersion()')) {
184
192
  return;
185
193
  }
186
- if (Pushy.assertHash(hash) && !Pushy.applyingUpdate) {
194
+ if (assertHash(hash) && !sharedState.applyingUpdate) {
187
195
  log('switchVersion: ' + hash);
188
- Pushy.applyingUpdate = true;
196
+ sharedState.applyingUpdate = true;
189
197
  return PushyModule.reloadUpdate({ hash });
190
198
  }
191
199
  };
@@ -194,7 +202,7 @@ export class Pushy {
194
202
  if (!assertDev('switchVersionLater()')) {
195
203
  return;
196
204
  }
197
- if (Pushy.assertHash(hash)) {
205
+ if (assertHash(hash)) {
198
206
  log('switchVersionLater: ' + hash);
199
207
  return PushyModule.setNeedUpdate({ hash });
200
208
  }
@@ -349,18 +357,18 @@ export class Pushy {
349
357
  log(`rolledback hash ${rolledBackVersion}, ignored`);
350
358
  return;
351
359
  }
352
- if (Pushy.downloadedHash === hash) {
353
- log(`duplicated downloaded hash ${Pushy.downloadedHash}, ignored`);
354
- return Pushy.downloadedHash;
360
+ if (sharedState.downloadedHash === hash) {
361
+ log(`duplicated downloaded hash ${sharedState.downloadedHash}, ignored`);
362
+ return sharedState.downloadedHash;
355
363
  }
356
- if (Pushy.progressHandlers[hash]) {
364
+ if (sharedState.progressHandlers[hash]) {
357
365
  return;
358
366
  }
359
367
  const patchStartTime = Date.now();
360
368
  if (onDownloadProgress) {
361
369
  // @ts-expect-error harmony not in existing platforms
362
370
  if (Platform.OS === 'harmony') {
363
- Pushy.progressHandlers[hash] = DeviceEventEmitter.addListener(
371
+ sharedState.progressHandlers[hash] = DeviceEventEmitter.addListener(
364
372
  'RCTPushyDownloadProgress',
365
373
  progressData => {
366
374
  if (progressData.hash === hash) {
@@ -369,14 +377,15 @@ export class Pushy {
369
377
  },
370
378
  );
371
379
  } else {
372
- Pushy.progressHandlers[hash] = pushyNativeEventEmitter.addListener(
373
- 'RCTPushyDownloadProgress',
374
- progressData => {
375
- if (progressData.hash === hash) {
376
- onDownloadProgress(progressData);
377
- }
378
- },
379
- );
380
+ sharedState.progressHandlers[hash] =
381
+ pushyNativeEventEmitter.addListener(
382
+ 'RCTPushyDownloadProgress',
383
+ progressData => {
384
+ if (progressData.hash === hash) {
385
+ onDownloadProgress(progressData);
386
+ }
387
+ },
388
+ );
380
389
  }
381
390
  }
382
391
  let succeeded = '';
@@ -444,9 +453,9 @@ export class Pushy {
444
453
  }
445
454
  }
446
455
  }
447
- if (Pushy.progressHandlers[hash]) {
448
- Pushy.progressHandlers[hash].remove();
449
- delete Pushy.progressHandlers[hash];
456
+ if (sharedState.progressHandlers[hash]) {
457
+ sharedState.progressHandlers[hash].remove();
458
+ delete sharedState.progressHandlers[hash];
450
459
  }
451
460
  if (__DEV__) {
452
461
  return hash;
@@ -482,7 +491,7 @@ export class Pushy {
482
491
  description,
483
492
  metaInfo,
484
493
  });
485
- Pushy.downloadedHash = hash;
494
+ sharedState.downloadedHash = hash;
486
495
  return hash;
487
496
  };
488
497
  downloadAndInstallApk = async (
@@ -492,10 +501,10 @@ export class Pushy {
492
501
  if (Platform.OS !== 'android') {
493
502
  return;
494
503
  }
495
- if (Pushy.apkStatus === 'downloading') {
504
+ if (sharedState.apkStatus === 'downloading') {
496
505
  return;
497
506
  }
498
- if (Pushy.apkStatus === 'downloaded') {
507
+ if (sharedState.apkStatus === 'downloaded') {
499
508
  this.report({ type: 'errorInstallApk' });
500
509
  this.throwIfEnabled(new Error('errorInstallApk'));
501
510
  return;
@@ -516,35 +525,36 @@ export class Pushy {
516
525
  return;
517
526
  }
518
527
  }
519
- Pushy.apkStatus = 'downloading';
528
+ sharedState.apkStatus = 'downloading';
520
529
  this.report({ type: 'downloadingApk' });
521
530
  const progressKey = 'downloadingApk';
522
531
  if (onDownloadProgress) {
523
- if (Pushy.progressHandlers[progressKey]) {
524
- Pushy.progressHandlers[progressKey].remove();
532
+ if (sharedState.progressHandlers[progressKey]) {
533
+ sharedState.progressHandlers[progressKey].remove();
525
534
  }
526
- Pushy.progressHandlers[progressKey] = pushyNativeEventEmitter.addListener(
527
- 'RCTPushyDownloadProgress',
528
- (progressData: ProgressData) => {
529
- if (progressData.hash === progressKey) {
530
- onDownloadProgress(progressData);
531
- }
532
- },
533
- );
535
+ sharedState.progressHandlers[progressKey] =
536
+ pushyNativeEventEmitter.addListener(
537
+ 'RCTPushyDownloadProgress',
538
+ (progressData: ProgressData) => {
539
+ if (progressData.hash === progressKey) {
540
+ onDownloadProgress(progressData);
541
+ }
542
+ },
543
+ );
534
544
  }
535
545
  await PushyModule.downloadAndInstallApk({
536
546
  url,
537
547
  target: 'update.apk',
538
548
  hash: progressKey,
539
549
  }).catch(() => {
540
- Pushy.apkStatus = null;
550
+ sharedState.apkStatus = null;
541
551
  this.report({ type: 'errorDownloadAndInstallApk' });
542
552
  this.throwIfEnabled(new Error('errorDownloadAndInstallApk'));
543
553
  });
544
- Pushy.apkStatus = 'downloaded';
545
- if (Pushy.progressHandlers[progressKey]) {
546
- Pushy.progressHandlers[progressKey].remove();
547
- delete Pushy.progressHandlers[progressKey];
554
+ sharedState.apkStatus = 'downloaded';
555
+ if (sharedState.progressHandlers[progressKey]) {
556
+ sharedState.progressHandlers[progressKey].remove();
557
+ delete sharedState.progressHandlers[progressKey];
548
558
  }
549
559
  };
550
560
  restartApp = async () => {
package/src/provider.tsx CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  Platform,
13
13
  Linking,
14
14
  } from 'react-native';
15
- import { Pushy, Cresc } from './client';
15
+ import { Pushy, Cresc, sharedState } from './client';
16
16
  import { currentVersion, packageVersion, getCurrentVersionInfo } from './core';
17
17
  import { CheckResult, ProgressData, UpdateTestPayload } from './type';
18
18
  import { UpdateContext } from './context';
@@ -171,7 +171,7 @@ export const UpdateProvider = ({
171
171
  return;
172
172
  }
173
173
  const rollout = info.config?.rollout?.[packageVersion];
174
- if (rollout) {
174
+ if (info.update && rollout) {
175
175
  if (!isInRollout(rollout)) {
176
176
  log(`not in ${rollout}% rollout, ignored`);
177
177
  return;
@@ -182,8 +182,15 @@ export const UpdateProvider = ({
182
182
  updateInfoRef.current = info;
183
183
  setUpdateInfo(info);
184
184
  if (info.expired) {
185
+ if (
186
+ options.onPackageExpired &&
187
+ (await options.onPackageExpired(info)) === false
188
+ ) {
189
+ log('onPackageExpired returned false, skipping');
190
+ return;
191
+ }
185
192
  const { downloadUrl } = info;
186
- if (downloadUrl && Pushy.apkStatus === null) {
193
+ if (downloadUrl && sharedState.apkStatus === null) {
187
194
  if (options.updateStrategy === 'silentAndNow') {
188
195
  if (Platform.OS === 'android' && downloadUrl.endsWith('.apk')) {
189
196
  downloadAndInstallApk(downloadUrl);
@@ -234,7 +241,7 @@ export const UpdateProvider = ({
234
241
  client,
235
242
  alertError,
236
243
  throwErrorIfEnabled,
237
- options.updateStrategy,
244
+ options,
238
245
  alertUpdate,
239
246
  downloadAndInstallApk,
240
247
  downloadUpdate,
package/src/type.ts CHANGED
@@ -92,6 +92,7 @@ export interface ClientOptions {
92
92
  beforeCheckUpdate?: () => Promise<boolean>;
93
93
  beforeDownloadUpdate?: (info: CheckResult) => Promise<boolean>;
94
94
  afterDownloadUpdate?: (info: CheckResult) => Promise<boolean>;
95
+ onPackageExpired?: (info: CheckResult) => Promise<boolean>;
95
96
  }
96
97
 
97
98
  export interface UpdateTestPayload {
package/src/utils.ts CHANGED
@@ -62,7 +62,7 @@ const ping =
62
62
  if (!pingFinished) {
63
63
  log('ping timeout', url);
64
64
  }
65
- }, 2000),
65
+ }, 5000),
66
66
  ),
67
67
  ]);
68
68
  };
@@ -81,6 +81,7 @@ export const testUrls = async (urls?: string[]) => {
81
81
  try {
82
82
  const ret = await promiseAny(urls.map(ping));
83
83
  if (ret) {
84
+ log('ping success, use url:', ret);
84
85
  return ret;
85
86
  }
86
87
  } catch {}