@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.
- package/building-blocks-rn-macos.podspec +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/src/native/InAppReview.d.ts +2 -0
- package/dist/src/native/InAppReview.js +14 -0
- package/dist/src/review/inAppReview.d.ts +13 -0
- package/dist/src/review/inAppReview.js +77 -0
- package/dist/src/review/index.d.ts +2 -0
- package/dist/src/review/index.js +1 -0
- package/macos/InAppReviewModule.h +4 -0
- package/macos/InAppReviewModule.m +25 -0
- package/package.json +7 -3
|
@@ -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,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 @@
|
|
|
1
|
+
export { isInAppReviewAvailable, requestInAppReview, recordSuccessfulActionAndPromptForReview, } from './inAppReview';
|
|
@@ -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.
|
|
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.
|
|
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",
|