@sudobility/building_blocks_rn 0.0.36 → 0.0.38

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.
@@ -14,7 +14,7 @@ Pod::Spec.new do |s|
14
14
  s.osx.deployment_target = '14.0'
15
15
 
16
16
  s.source_files = 'macos/**/*.{h,m,mm}'
17
- s.frameworks = 'AuthenticationServices', 'Security'
17
+ s.frameworks = 'AuthenticationServices', 'Security', 'StoreKit'
18
18
 
19
19
  s.dependency 'React-Core'
20
20
  end
package/dist/index.d.ts CHANGED
@@ -21,3 +21,5 @@ export type { ErrorBoundaryProps } from './src/components/error';
21
21
  export { initializeI18nRN, getI18n, i18n } from './src/i18n';
22
22
  export type { I18nConfig } from './src/i18n';
23
23
  export { authenticate as webAuthAuthenticate, generateCodeVerifier as webAuthGenerateCodeVerifier, sha256Base64Url as webAuthSha256Base64Url, } from './src/native/WebAuth';
24
+ export { isInAppReviewAvailable, requestInAppReview, recordSuccessfulActionAndPromptForReview, } from './src/review';
25
+ export type { ReviewPromptOptions, ReviewPromptResult } from './src/review';
package/dist/index.js CHANGED
@@ -27,3 +27,5 @@ export { ErrorBoundary } from './src/components/error';
27
27
  export { initializeI18nRN, getI18n, i18n } from './src/i18n';
28
28
  // Native modules (desktop only)
29
29
  export { authenticate as webAuthAuthenticate, generateCodeVerifier as webAuthGenerateCodeVerifier, sha256Base64Url as webAuthSha256Base64Url, } from './src/native/WebAuth';
30
+ // Review prompts
31
+ export { isInAppReviewAvailable, requestInAppReview, recordSuccessfulActionAndPromptForReview, } from './src/review';
@@ -0,0 +1,2 @@
1
+ export declare function isNativeDesktopReviewAvailable(): Promise<boolean>;
2
+ export declare function requestNativeDesktopReview(): Promise<boolean>;
@@ -0,0 +1,14 @@
1
+ import { NativeModules, Platform } from 'react-native';
2
+ const { InAppReviewModule } = NativeModules;
3
+ export async function isNativeDesktopReviewAvailable() {
4
+ if (Platform.OS !== 'macos' || !InAppReviewModule) {
5
+ return false;
6
+ }
7
+ return InAppReviewModule.isAvailable();
8
+ }
9
+ export async function requestNativeDesktopReview() {
10
+ if (Platform.OS !== 'macos' || !InAppReviewModule) {
11
+ return false;
12
+ }
13
+ return InAppReviewModule.requestReview();
14
+ }
@@ -0,0 +1,13 @@
1
+ export interface ReviewPromptOptions {
2
+ storagePrefix?: string;
3
+ minSuccessCount?: number;
4
+ cooldownDays?: number;
5
+ }
6
+ export interface ReviewPromptResult {
7
+ prompted: boolean;
8
+ available: boolean;
9
+ successCount: number;
10
+ }
11
+ export declare function isInAppReviewAvailable(): Promise<boolean>;
12
+ export declare function requestInAppReview(): Promise<boolean>;
13
+ export declare function recordSuccessfulActionAndPromptForReview(options?: ReviewPromptOptions): Promise<ReviewPromptResult>;
@@ -0,0 +1,77 @@
1
+ import AsyncStorage from '@react-native-async-storage/async-storage';
2
+ import InAppReview from 'react-native-in-app-review';
3
+ import { Platform } from 'react-native';
4
+ import { isNativeDesktopReviewAvailable, requestNativeDesktopReview, } from '../native/InAppReview';
5
+ const DEFAULT_STORAGE_PREFIX = '@sudobility.review_prompt';
6
+ const DEFAULT_MIN_SUCCESS_COUNT = 3;
7
+ const DEFAULT_COOLDOWN_DAYS = 30;
8
+ function getStorageKeys(storagePrefix) {
9
+ return {
10
+ successCount: `${storagePrefix}.success_count`,
11
+ lastPromptedAt: `${storagePrefix}.last_prompted_at`,
12
+ };
13
+ }
14
+ async function isPackageReviewAvailable() {
15
+ if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
16
+ return false;
17
+ }
18
+ try {
19
+ return Boolean(InAppReview.isAvailable());
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ export async function isInAppReviewAvailable() {
26
+ if (Platform.OS === 'ios' || Platform.OS === 'android') {
27
+ return isPackageReviewAvailable();
28
+ }
29
+ if (Platform.OS === 'macos') {
30
+ return isNativeDesktopReviewAvailable();
31
+ }
32
+ return false;
33
+ }
34
+ export async function requestInAppReview() {
35
+ if (Platform.OS === 'ios' || Platform.OS === 'android') {
36
+ if (!(await isPackageReviewAvailable())) {
37
+ return false;
38
+ }
39
+ try {
40
+ await InAppReview.RequestInAppReview();
41
+ return true;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ if (Platform.OS === 'macos') {
48
+ return requestNativeDesktopReview();
49
+ }
50
+ return false;
51
+ }
52
+ export async function recordSuccessfulActionAndPromptForReview(options = {}) {
53
+ const storagePrefix = options.storagePrefix ?? DEFAULT_STORAGE_PREFIX;
54
+ const minSuccessCount = options.minSuccessCount ?? DEFAULT_MIN_SUCCESS_COUNT;
55
+ const cooldownDays = options.cooldownDays ?? DEFAULT_COOLDOWN_DAYS;
56
+ const cooldownMs = cooldownDays * 24 * 60 * 60 * 1000;
57
+ const keys = getStorageKeys(storagePrefix);
58
+ const storedCount = await AsyncStorage.getItem(keys.successCount);
59
+ const successCount = (Number.parseInt(storedCount ?? '0', 10) || 0) + 1;
60
+ await AsyncStorage.setItem(keys.successCount, String(successCount));
61
+ const available = await isInAppReviewAvailable();
62
+ if (!available || successCount < minSuccessCount) {
63
+ return { prompted: false, available, successCount };
64
+ }
65
+ const lastPromptedAt = await AsyncStorage.getItem(keys.lastPromptedAt);
66
+ if (lastPromptedAt) {
67
+ const elapsedMs = Date.now() - Number.parseInt(lastPromptedAt, 10);
68
+ if (Number.isFinite(elapsedMs) && elapsedMs < cooldownMs) {
69
+ return { prompted: false, available, successCount };
70
+ }
71
+ }
72
+ const prompted = await requestInAppReview();
73
+ if (prompted) {
74
+ await AsyncStorage.setItem(keys.lastPromptedAt, String(Date.now()));
75
+ }
76
+ return { prompted, available, successCount };
77
+ }
@@ -0,0 +1,2 @@
1
+ export { isInAppReviewAvailable, requestInAppReview, recordSuccessfulActionAndPromptForReview, } from './inAppReview';
2
+ export type { ReviewPromptOptions, ReviewPromptResult } from './inAppReview';
@@ -0,0 +1 @@
1
+ export { isInAppReviewAvailable, requestInAppReview, recordSuccessfulActionAndPromptForReview, } from './inAppReview';
@@ -0,0 +1,4 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface InAppReviewModule : NSObject <RCTBridgeModule>
4
+ @end
@@ -0,0 +1,25 @@
1
+ #import "InAppReviewModule.h"
2
+ #import <StoreKit/StoreKit.h>
3
+
4
+ @implementation InAppReviewModule
5
+
6
+ RCT_EXPORT_MODULE();
7
+
8
+ + (BOOL)requiresMainQueueSetup {
9
+ return YES;
10
+ }
11
+
12
+ RCT_EXPORT_METHOD(isAvailable:(RCTPromiseResolveBlock)resolve
13
+ rejecter:(RCTPromiseRejectBlock)reject) {
14
+ resolve(@YES);
15
+ }
16
+
17
+ RCT_EXPORT_METHOD(requestReview:(RCTPromiseResolveBlock)resolve
18
+ rejecter:(RCTPromiseRejectBlock)reject) {
19
+ dispatch_async(dispatch_get_main_queue(), ^{
20
+ [SKStoreReviewController requestReview];
21
+ resolve(@YES);
22
+ });
23
+ }
24
+
25
+ @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sudobility/building_blocks_rn",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "description": "Higher-level shared UI building blocks for Sudobility React Native apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -44,7 +44,8 @@
44
44
  "i18next": "^23.0.0 || ^24.0.0 || ^25.0.0",
45
45
  "react-i18next": "^14.0.0 || ^15.0.0 || ^16.0.0",
46
46
  "@react-native-async-storage/async-storage": ">=1.0.0",
47
- "@sudobility/design": "^1.1.30"
47
+ "@sudobility/design": "^1.1.30",
48
+ "react-native-in-app-review": ">=4.4.2"
48
49
  },
49
50
  "peerDependenciesMeta": {
50
51
  "firebase": {
@@ -64,6 +65,9 @@
64
65
  },
65
66
  "@sudobility/types": {
66
67
  "optional": true
68
+ },
69
+ "react-native-in-app-review": {
70
+ "optional": true
67
71
  }
68
72
  },
69
73
  "devDependencies": {
@@ -71,7 +75,7 @@
71
75
  "@react-native-async-storage/async-storage": "2.2.0",
72
76
  "@react-navigation/native": "^7.1.28",
73
77
  "@react-navigation/native-stack": "^7.10.1",
74
- "@sudobility/types": "^1.9.61",
78
+ "@sudobility/types": "^1.9.62",
75
79
  "@tanstack/react-query": "^5.90.19",
76
80
  "@types/react": "~19.1.0",
77
81
  "@types/react-dom": "^19.2.3",