react-native-nitro-version-check 1.0.2 → 1.1.1

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.
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
  ## Example
16
16
 
17
17
  ```ts
18
- import { VersionCheck, needsUpdate, getStoreUrl } from 'react-native-nitro-version-check'
18
+ import { VersionCheck } from 'react-native-nitro-version-check'
19
19
 
20
20
  // Sync — no bridge, no async
21
21
  VersionCheck.version // "1.2.0"
@@ -23,9 +23,12 @@ VersionCheck.buildNumber // "42"
23
23
  VersionCheck.packageName // "com.example.app"
24
24
  VersionCheck.installSource // "appstore" | "testflight" | "playstore" | undefined
25
25
 
26
+ // Or destructure properties
27
+ const { version, buildNumber, packageName, installSource } = VersionCheck
28
+
26
29
  // Check for updates
27
- if (await needsUpdate()) {
28
- Linking.openURL(await getStoreUrl())
30
+ if (await VersionCheck.needsUpdate()) {
31
+ Linking.openURL(await VersionCheck.getStoreUrl())
29
32
  }
30
33
  ```
31
34
 
@@ -35,6 +38,8 @@ if (await needsUpdate()) {
35
38
  bun add react-native-nitro-version-check
36
39
  ```
37
40
 
41
+ > Check the [full installation guide](https://alshehriali0.github.io/react-native-nitro-version-check/docs/installation) for platform setup and additional dependencies.
42
+
38
43
  ## Documentation
39
44
 
40
45
  - [**Nitro Version Check** docs 📚](https://alshehriali0.github.io/react-native-nitro-version-check/)
@@ -36,15 +36,17 @@ class HybridVersionCheck : HybridVersionCheckSpec() {
36
36
  return java.util.Locale.getDefault().country ?: "unknown"
37
37
  }
38
38
 
39
- override fun getStoreUrl(): Promise<String> {
39
+ override fun getStoreUrl(countryCode: String?): Promise<String> {
40
40
  return Promise.async {
41
+ // Country code is not used on Android Play Store, but parameter is kept for API consistency
41
42
  "https://play.google.com/store/apps/details?id=$packageName"
42
43
  }
43
44
  }
44
45
 
45
- override fun getLatestVersion(): Promise<String> {
46
+ override fun getLatestVersion(countryCode: String?): Promise<String> {
46
47
  return Promise.async {
47
48
  try {
49
+ // Country code is not used on Android Play Store, but parameter is kept for API consistency
48
50
  val url = URL("https://play.google.com/store/apps/details?id=$packageName&hl=en")
49
51
  val connection = url.openConnection() as HttpURLConnection
50
52
  connection.connectTimeout = TIMEOUT_MS
@@ -30,10 +30,12 @@ class HybridVersionCheck: HybridVersionCheckSpec {
30
30
  return Locale.current.regionCode ?? "unknown"
31
31
  }
32
32
 
33
- func getStoreUrl() throws -> Promise<String> {
34
- return Promise.async {
33
+ func getStoreUrl(countryCode: String? = nil) throws -> Promise<String> {
34
+ return Promise.async { [self] in
35
35
  let bundleId = Bundle.main.bundleIdentifier ?? ""
36
- let url = URL(string: "https://itunes.apple.com/lookup?bundleId=\(bundleId)")!
36
+ let country = countryCode ?? (try? self.getCountry()) ?? "US"
37
+ let urlString = "https://itunes.apple.com/\(country.lowercased())/lookup?bundleId=\(bundleId)"
38
+ let url = URL(string: urlString)!
37
39
  let (data, _) = try await HybridVersionCheck.session.data(from: url)
38
40
  guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
39
41
  let results = json["results"] as? [[String: Any]],
@@ -44,10 +46,12 @@ class HybridVersionCheck: HybridVersionCheckSpec {
44
46
  }
45
47
  }
46
48
 
47
- func getLatestVersion() throws -> Promise<String> {
48
- return Promise.async {
49
+ func getLatestVersion(countryCode: String? = nil) throws -> Promise<String> {
50
+ return Promise.async { [self] in
49
51
  let bundleId = Bundle.main.bundleIdentifier ?? ""
50
- let url = URL(string: "https://itunes.apple.com/lookup?bundleId=\(bundleId)")!
52
+ let country = countryCode ?? (try? self.getCountry()) ?? "US"
53
+ let urlString = "https://itunes.apple.com/\(country.lowercased())/lookup?bundleId=\(bundleId)"
54
+ let url = URL(string: urlString)!
51
55
  let (data, _) = try await HybridVersionCheck.session.data(from: url)
52
56
  guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
53
57
  let results = json["results"] as? [[String: Any]],
package/lib/index.d.ts CHANGED
@@ -1,62 +1,11 @@
1
1
  import type { UpdateLevel } from "./semver";
2
2
  import { compareVersions } from "./semver";
3
- /**
4
- * Returns the device's current 2-letter ISO country code.
5
- *
6
- * @example
7
- * ```ts
8
- * getCountry() // "US"
9
- * ```
10
- */
11
- export declare const getCountry: () => string;
12
- /**
13
- * Returns the store URL for this app.
14
- *
15
- * Automatically resolves to the App Store on iOS and Play Store on Android.
16
- *
17
- * @example
18
- * ```ts
19
- * const url = await getStoreUrl();
20
- * Linking.openURL(url);
21
- * ```
22
- */
23
- export declare const getStoreUrl: () => Promise<string>;
24
- /**
25
- * Fetches the latest version of this app available in the store.
26
- *
27
- * @example
28
- * ```ts
29
- * const latest = await getLatestVersion(); // "1.3.0"
30
- * ```
31
- */
32
- export declare const getLatestVersion: () => Promise<string>;
33
- /**
34
- * Checks whether an app update is available.
35
- *
36
- * Uses semantic version comparison. By default checks for any version
37
- * increase, but you can filter by granularity:
38
- *
39
- * - `"major"` — only returns `true` for major bumps (1.x → 2.x)
40
- * - `"minor"` — returns `true` for major or minor bumps
41
- * - `"patch"` — returns `true` for any version increase (default)
42
- *
43
- * @example
44
- * ```ts
45
- * if (await needsUpdate()) {
46
- * const url = await getStoreUrl();
47
- * Linking.openURL(url);
48
- * }
49
- *
50
- * // Only prompt for major updates
51
- * const majorUpdate = await needsUpdate({ level: "major" });
52
- * ```
53
- */
54
- export declare const needsUpdate: (options?: {
55
- level?: UpdateLevel;
56
- }) => Promise<boolean>;
57
3
  /**
58
4
  * All version-check APIs in one object.
59
5
  *
6
+ * Provides access to app version information, store URLs, and update checking.
7
+ * Sync properties are cached at module init for zero native overhead.
8
+ *
60
9
  * @example
61
10
  * ```ts
62
11
  * VersionCheck.version // "1.2.0"
@@ -65,6 +14,9 @@ export declare const needsUpdate: (options?: {
65
14
  * VersionCheck.getCountry() // "US"
66
15
  *
67
16
  * const url = await VersionCheck.getStoreUrl();
17
+ * if (await VersionCheck.needsUpdate()) {
18
+ * Linking.openURL(url);
19
+ * }
68
20
  * ```
69
21
  */
70
22
  export declare const VersionCheck: {
@@ -132,37 +84,84 @@ export declare const VersionCheck: {
132
84
  /**
133
85
  * Returns the App Store (iOS) or Play Store (Android) URL for this app.
134
86
  *
87
+ * @param options - Optional configuration
88
+ * @param options.countryCode - 2-letter ISO country code (e.g., "US", "GB")
89
+ * Defaults to the device's current country from `getCountry()`.
90
+ * Only used on iOS; ignored on Android.
91
+ *
135
92
  * @example
136
93
  * ```ts
137
94
  * const url = await VersionCheck.getStoreUrl();
95
+ * const urlUS = await VersionCheck.getStoreUrl({ countryCode: "US" });
138
96
  * Linking.openURL(url);
139
97
  * ```
140
98
  */
141
- readonly getStoreUrl: () => Promise<string>;
99
+ readonly getStoreUrl: (options?: {
100
+ countryCode?: string;
101
+ }) => Promise<string>;
142
102
  /**
143
103
  * Fetches the latest version of this app available in the store.
144
104
  *
145
105
  * Queries the iTunes API on iOS and the Play Store on Android.
106
+ * On iOS, uses the device's current country code by default but can be overridden.
107
+ *
108
+ * @param options - Optional configuration
109
+ * @param options.countryCode - 2-letter ISO country code (e.g., "US", "GB")
110
+ * Defaults to the device's current country from `getCountry()`.
111
+ * If the device region changes, the next call will use the new country.
112
+ * Only used on iOS; ignored on Android.
146
113
  *
147
114
  * @example
148
115
  * ```ts
149
- * const latest = await VersionCheck.getLatestVersion(); // "1.3.0"
116
+ * const latest = await VersionCheck.getLatestVersion(); // Uses current device country
117
+ * const latestUS = await VersionCheck.getLatestVersion({ countryCode: "US" });
118
+ * const latestGB = await VersionCheck.getLatestVersion({ countryCode: "GB" });
150
119
  * ```
151
120
  */
152
- readonly getLatestVersion: () => Promise<string>;
121
+ readonly getLatestVersion: (options?: {
122
+ countryCode?: string;
123
+ }) => Promise<string>;
153
124
  /**
154
125
  * Checks whether an app update is available by comparing the current
155
126
  * version against the latest store version.
156
127
  *
128
+ * Uses semantic version comparison. By default checks for any version
129
+ * increase, but you can filter by granularity:
130
+ *
131
+ * - `"major"` — only returns `true` for major bumps (1.x → 2.x)
132
+ * - `"minor"` — returns `true` for major or minor bumps
133
+ * - `"patch"` — returns `true` for any version increase (default)
134
+ *
157
135
  * @example
158
136
  * ```ts
159
137
  * if (await VersionCheck.needsUpdate()) {
160
138
  * const url = await VersionCheck.getStoreUrl();
161
139
  * Linking.openURL(url);
162
140
  * }
141
+ *
142
+ * // Only prompt for major updates
143
+ * const majorUpdate = await VersionCheck.needsUpdate({ level: "major" });
144
+ * ```
145
+ */
146
+ readonly needsUpdate: (options?: {
147
+ level?: UpdateLevel;
148
+ }) => Promise<boolean>;
149
+ /**
150
+ * Compares two semantic version strings.
151
+ *
152
+ * @returns -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * VersionCheck.compareVersions('1.0.0', '1.0.1') // -1
157
+ * VersionCheck.compareVersions('2.0.0', '2.0.0') // 0
158
+ * VersionCheck.compareVersions('3.0.0', '2.9.9') // 1
159
+ *
160
+ * if (VersionCheck.compareVersions(currentVersion, minimumVersion) < 0) {
161
+ * // Current version is below minimum — force update
162
+ * }
163
163
  * ```
164
164
  */
165
- readonly needsUpdate: () => Promise<boolean>;
165
+ readonly compareVersions: typeof compareVersions;
166
166
  };
167
- export { compareVersions };
168
167
  export type { UpdateLevel };
package/lib/index.js CHANGED
@@ -6,64 +6,12 @@ const version = HybridVersionCheck.version;
6
6
  const buildNumber = HybridVersionCheck.buildNumber;
7
7
  const packageName = HybridVersionCheck.packageName;
8
8
  const installSource = HybridVersionCheck.installSource;
9
- /**
10
- * Returns the device's current 2-letter ISO country code.
11
- *
12
- * @example
13
- * ```ts
14
- * getCountry() // "US"
15
- * ```
16
- */
17
- export const getCountry = () => HybridVersionCheck.getCountry();
18
- /**
19
- * Returns the store URL for this app.
20
- *
21
- * Automatically resolves to the App Store on iOS and Play Store on Android.
22
- *
23
- * @example
24
- * ```ts
25
- * const url = await getStoreUrl();
26
- * Linking.openURL(url);
27
- * ```
28
- */
29
- export const getStoreUrl = () => HybridVersionCheck.getStoreUrl();
30
- /**
31
- * Fetches the latest version of this app available in the store.
32
- *
33
- * @example
34
- * ```ts
35
- * const latest = await getLatestVersion(); // "1.3.0"
36
- * ```
37
- */
38
- export const getLatestVersion = () => HybridVersionCheck.getLatestVersion();
39
- /**
40
- * Checks whether an app update is available.
41
- *
42
- * Uses semantic version comparison. By default checks for any version
43
- * increase, but you can filter by granularity:
44
- *
45
- * - `"major"` — only returns `true` for major bumps (1.x → 2.x)
46
- * - `"minor"` — returns `true` for major or minor bumps
47
- * - `"patch"` — returns `true` for any version increase (default)
48
- *
49
- * @example
50
- * ```ts
51
- * if (await needsUpdate()) {
52
- * const url = await getStoreUrl();
53
- * Linking.openURL(url);
54
- * }
55
- *
56
- * // Only prompt for major updates
57
- * const majorUpdate = await needsUpdate({ level: "major" });
58
- * ```
59
- */
60
- export const needsUpdate = async (options) => {
61
- const latest = await HybridVersionCheck.getLatestVersion();
62
- return isNewerVersion(version, latest, options?.level ?? "patch");
63
- };
64
9
  /**
65
10
  * All version-check APIs in one object.
66
11
  *
12
+ * Provides access to app version information, store URLs, and update checking.
13
+ * Sync properties are cached at module init for zero native overhead.
14
+ *
67
15
  * @example
68
16
  * ```ts
69
17
  * VersionCheck.version // "1.2.0"
@@ -72,6 +20,9 @@ export const needsUpdate = async (options) => {
72
20
  * VersionCheck.getCountry() // "US"
73
21
  *
74
22
  * const url = await VersionCheck.getStoreUrl();
23
+ * if (await VersionCheck.needsUpdate()) {
24
+ * Linking.openURL(url);
25
+ * }
75
26
  * ```
76
27
  */
77
28
  export const VersionCheck = {
@@ -135,40 +86,88 @@ export const VersionCheck = {
135
86
  * VersionCheck.getCountry() // "US"
136
87
  * ```
137
88
  */
138
- getCountry,
89
+ getCountry: () => HybridVersionCheck.getCountry(),
139
90
  /**
140
91
  * Returns the App Store (iOS) or Play Store (Android) URL for this app.
141
92
  *
93
+ * @param options - Optional configuration
94
+ * @param options.countryCode - 2-letter ISO country code (e.g., "US", "GB")
95
+ * Defaults to the device's current country from `getCountry()`.
96
+ * Only used on iOS; ignored on Android.
97
+ *
142
98
  * @example
143
99
  * ```ts
144
100
  * const url = await VersionCheck.getStoreUrl();
101
+ * const urlUS = await VersionCheck.getStoreUrl({ countryCode: "US" });
145
102
  * Linking.openURL(url);
146
103
  * ```
147
104
  */
148
- getStoreUrl,
105
+ getStoreUrl: async (options) => {
106
+ return HybridVersionCheck.getStoreUrl(options?.countryCode);
107
+ },
149
108
  /**
150
109
  * Fetches the latest version of this app available in the store.
151
110
  *
152
111
  * Queries the iTunes API on iOS and the Play Store on Android.
112
+ * On iOS, uses the device's current country code by default but can be overridden.
113
+ *
114
+ * @param options - Optional configuration
115
+ * @param options.countryCode - 2-letter ISO country code (e.g., "US", "GB")
116
+ * Defaults to the device's current country from `getCountry()`.
117
+ * If the device region changes, the next call will use the new country.
118
+ * Only used on iOS; ignored on Android.
153
119
  *
154
120
  * @example
155
121
  * ```ts
156
- * const latest = await VersionCheck.getLatestVersion(); // "1.3.0"
122
+ * const latest = await VersionCheck.getLatestVersion(); // Uses current device country
123
+ * const latestUS = await VersionCheck.getLatestVersion({ countryCode: "US" });
124
+ * const latestGB = await VersionCheck.getLatestVersion({ countryCode: "GB" });
157
125
  * ```
158
126
  */
159
- getLatestVersion,
127
+ getLatestVersion: async (options) => {
128
+ return HybridVersionCheck.getLatestVersion(options?.countryCode);
129
+ },
160
130
  /**
161
131
  * Checks whether an app update is available by comparing the current
162
132
  * version against the latest store version.
163
133
  *
134
+ * Uses semantic version comparison. By default checks for any version
135
+ * increase, but you can filter by granularity:
136
+ *
137
+ * - `"major"` — only returns `true` for major bumps (1.x → 2.x)
138
+ * - `"minor"` — returns `true` for major or minor bumps
139
+ * - `"patch"` — returns `true` for any version increase (default)
140
+ *
164
141
  * @example
165
142
  * ```ts
166
143
  * if (await VersionCheck.needsUpdate()) {
167
144
  * const url = await VersionCheck.getStoreUrl();
168
145
  * Linking.openURL(url);
169
146
  * }
147
+ *
148
+ * // Only prompt for major updates
149
+ * const majorUpdate = await VersionCheck.needsUpdate({ level: "major" });
150
+ * ```
151
+ */
152
+ needsUpdate: async (options) => {
153
+ const latest = await HybridVersionCheck.getLatestVersion();
154
+ return isNewerVersion(version, latest, options?.level ?? "patch");
155
+ },
156
+ /**
157
+ * Compares two semantic version strings.
158
+ *
159
+ * @returns -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * VersionCheck.compareVersions('1.0.0', '1.0.1') // -1
164
+ * VersionCheck.compareVersions('2.0.0', '2.0.0') // 0
165
+ * VersionCheck.compareVersions('3.0.0', '2.9.9') // 1
166
+ *
167
+ * if (VersionCheck.compareVersions(currentVersion, minimumVersion) < 0) {
168
+ * // Current version is below minimum — force update
169
+ * }
170
170
  * ```
171
171
  */
172
- needsUpdate: () => HybridVersionCheck.needsUpdate(),
172
+ compareVersions,
173
173
  };
174
- export { compareVersions };
@@ -8,7 +8,7 @@ export interface VersionCheck extends HybridObject<{
8
8
  readonly packageName: string;
9
9
  readonly installSource: string | undefined;
10
10
  getCountry(): string;
11
- getStoreUrl(): Promise<string>;
12
- getLatestVersion(): Promise<string>;
11
+ getStoreUrl(countryCode?: string): Promise<string>;
12
+ getLatestVersion(countryCode?: string): Promise<string>;
13
13
  needsUpdate(): Promise<boolean>;
14
14
  }
@@ -77,9 +77,9 @@ namespace margelo::nitro::nitroversioncheck {
77
77
  auto __result = method(_javaPart);
78
78
  return __result->toStdString();
79
79
  }
80
- std::shared_ptr<Promise<std::string>> JHybridVersionCheckSpec::getStoreUrl() {
81
- static const auto method = javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>()>("getStoreUrl");
82
- auto __result = method(_javaPart);
80
+ std::shared_ptr<Promise<std::string>> JHybridVersionCheckSpec::getStoreUrl(const std::optional<std::string>& countryCode) {
81
+ static const auto method = javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>(jni::alias_ref<jni::JString> /* countryCode */)>("getStoreUrl");
82
+ auto __result = method(_javaPart, countryCode.has_value() ? jni::make_jstring(countryCode.value()) : nullptr);
83
83
  return [&]() {
84
84
  auto __promise = Promise<std::string>::create();
85
85
  __result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& __boxedResult) {
@@ -93,9 +93,9 @@ namespace margelo::nitro::nitroversioncheck {
93
93
  return __promise;
94
94
  }();
95
95
  }
96
- std::shared_ptr<Promise<std::string>> JHybridVersionCheckSpec::getLatestVersion() {
97
- static const auto method = javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>()>("getLatestVersion");
98
- auto __result = method(_javaPart);
96
+ std::shared_ptr<Promise<std::string>> JHybridVersionCheckSpec::getLatestVersion(const std::optional<std::string>& countryCode) {
97
+ static const auto method = javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>(jni::alias_ref<jni::JString> /* countryCode */)>("getLatestVersion");
98
+ auto __result = method(_javaPart, countryCode.has_value() ? jni::make_jstring(countryCode.value()) : nullptr);
99
99
  return [&]() {
100
100
  auto __promise = Promise<std::string>::create();
101
101
  __result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& __boxedResult) {
@@ -59,8 +59,8 @@ namespace margelo::nitro::nitroversioncheck {
59
59
  public:
60
60
  // Methods
61
61
  std::string getCountry() override;
62
- std::shared_ptr<Promise<std::string>> getStoreUrl() override;
63
- std::shared_ptr<Promise<std::string>> getLatestVersion() override;
62
+ std::shared_ptr<Promise<std::string>> getStoreUrl(const std::optional<std::string>& countryCode) override;
63
+ std::shared_ptr<Promise<std::string>> getLatestVersion(const std::optional<std::string>& countryCode) override;
64
64
  std::shared_ptr<Promise<bool>> needsUpdate() override;
65
65
 
66
66
  private:
@@ -66,11 +66,11 @@ abstract class HybridVersionCheckSpec: HybridObject() {
66
66
 
67
67
  @DoNotStrip
68
68
  @Keep
69
- abstract fun getStoreUrl(): Promise<String>
69
+ abstract fun getStoreUrl(countryCode: String?): Promise<String>
70
70
 
71
71
  @DoNotStrip
72
72
  @Keep
73
- abstract fun getLatestVersion(): Promise<String>
73
+ abstract fun getLatestVersion(countryCode: String?): Promise<String>
74
74
 
75
75
  @DoNotStrip
76
76
  @Keep
@@ -91,16 +91,16 @@ namespace margelo::nitro::nitroversioncheck {
91
91
  auto __value = std::move(__result.value());
92
92
  return __value;
93
93
  }
94
- inline std::shared_ptr<Promise<std::string>> getStoreUrl() override {
95
- auto __result = _swiftPart.getStoreUrl();
94
+ inline std::shared_ptr<Promise<std::string>> getStoreUrl(const std::optional<std::string>& countryCode) override {
95
+ auto __result = _swiftPart.getStoreUrl(countryCode);
96
96
  if (__result.hasError()) [[unlikely]] {
97
97
  std::rethrow_exception(__result.error());
98
98
  }
99
99
  auto __value = std::move(__result.value());
100
100
  return __value;
101
101
  }
102
- inline std::shared_ptr<Promise<std::string>> getLatestVersion() override {
103
- auto __result = _swiftPart.getLatestVersion();
102
+ inline std::shared_ptr<Promise<std::string>> getLatestVersion(const std::optional<std::string>& countryCode) override {
103
+ auto __result = _swiftPart.getLatestVersion(countryCode);
104
104
  if (__result.hasError()) [[unlikely]] {
105
105
  std::rethrow_exception(__result.error());
106
106
  }
@@ -17,8 +17,8 @@ public protocol HybridVersionCheckSpec_protocol: HybridObject {
17
17
 
18
18
  // Methods
19
19
  func getCountry() throws -> String
20
- func getStoreUrl() throws -> Promise<String>
21
- func getLatestVersion() throws -> Promise<String>
20
+ func getStoreUrl(countryCode: String?) throws -> Promise<String>
21
+ func getLatestVersion(countryCode: String?) throws -> Promise<String>
22
22
  func needsUpdate() throws -> Promise<Bool>
23
23
  }
24
24
 
@@ -169,9 +169,16 @@ open class HybridVersionCheckSpec_cxx {
169
169
  }
170
170
 
171
171
  @inline(__always)
172
- public final func getStoreUrl() -> bridge.Result_std__shared_ptr_Promise_std__string___ {
172
+ public final func getStoreUrl(countryCode: bridge.std__optional_std__string_) -> bridge.Result_std__shared_ptr_Promise_std__string___ {
173
173
  do {
174
- let __result = try self.__implementation.getStoreUrl()
174
+ let __result = try self.__implementation.getStoreUrl(countryCode: { () -> String? in
175
+ if bridge.has_value_std__optional_std__string_(countryCode) {
176
+ let __unwrapped = bridge.get_std__optional_std__string_(countryCode)
177
+ return String(__unwrapped)
178
+ } else {
179
+ return nil
180
+ }
181
+ }())
175
182
  let __resultCpp = { () -> bridge.std__shared_ptr_Promise_std__string__ in
176
183
  let __promise = bridge.create_std__shared_ptr_Promise_std__string__()
177
184
  let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_std__string__(__promise)
@@ -188,9 +195,16 @@ open class HybridVersionCheckSpec_cxx {
188
195
  }
189
196
 
190
197
  @inline(__always)
191
- public final func getLatestVersion() -> bridge.Result_std__shared_ptr_Promise_std__string___ {
198
+ public final func getLatestVersion(countryCode: bridge.std__optional_std__string_) -> bridge.Result_std__shared_ptr_Promise_std__string___ {
192
199
  do {
193
- let __result = try self.__implementation.getLatestVersion()
200
+ let __result = try self.__implementation.getLatestVersion(countryCode: { () -> String? in
201
+ if bridge.has_value_std__optional_std__string_(countryCode) {
202
+ let __unwrapped = bridge.get_std__optional_std__string_(countryCode)
203
+ return String(__unwrapped)
204
+ } else {
205
+ return nil
206
+ }
207
+ }())
194
208
  let __resultCpp = { () -> bridge.std__shared_ptr_Promise_std__string__ in
195
209
  let __promise = bridge.create_std__shared_ptr_Promise_std__string__()
196
210
  let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_std__string__(__promise)
@@ -54,8 +54,8 @@ namespace margelo::nitro::nitroversioncheck {
54
54
  public:
55
55
  // Methods
56
56
  virtual std::string getCountry() = 0;
57
- virtual std::shared_ptr<Promise<std::string>> getStoreUrl() = 0;
58
- virtual std::shared_ptr<Promise<std::string>> getLatestVersion() = 0;
57
+ virtual std::shared_ptr<Promise<std::string>> getStoreUrl(const std::optional<std::string>& countryCode) = 0;
58
+ virtual std::shared_ptr<Promise<std::string>> getLatestVersion(const std::optional<std::string>& countryCode) = 0;
59
59
  virtual std::shared_ptr<Promise<bool>> needsUpdate() = 0;
60
60
 
61
61
  protected:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-version-check",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "A lightweight, fast version-checking library for React Native, powered by Nitro Modules",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",
@@ -9,8 +9,8 @@
9
9
  "source": "src/index",
10
10
  "files": [
11
11
  "src",
12
- "react-native.config.js",
13
12
  "lib",
13
+ "react-native.config.js",
14
14
  "nitrogen",
15
15
  "android/build.gradle",
16
16
  "android/gradle.properties",
@@ -34,6 +34,9 @@
34
34
  "release": "release-it",
35
35
  "build": "tsc",
36
36
  "typecheck": "tsc --noEmit",
37
+ "test": "jest",
38
+ "test:watch": "jest --watch",
39
+ "test:coverage": "jest --coverage",
37
40
  "lint": "biome check .",
38
41
  "lint:fix": "biome check --write .",
39
42
  "format": "biome format --write .",
@@ -149,12 +152,15 @@
149
152
  },
150
153
  "devDependencies": {
151
154
  "@release-it/conventional-changelog": "^10.0.5",
155
+ "@types/jest": "^29.5.12",
152
156
  "@types/react": "^19.1.03",
157
+ "jest": "^29.7.0",
153
158
  "nitrogen": "*",
154
159
  "react": "19.2.0",
155
160
  "react-native": "0.83.0",
156
161
  "react-native-nitro-modules": "*",
157
162
  "release-it": "^19.2.4",
163
+ "ts-jest": "^29.1.2",
158
164
  "typescript": "^5.8.3"
159
165
  },
160
166
  "peerDependencies": {
@@ -0,0 +1,283 @@
1
+ import { type UpdateLevel, VersionCheck } from "../index";
2
+
3
+ // Mock the NitroModules
4
+ jest.mock("react-native-nitro-modules", () => ({
5
+ NitroModules: {
6
+ createHybridObject: jest.fn(() => ({
7
+ version: "1.2.0",
8
+ buildNumber: "42",
9
+ packageName: "com.example.app",
10
+ installSource: "appstore",
11
+ getCountry: jest.fn(() => "US"),
12
+ getStoreUrl: jest.fn(() => Promise.resolve("https://apps.apple.com/app/example")),
13
+ getLatestVersion: jest.fn(() => Promise.resolve("1.3.0")),
14
+ })),
15
+ },
16
+ }));
17
+
18
+ describe("VersionCheck API", () => {
19
+ describe("structure", () => {
20
+ it("should export VersionCheck object with all required properties", () => {
21
+ expect(VersionCheck).toBeDefined();
22
+ expect(typeof VersionCheck).toBe("object");
23
+ });
24
+
25
+ it("should have sync properties", () => {
26
+ expect(VersionCheck.version).toBeDefined();
27
+ expect(typeof VersionCheck.version).toBe("string");
28
+
29
+ expect(VersionCheck.buildNumber).toBeDefined();
30
+ expect(typeof VersionCheck.buildNumber).toBe("string");
31
+
32
+ expect(VersionCheck.packageName).toBeDefined();
33
+ expect(typeof VersionCheck.packageName).toBe("string");
34
+ });
35
+
36
+ it("should have optional installSource property", () => {
37
+ expect(VersionCheck.installSource).toBeDefined();
38
+ expect(
39
+ VersionCheck.installSource === undefined ||
40
+ VersionCheck.installSource === "appstore" ||
41
+ VersionCheck.installSource === "testflight" ||
42
+ VersionCheck.installSource === "playstore"
43
+ ).toBe(true);
44
+ });
45
+
46
+ it("should have sync method getCountry", () => {
47
+ expect(typeof VersionCheck.getCountry).toBe("function");
48
+ expect(VersionCheck.getCountry()).toBeDefined();
49
+ });
50
+
51
+ it("should have async method getStoreUrl", () => {
52
+ expect(typeof VersionCheck.getStoreUrl).toBe("function");
53
+ const result = VersionCheck.getStoreUrl();
54
+ expect(result).toBeInstanceOf(Promise);
55
+ });
56
+
57
+ it("should have async method getLatestVersion", () => {
58
+ expect(typeof VersionCheck.getLatestVersion).toBe("function");
59
+ const result = VersionCheck.getLatestVersion();
60
+ expect(result).toBeInstanceOf(Promise);
61
+ });
62
+
63
+ it("should have async method needsUpdate", () => {
64
+ expect(typeof VersionCheck.needsUpdate).toBe("function");
65
+ const result = VersionCheck.needsUpdate();
66
+ expect(result).toBeInstanceOf(Promise);
67
+ });
68
+ });
69
+
70
+ describe("property access", () => {
71
+ it("should return string values for sync properties", () => {
72
+ expect(typeof VersionCheck.version).toBe("string");
73
+ expect(typeof VersionCheck.buildNumber).toBe("string");
74
+ expect(typeof VersionCheck.packageName).toBe("string");
75
+ });
76
+
77
+ it("should have consistent version format", () => {
78
+ const version = VersionCheck.version;
79
+ // Should be a semantic version like "1.2.0"
80
+ expect(version).toMatch(/^\d+\.\d+\.\d+$/);
81
+ });
82
+
83
+ it("should return a valid country code for getCountry()", () => {
84
+ const country = VersionCheck.getCountry();
85
+ // Should be a 2-letter ISO country code (uppercase letters)
86
+ expect(country).toMatch(/^[A-Z]{2}$/);
87
+ });
88
+ });
89
+
90
+ describe("needsUpdate", () => {
91
+ it("should return a Promise<boolean>", async () => {
92
+ const result = VersionCheck.needsUpdate();
93
+ expect(result).toBeInstanceOf(Promise);
94
+ const resolved = await result;
95
+ expect(typeof resolved).toBe("boolean");
96
+ });
97
+
98
+ it("should accept options with level parameter", async () => {
99
+ const levels: UpdateLevel[] = ["major", "minor", "patch"];
100
+ for (const level of levels) {
101
+ const result = VersionCheck.needsUpdate({ level });
102
+ expect(result).toBeInstanceOf(Promise);
103
+ const resolved = await result;
104
+ expect(typeof resolved).toBe("boolean");
105
+ }
106
+ });
107
+
108
+ it("should default to patch level when no options provided", async () => {
109
+ const result = await VersionCheck.needsUpdate();
110
+ expect(typeof result).toBe("boolean");
111
+ });
112
+
113
+ it("should compare current version with latest", async () => {
114
+ // With mocked latest version as '1.3.0' and current as '1.2.0'
115
+ const needsUpdate = await VersionCheck.needsUpdate();
116
+ expect(typeof needsUpdate).toBe("boolean");
117
+ });
118
+ });
119
+
120
+ describe("API consistency", () => {
121
+ it("should provide consistent property access across multiple calls", () => {
122
+ expect(VersionCheck.version).toBe(VersionCheck.version);
123
+ expect(VersionCheck.buildNumber).toBe(VersionCheck.buildNumber);
124
+ expect(VersionCheck.packageName).toBe(VersionCheck.packageName);
125
+ expect(VersionCheck.getCountry()).toBe(VersionCheck.getCountry());
126
+ });
127
+
128
+ it("should have readonly type signature", () => {
129
+ // Type checking ensures VersionCheck object is readonly
130
+ // This test verifies all methods and properties exist as expected
131
+ expect(VersionCheck).toBeDefined();
132
+ expect(Object.keys(VersionCheck).length > 0).toBe(true);
133
+ });
134
+ });
135
+
136
+ describe("async operations", () => {
137
+ it("should fetch store URL", async () => {
138
+ const url = await VersionCheck.getStoreUrl();
139
+ expect(url).toMatch(/^https?:\/\//);
140
+ expect(url).toMatch(/apps\.apple\.com|play\.google\.com/);
141
+ });
142
+
143
+ it("should fetch store URL with custom country code", async () => {
144
+ const urlUS = await VersionCheck.getStoreUrl({ countryCode: "US" });
145
+ const urlGB = await VersionCheck.getStoreUrl({ countryCode: "GB" });
146
+ expect(urlUS).toMatch(/^https?:\/\//);
147
+ expect(urlGB).toMatch(/^https?:\/\//);
148
+ expect(urlUS).toMatch(/apps\.apple\.com|play\.google\.com/);
149
+ expect(urlGB).toMatch(/apps\.apple\.com|play\.google\.com/);
150
+ });
151
+
152
+ it("should fetch store URL with undefined country code", async () => {
153
+ const url = await VersionCheck.getStoreUrl({ countryCode: undefined });
154
+ expect(url).toMatch(/^https?:\/\//);
155
+ expect(url).toMatch(/apps\.apple\.com|play\.google\.com/);
156
+ });
157
+
158
+ it("should fetch latest version", async () => {
159
+ const latest = await VersionCheck.getLatestVersion();
160
+ expect(typeof latest).toBe("string");
161
+ // Should be a semantic version
162
+ expect(latest).toMatch(/^\d+\.\d+\.\d+$/);
163
+ });
164
+
165
+ it("should fetch latest version with custom country code", async () => {
166
+ const latestUS = await VersionCheck.getLatestVersion({ countryCode: "US" });
167
+ const latestGB = await VersionCheck.getLatestVersion({ countryCode: "GB" });
168
+ expect(typeof latestUS).toBe("string");
169
+ expect(typeof latestGB).toBe("string");
170
+ expect(latestUS).toMatch(/^\d+\.\d+\.\d+$/);
171
+ expect(latestGB).toMatch(/^\d+\.\d+\.\d+$/);
172
+ });
173
+
174
+ it("should use device country code by default", async () => {
175
+ const latest = await VersionCheck.getLatestVersion();
176
+ const latestWithDefault = await VersionCheck.getLatestVersion({ countryCode: undefined });
177
+ expect(typeof latest).toBe("string");
178
+ expect(typeof latestWithDefault).toBe("string");
179
+ });
180
+
181
+ it("should handle getLatestVersion and needsUpdate in parallel", async () => {
182
+ const [latest, needsUpdate] = await Promise.all([VersionCheck.getLatestVersion(), VersionCheck.needsUpdate()]);
183
+ expect(typeof latest).toBe("string");
184
+ expect(typeof needsUpdate).toBe("boolean");
185
+ });
186
+
187
+ it("should handle all async operations in parallel", async () => {
188
+ const [storeUrl, latestVersion, needsUpdate] = await Promise.all([
189
+ VersionCheck.getStoreUrl(),
190
+ VersionCheck.getLatestVersion(),
191
+ VersionCheck.needsUpdate(),
192
+ ]);
193
+ expect(typeof storeUrl).toBe("string");
194
+ expect(typeof latestVersion).toBe("string");
195
+ expect(typeof needsUpdate).toBe("boolean");
196
+ });
197
+ });
198
+
199
+ describe("common usage patterns", () => {
200
+ it("should support destructuring", () => {
201
+ const { version, buildNumber, packageName, installSource } = VersionCheck;
202
+ expect(typeof version).toBe("string");
203
+ expect(typeof buildNumber).toBe("string");
204
+ expect(typeof packageName).toBe("string");
205
+ expect(installSource === undefined || typeof installSource === "string").toBe(true);
206
+ });
207
+
208
+ it("should support destructuring with getCountry", () => {
209
+ const { getCountry } = VersionCheck;
210
+ expect(typeof getCountry).toBe("function");
211
+ expect(getCountry()).toBeDefined();
212
+ });
213
+
214
+ it("should support common update flow", async () => {
215
+ const url = await VersionCheck.getStoreUrl();
216
+ const needsUpdate = await VersionCheck.needsUpdate();
217
+
218
+ expect(url).toMatch(/^https?:\/\//);
219
+ expect(url).toMatch(/apps\.apple\.com|play\.google\.com/);
220
+ expect(typeof needsUpdate).toBe("boolean");
221
+
222
+ if (needsUpdate) {
223
+ expect(url).toBeTruthy();
224
+ }
225
+ });
226
+
227
+ it("should support granular update checks", async () => {
228
+ const majorUpdate = await VersionCheck.needsUpdate({ level: "major" });
229
+ const minorUpdate = await VersionCheck.needsUpdate({ level: "minor" });
230
+ const patchUpdate = await VersionCheck.needsUpdate({ level: "patch" });
231
+
232
+ // All should return boolean values
233
+ expect([majorUpdate, minorUpdate, patchUpdate]).toEqual(
234
+ expect.arrayContaining([true, false].includes(majorUpdate) ? [majorUpdate] : [])
235
+ );
236
+ expect(typeof majorUpdate).toBe("boolean");
237
+ expect(typeof minorUpdate).toBe("boolean");
238
+ expect(typeof patchUpdate).toBe("boolean");
239
+
240
+ // Logical consistency: granular checks have semantic ordering
241
+ // If major update, then minor and patch should also be true
242
+ if (majorUpdate) {
243
+ expect(minorUpdate).toBe(true);
244
+ expect(patchUpdate).toBe(true);
245
+ }
246
+ // If minor but not major, patch should still be true
247
+ if (minorUpdate && !majorUpdate) {
248
+ expect(patchUpdate).toBe(true);
249
+ }
250
+ });
251
+
252
+ it("should support install source detection", () => {
253
+ const source = VersionCheck.installSource;
254
+
255
+ if (source !== undefined) {
256
+ expect(["appstore", "testflight", "playstore"]).toContain(source);
257
+ }
258
+ });
259
+
260
+ it("should support region-specific store URLs", async () => {
261
+ const urlUS = await VersionCheck.getStoreUrl({ countryCode: "US" });
262
+ const urlGB = await VersionCheck.getStoreUrl({ countryCode: "GB" });
263
+ const urlJP = await VersionCheck.getStoreUrl({ countryCode: "JP" });
264
+
265
+ expect(urlUS).toMatch(/^https?:\/\//);
266
+ expect(urlGB).toMatch(/^https?:\/\//);
267
+ expect(urlJP).toMatch(/^https?:\/\//);
268
+ expect(urlUS).toMatch(/apps\.apple\.com|play\.google\.com/);
269
+ expect(urlGB).toMatch(/apps\.apple\.com|play\.google\.com/);
270
+ expect(urlJP).toMatch(/apps\.apple\.com|play\.google\.com/);
271
+ });
272
+
273
+ it("should support region-specific version checks", async () => {
274
+ const latestUS = await VersionCheck.getLatestVersion({ countryCode: "US" });
275
+ const latestGB = await VersionCheck.getLatestVersion({ countryCode: "GB" });
276
+ const latestJP = await VersionCheck.getLatestVersion({ countryCode: "JP" });
277
+
278
+ expect(typeof latestUS).toBe("string");
279
+ expect(typeof latestGB).toBe("string");
280
+ expect(typeof latestJP).toBe("string");
281
+ });
282
+ });
283
+ });
@@ -0,0 +1,145 @@
1
+ import { compareVersions, isNewerVersion } from "../semver";
2
+
3
+ describe("semver", () => {
4
+ describe("compareVersions", () => {
5
+ it("should return -1 when first version is older", () => {
6
+ expect(compareVersions("1.2.0", "1.3.0")).toBe(-1);
7
+ expect(compareVersions("1.2.0", "2.0.0")).toBe(-1);
8
+ expect(compareVersions("1.2.3", "1.2.4")).toBe(-1);
9
+ });
10
+
11
+ it("should return 1 when first version is newer", () => {
12
+ expect(compareVersions("2.0.0", "1.9.9")).toBe(1);
13
+ expect(compareVersions("1.3.0", "1.2.0")).toBe(1);
14
+ expect(compareVersions("1.2.4", "1.2.3")).toBe(1);
15
+ });
16
+
17
+ it("should return 0 when versions are equal", () => {
18
+ expect(compareVersions("1.0.0", "1.0.0")).toBe(0);
19
+ expect(compareVersions("2.5.10", "2.5.10")).toBe(0);
20
+ });
21
+
22
+ it("should handle major version differences", () => {
23
+ expect(compareVersions("1.0.0", "2.0.0")).toBe(-1);
24
+ expect(compareVersions("3.0.0", "2.0.0")).toBe(1);
25
+ });
26
+
27
+ it("should handle minor version differences", () => {
28
+ expect(compareVersions("1.2.0", "1.3.0")).toBe(-1);
29
+ expect(compareVersions("1.5.0", "1.3.0")).toBe(1);
30
+ });
31
+
32
+ it("should handle patch version differences", () => {
33
+ expect(compareVersions("1.0.5", "1.0.10")).toBe(-1);
34
+ expect(compareVersions("1.0.10", "1.0.5")).toBe(1);
35
+ });
36
+
37
+ it("should handle versions with leading zeros", () => {
38
+ expect(compareVersions("01.02.03", "1.2.3")).toBe(0);
39
+ });
40
+
41
+ it("should handle malformed versions gracefully", () => {
42
+ // Missing parts should be treated as 0
43
+ expect(compareVersions("1", "1.0.0")).toBe(0);
44
+ expect(compareVersions("1.2", "1.2.0")).toBe(0);
45
+ });
46
+
47
+ it("should handle real-world version scenarios", () => {
48
+ // Bug fix scenario
49
+ expect(compareVersions("2.2.0", "2.0.22")).toBe(1); // 2.2.0 is newer than 2.0.22
50
+ expect(compareVersions("2.0.22", "2.2.0")).toBe(-1);
51
+
52
+ // Update checks
53
+ expect(compareVersions("1.0.0", "1.0.1")).toBe(-1);
54
+ expect(compareVersions("1.0.0", "1.1.0")).toBe(-1);
55
+ expect(compareVersions("1.0.0", "2.0.0")).toBe(-1);
56
+ });
57
+ });
58
+
59
+ describe("isNewerVersion", () => {
60
+ describe("with patch level (default)", () => {
61
+ it("should return true for any version increase", () => {
62
+ expect(isNewerVersion("1.0.0", "1.0.1", "patch")).toBe(true);
63
+ expect(isNewerVersion("1.0.0", "1.1.0", "patch")).toBe(true);
64
+ expect(isNewerVersion("1.0.0", "2.0.0", "patch")).toBe(true);
65
+ });
66
+
67
+ it("should return false when current is same or newer", () => {
68
+ expect(isNewerVersion("1.0.0", "1.0.0", "patch")).toBe(false);
69
+ expect(isNewerVersion("1.0.1", "1.0.0", "patch")).toBe(false);
70
+ expect(isNewerVersion("2.0.0", "1.0.0", "patch")).toBe(false);
71
+ });
72
+
73
+ it("should use patch level as default", () => {
74
+ expect(isNewerVersion("1.0.0", "1.0.1")).toBe(true);
75
+ expect(isNewerVersion("1.0.0", "1.0.0")).toBe(false);
76
+ });
77
+ });
78
+
79
+ describe("with minor level", () => {
80
+ it("should return true for major or minor bumps", () => {
81
+ expect(isNewerVersion("1.0.0", "1.1.0", "minor")).toBe(true);
82
+ expect(isNewerVersion("1.0.0", "2.0.0", "minor")).toBe(true);
83
+ });
84
+
85
+ it("should return false for patch-only bumps", () => {
86
+ expect(isNewerVersion("1.0.0", "1.0.1", "minor")).toBe(false);
87
+ });
88
+
89
+ it("should return false when current is same or newer", () => {
90
+ expect(isNewerVersion("1.1.0", "1.1.0", "minor")).toBe(false);
91
+ expect(isNewerVersion("1.1.0", "1.0.0", "minor")).toBe(false);
92
+ expect(isNewerVersion("2.0.0", "1.5.0", "minor")).toBe(false);
93
+ });
94
+ });
95
+
96
+ describe("with major level", () => {
97
+ it("should return true only for major bumps", () => {
98
+ expect(isNewerVersion("1.0.0", "2.0.0", "major")).toBe(true);
99
+ expect(isNewerVersion("1.5.9", "2.0.0", "major")).toBe(true);
100
+ });
101
+
102
+ it("should return false for minor or patch bumps", () => {
103
+ expect(isNewerVersion("1.0.0", "1.1.0", "major")).toBe(false);
104
+ expect(isNewerVersion("1.0.0", "1.0.1", "major")).toBe(false);
105
+ });
106
+
107
+ it("should return false when current is same or newer", () => {
108
+ expect(isNewerVersion("1.0.0", "1.0.0", "major")).toBe(false);
109
+ expect(isNewerVersion("2.0.0", "1.0.0", "major")).toBe(false);
110
+ });
111
+ });
112
+
113
+ describe("real-world scenarios", () => {
114
+ it("should handle the GitHub issue scenario", () => {
115
+ // From the issue: latest is 2.0.22, current is 2.2.0
116
+ expect(isNewerVersion("2.2.0", "2.0.22", "patch")).toBe(false);
117
+ expect(isNewerVersion("2.2.0", "2.0.22", "minor")).toBe(false);
118
+ expect(isNewerVersion("2.2.0", "2.0.22", "major")).toBe(false);
119
+ });
120
+
121
+ it("should handle typical update scenarios", () => {
122
+ // App is on 1.2.0, store has 1.2.1
123
+ expect(isNewerVersion("1.2.0", "1.2.1", "patch")).toBe(true);
124
+ expect(isNewerVersion("1.2.0", "1.2.1", "minor")).toBe(false);
125
+ expect(isNewerVersion("1.2.0", "1.2.1", "major")).toBe(false);
126
+
127
+ // App is on 1.2.0, store has 1.3.0
128
+ expect(isNewerVersion("1.2.0", "1.3.0", "patch")).toBe(true);
129
+ expect(isNewerVersion("1.2.0", "1.3.0", "minor")).toBe(true);
130
+ expect(isNewerVersion("1.2.0", "1.3.0", "major")).toBe(false);
131
+
132
+ // App is on 1.2.0, store has 2.0.0
133
+ expect(isNewerVersion("1.2.0", "2.0.0", "patch")).toBe(true);
134
+ expect(isNewerVersion("1.2.0", "2.0.0", "minor")).toBe(true);
135
+ expect(isNewerVersion("1.2.0", "2.0.0", "major")).toBe(true);
136
+ });
137
+
138
+ it("should correctly identify when app is already up to date", () => {
139
+ expect(isNewerVersion("1.5.0", "1.4.0")).toBe(false);
140
+ expect(isNewerVersion("2.0.0", "1.9.9")).toBe(false);
141
+ expect(isNewerVersion("3.0.0", "2.99.99")).toBe(false);
142
+ });
143
+ });
144
+ });
145
+ });
package/src/index.ts CHANGED
@@ -11,68 +11,12 @@ const buildNumber = HybridVersionCheck.buildNumber;
11
11
  const packageName = HybridVersionCheck.packageName;
12
12
  const installSource = HybridVersionCheck.installSource;
13
13
 
14
- /**
15
- * Returns the device's current 2-letter ISO country code.
16
- *
17
- * @example
18
- * ```ts
19
- * getCountry() // "US"
20
- * ```
21
- */
22
- export const getCountry = () => HybridVersionCheck.getCountry();
23
-
24
- /**
25
- * Returns the store URL for this app.
26
- *
27
- * Automatically resolves to the App Store on iOS and Play Store on Android.
28
- *
29
- * @example
30
- * ```ts
31
- * const url = await getStoreUrl();
32
- * Linking.openURL(url);
33
- * ```
34
- */
35
- export const getStoreUrl = () => HybridVersionCheck.getStoreUrl();
36
-
37
- /**
38
- * Fetches the latest version of this app available in the store.
39
- *
40
- * @example
41
- * ```ts
42
- * const latest = await getLatestVersion(); // "1.3.0"
43
- * ```
44
- */
45
- export const getLatestVersion = () => HybridVersionCheck.getLatestVersion();
46
-
47
- /**
48
- * Checks whether an app update is available.
49
- *
50
- * Uses semantic version comparison. By default checks for any version
51
- * increase, but you can filter by granularity:
52
- *
53
- * - `"major"` — only returns `true` for major bumps (1.x → 2.x)
54
- * - `"minor"` — returns `true` for major or minor bumps
55
- * - `"patch"` — returns `true` for any version increase (default)
56
- *
57
- * @example
58
- * ```ts
59
- * if (await needsUpdate()) {
60
- * const url = await getStoreUrl();
61
- * Linking.openURL(url);
62
- * }
63
- *
64
- * // Only prompt for major updates
65
- * const majorUpdate = await needsUpdate({ level: "major" });
66
- * ```
67
- */
68
- export const needsUpdate = async (options?: { level?: UpdateLevel }): Promise<boolean> => {
69
- const latest = await HybridVersionCheck.getLatestVersion();
70
- return isNewerVersion(version, latest, options?.level ?? "patch");
71
- };
72
-
73
14
  /**
74
15
  * All version-check APIs in one object.
75
16
  *
17
+ * Provides access to app version information, store URLs, and update checking.
18
+ * Sync properties are cached at module init for zero native overhead.
19
+ *
76
20
  * @example
77
21
  * ```ts
78
22
  * VersionCheck.version // "1.2.0"
@@ -81,6 +25,9 @@ export const needsUpdate = async (options?: { level?: UpdateLevel }): Promise<bo
81
25
  * VersionCheck.getCountry() // "US"
82
26
  *
83
27
  * const url = await VersionCheck.getStoreUrl();
28
+ * if (await VersionCheck.needsUpdate()) {
29
+ * Linking.openURL(url);
30
+ * }
84
31
  * ```
85
32
  */
86
33
  export const VersionCheck = {
@@ -144,42 +91,89 @@ export const VersionCheck = {
144
91
  * VersionCheck.getCountry() // "US"
145
92
  * ```
146
93
  */
147
- getCountry,
94
+ getCountry: () => HybridVersionCheck.getCountry(),
148
95
  /**
149
96
  * Returns the App Store (iOS) or Play Store (Android) URL for this app.
150
97
  *
98
+ * @param options - Optional configuration
99
+ * @param options.countryCode - 2-letter ISO country code (e.g., "US", "GB")
100
+ * Defaults to the device's current country from `getCountry()`.
101
+ * Only used on iOS; ignored on Android.
102
+ *
151
103
  * @example
152
104
  * ```ts
153
105
  * const url = await VersionCheck.getStoreUrl();
106
+ * const urlUS = await VersionCheck.getStoreUrl({ countryCode: "US" });
154
107
  * Linking.openURL(url);
155
108
  * ```
156
109
  */
157
- getStoreUrl,
110
+ getStoreUrl: async (options?: { countryCode?: string }): Promise<string> => {
111
+ return HybridVersionCheck.getStoreUrl(options?.countryCode);
112
+ },
158
113
  /**
159
114
  * Fetches the latest version of this app available in the store.
160
115
  *
161
116
  * Queries the iTunes API on iOS and the Play Store on Android.
117
+ * On iOS, uses the device's current country code by default but can be overridden.
118
+ *
119
+ * @param options - Optional configuration
120
+ * @param options.countryCode - 2-letter ISO country code (e.g., "US", "GB")
121
+ * Defaults to the device's current country from `getCountry()`.
122
+ * If the device region changes, the next call will use the new country.
123
+ * Only used on iOS; ignored on Android.
162
124
  *
163
125
  * @example
164
126
  * ```ts
165
- * const latest = await VersionCheck.getLatestVersion(); // "1.3.0"
127
+ * const latest = await VersionCheck.getLatestVersion(); // Uses current device country
128
+ * const latestUS = await VersionCheck.getLatestVersion({ countryCode: "US" });
129
+ * const latestGB = await VersionCheck.getLatestVersion({ countryCode: "GB" });
166
130
  * ```
167
131
  */
168
- getLatestVersion,
132
+ getLatestVersion: async (options?: { countryCode?: string }): Promise<string> => {
133
+ return HybridVersionCheck.getLatestVersion(options?.countryCode);
134
+ },
169
135
  /**
170
136
  * Checks whether an app update is available by comparing the current
171
137
  * version against the latest store version.
172
138
  *
139
+ * Uses semantic version comparison. By default checks for any version
140
+ * increase, but you can filter by granularity:
141
+ *
142
+ * - `"major"` — only returns `true` for major bumps (1.x → 2.x)
143
+ * - `"minor"` — returns `true` for major or minor bumps
144
+ * - `"patch"` — returns `true` for any version increase (default)
145
+ *
173
146
  * @example
174
147
  * ```ts
175
148
  * if (await VersionCheck.needsUpdate()) {
176
149
  * const url = await VersionCheck.getStoreUrl();
177
150
  * Linking.openURL(url);
178
151
  * }
152
+ *
153
+ * // Only prompt for major updates
154
+ * const majorUpdate = await VersionCheck.needsUpdate({ level: "major" });
179
155
  * ```
180
156
  */
181
- needsUpdate: () => HybridVersionCheck.needsUpdate(),
157
+ needsUpdate: async (options?: { level?: UpdateLevel }): Promise<boolean> => {
158
+ const latest = await HybridVersionCheck.getLatestVersion();
159
+ return isNewerVersion(version, latest, options?.level ?? "patch");
160
+ },
161
+ /**
162
+ * Compares two semantic version strings.
163
+ *
164
+ * @returns -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
165
+ *
166
+ * @example
167
+ * ```ts
168
+ * VersionCheck.compareVersions('1.0.0', '1.0.1') // -1
169
+ * VersionCheck.compareVersions('2.0.0', '2.0.0') // 0
170
+ * VersionCheck.compareVersions('3.0.0', '2.9.9') // 1
171
+ *
172
+ * if (VersionCheck.compareVersions(currentVersion, minimumVersion) < 0) {
173
+ * // Current version is below minimum — force update
174
+ * }
175
+ * ```
176
+ */
177
+ compareVersions,
182
178
  } as const;
183
-
184
- export { compareVersions };
185
179
  export type { UpdateLevel };
@@ -10,7 +10,7 @@ export interface VersionCheck
10
10
  readonly packageName: string;
11
11
  readonly installSource: string | undefined;
12
12
  getCountry(): string;
13
- getStoreUrl(): Promise<string>;
14
- getLatestVersion(): Promise<string>;
13
+ getStoreUrl(countryCode?: string): Promise<string>;
14
+ getLatestVersion(countryCode?: string): Promise<string>;
15
15
  needsUpdate(): Promise<boolean>;
16
16
  }