expo-iap 2.7.11 → 2.7.13
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/CHANGELOG.md +2 -137
- package/CLAUDE.md +55 -0
- package/CONTRIBUTING.md +118 -72
- package/README.md +4 -4
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +34 -6
- package/build/ExpoIap.types.d.ts +1 -0
- package/build/ExpoIap.types.d.ts.map +1 -1
- package/build/ExpoIap.types.js.map +1 -1
- package/build/helpers/subscription.d.ts +22 -0
- package/build/helpers/subscription.d.ts.map +1 -0
- package/build/helpers/subscription.js +92 -0
- package/build/helpers/subscription.js.map +1 -0
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -2
- package/build/index.js.map +1 -1
- package/build/useIap.d.ts +4 -0
- package/build/useIap.d.ts.map +1 -1
- package/build/useIap.js +26 -1
- package/build/useIap.js.map +1 -1
- package/bun.lock +2493 -0
- package/ios/ExpoIapModule.swift +99 -4
- package/package.json +1 -1
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/src/ExpoIap.types.ts +3 -2
- package/src/helpers/subscription.ts +122 -0
- package/src/index.ts +12 -4
- package/src/useIap.ts +41 -0
- package/bun.lockb +0 -0
package/ios/ExpoIapModule.swift
CHANGED
|
@@ -48,9 +48,10 @@ func serializeTransaction(_ transaction: Transaction, jwsRepresentationIos: Stri
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
var purchaseMap: [String: Any?] = [
|
|
51
|
-
"id": transaction.
|
|
51
|
+
"id": String(transaction.id),
|
|
52
|
+
"productId": transaction.productID,
|
|
52
53
|
"ids": [transaction.productID],
|
|
53
|
-
"transactionId": String(transaction.id),
|
|
54
|
+
"transactionId": String(transaction.id), // @deprecated - use id instead
|
|
54
55
|
"transactionDate": transaction.purchaseDate.timeIntervalSince1970 * 1000,
|
|
55
56
|
"transactionReceipt": jwsReceipt,
|
|
56
57
|
"platform": "ios",
|
|
@@ -240,7 +241,7 @@ public class ExpoIapModule: Module {
|
|
|
240
241
|
"ERROR_CODES": IapErrorCode.toDictionary()
|
|
241
242
|
])
|
|
242
243
|
|
|
243
|
-
Events(IapEvent.PurchaseUpdated, IapEvent.PurchaseError)
|
|
244
|
+
Events(IapEvent.PurchaseUpdated, IapEvent.PurchaseError, IapEvent.PromotedProductIOS)
|
|
244
245
|
|
|
245
246
|
OnStartObserving {
|
|
246
247
|
self.hasListeners = true
|
|
@@ -493,6 +494,14 @@ public class ExpoIapModule: Module {
|
|
|
493
494
|
options.insert(.appAccountToken(appAccountUUID))
|
|
494
495
|
}
|
|
495
496
|
guard let windowScene = await self.currentWindowScene() else {
|
|
497
|
+
let errorData = [
|
|
498
|
+
"responseCode": IapErrorCode.serviceError,
|
|
499
|
+
"debugMessage": "Could not find window scene",
|
|
500
|
+
"code": IapErrorCode.serviceError,
|
|
501
|
+
"message": "Could not find window scene",
|
|
502
|
+
"productId": sku,
|
|
503
|
+
]
|
|
504
|
+
self.sendEvent(IapEvent.PurchaseError, errorData)
|
|
496
505
|
throw Exception(name: "ExpoIapModule", description: "Could not find window scene", code: IapErrorCode.serviceError)
|
|
497
506
|
}
|
|
498
507
|
let result: Product.PurchaseResult
|
|
@@ -536,19 +545,105 @@ public class ExpoIapModule: Module {
|
|
|
536
545
|
return serialized
|
|
537
546
|
}
|
|
538
547
|
case .userCancelled:
|
|
548
|
+
let errorData = [
|
|
549
|
+
"responseCode": IapErrorCode.userCancelled,
|
|
550
|
+
"debugMessage": "User cancelled the purchase",
|
|
551
|
+
"code": IapErrorCode.userCancelled,
|
|
552
|
+
"message": "User cancelled the purchase",
|
|
553
|
+
"productId": sku,
|
|
554
|
+
]
|
|
555
|
+
self.sendEvent(IapEvent.PurchaseError, errorData)
|
|
539
556
|
throw Exception(name: "ExpoIapModule", description: "User cancelled the purchase", code: IapErrorCode.userCancelled)
|
|
540
557
|
case .pending:
|
|
558
|
+
let errorData = [
|
|
559
|
+
"responseCode": IapErrorCode.deferredPayment,
|
|
560
|
+
"debugMessage": "The payment was deferred",
|
|
561
|
+
"code": IapErrorCode.deferredPayment,
|
|
562
|
+
"message": "The payment was deferred",
|
|
563
|
+
"productId": sku,
|
|
564
|
+
]
|
|
565
|
+
self.sendEvent(IapEvent.PurchaseError, errorData)
|
|
541
566
|
throw Exception(name: "ExpoIapModule", description: "The payment was deferred", code: IapErrorCode.deferredPayment)
|
|
542
567
|
@unknown default:
|
|
568
|
+
let errorData = [
|
|
569
|
+
"responseCode": IapErrorCode.unknown,
|
|
570
|
+
"debugMessage": "Unknown purchase result",
|
|
571
|
+
"code": IapErrorCode.unknown,
|
|
572
|
+
"message": "Unknown purchase result",
|
|
573
|
+
"productId": sku,
|
|
574
|
+
]
|
|
575
|
+
self.sendEvent(IapEvent.PurchaseError, errorData)
|
|
543
576
|
throw Exception(name: "ExpoIapModule", description: "Unknown purchase result", code: IapErrorCode.unknown)
|
|
544
577
|
}
|
|
545
578
|
} catch {
|
|
546
579
|
if error is Exception {
|
|
547
580
|
throw error
|
|
548
581
|
}
|
|
549
|
-
|
|
582
|
+
|
|
583
|
+
// Map StoreKit errors to proper error codes
|
|
584
|
+
var errorCode = IapErrorCode.purchaseError
|
|
585
|
+
var errorMessage = error.localizedDescription
|
|
586
|
+
|
|
587
|
+
// Check for specific StoreKit error types
|
|
588
|
+
if let nsError = error as NSError? {
|
|
589
|
+
switch nsError.domain {
|
|
590
|
+
case "SKErrorDomain":
|
|
591
|
+
// Handle SKError codes
|
|
592
|
+
switch nsError.code {
|
|
593
|
+
case 0: // SKError.unknown
|
|
594
|
+
errorCode = IapErrorCode.unknown
|
|
595
|
+
case 1: // SKError.clientInvalid
|
|
596
|
+
errorCode = IapErrorCode.serviceError
|
|
597
|
+
case 2: // SKError.paymentCancelled
|
|
598
|
+
errorCode = IapErrorCode.userCancelled
|
|
599
|
+
errorMessage = "User cancelled the purchase"
|
|
600
|
+
case 3: // SKError.paymentInvalid
|
|
601
|
+
errorCode = IapErrorCode.userError
|
|
602
|
+
case 4: // SKError.paymentNotAllowed
|
|
603
|
+
errorCode = IapErrorCode.userError
|
|
604
|
+
errorMessage = "Payment not allowed"
|
|
605
|
+
case 5: // SKError.storeProductNotAvailable
|
|
606
|
+
errorCode = IapErrorCode.itemUnavailable
|
|
607
|
+
case 6: // SKError.cloudServicePermissionDenied
|
|
608
|
+
errorCode = IapErrorCode.serviceError
|
|
609
|
+
case 7: // SKError.cloudServiceNetworkConnectionFailed
|
|
610
|
+
errorCode = IapErrorCode.networkError
|
|
611
|
+
case 8: // SKError.cloudServiceRevoked
|
|
612
|
+
errorCode = IapErrorCode.serviceError
|
|
613
|
+
default:
|
|
614
|
+
errorCode = IapErrorCode.purchaseError
|
|
615
|
+
}
|
|
616
|
+
case "NSURLErrorDomain":
|
|
617
|
+
errorCode = IapErrorCode.networkError
|
|
618
|
+
errorMessage = "Network error: \(error.localizedDescription)"
|
|
619
|
+
default:
|
|
620
|
+
errorCode = IapErrorCode.purchaseError
|
|
621
|
+
}
|
|
622
|
+
} else if error.localizedDescription.lowercased().contains("network") {
|
|
623
|
+
errorCode = IapErrorCode.networkError
|
|
624
|
+
} else if error.localizedDescription.lowercased().contains("cancelled") {
|
|
625
|
+
errorCode = IapErrorCode.userCancelled
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
let errorData = [
|
|
629
|
+
"responseCode": errorCode,
|
|
630
|
+
"debugMessage": "Purchase failed: \(error.localizedDescription)",
|
|
631
|
+
"code": errorCode,
|
|
632
|
+
"message": errorMessage,
|
|
633
|
+
"productId": sku,
|
|
634
|
+
]
|
|
635
|
+
self.sendEvent(IapEvent.PurchaseError, errorData)
|
|
636
|
+
throw Exception(name: "ExpoIapModule", description: errorMessage, code: errorCode)
|
|
550
637
|
}
|
|
551
638
|
} else {
|
|
639
|
+
let errorData = [
|
|
640
|
+
"responseCode": IapErrorCode.itemUnavailable,
|
|
641
|
+
"debugMessage": "Invalid product ID",
|
|
642
|
+
"code": IapErrorCode.itemUnavailable,
|
|
643
|
+
"message": "Invalid product ID",
|
|
644
|
+
"productId": sku,
|
|
645
|
+
]
|
|
646
|
+
self.sendEvent(IapEvent.PurchaseError, errorData)
|
|
552
647
|
throw Exception(name: "ExpoIapModule", description: "Invalid product ID", code: IapErrorCode.itemUnavailable)
|
|
553
648
|
}
|
|
554
649
|
}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/withIAP.ts"],"version":"5.
|
|
1
|
+
{"root":["./src/withIAP.ts"],"version":"5.9.2"}
|
package/src/ExpoIap.types.ts
CHANGED
|
@@ -28,8 +28,9 @@ export type ProductBase = {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
export type PurchaseBase = {
|
|
31
|
-
id: string;
|
|
32
|
-
|
|
31
|
+
id: string; // Transaction identifier - used by finishTransaction
|
|
32
|
+
productId: string; // Product identifier - which product was purchased
|
|
33
|
+
transactionId?: string; // @deprecated - use id instead
|
|
33
34
|
transactionDate: number;
|
|
34
35
|
transactionReceipt: string;
|
|
35
36
|
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {Platform} from 'react-native';
|
|
2
|
+
import {getAvailablePurchases} from '../index';
|
|
3
|
+
|
|
4
|
+
export interface ActiveSubscription {
|
|
5
|
+
productId: string;
|
|
6
|
+
isActive: boolean;
|
|
7
|
+
expirationDateIOS?: Date;
|
|
8
|
+
autoRenewingAndroid?: boolean;
|
|
9
|
+
environmentIOS?: string;
|
|
10
|
+
willExpireSoon?: boolean;
|
|
11
|
+
daysUntilExpirationIOS?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get all active subscriptions with detailed information
|
|
16
|
+
* @param subscriptionIds - Optional array of subscription product IDs to filter. If not provided, returns all active subscriptions.
|
|
17
|
+
* @returns Promise<ActiveSubscription[]> array of active subscriptions with details
|
|
18
|
+
*/
|
|
19
|
+
export const getActiveSubscriptions = async (
|
|
20
|
+
subscriptionIds?: string[],
|
|
21
|
+
): Promise<ActiveSubscription[]> => {
|
|
22
|
+
try {
|
|
23
|
+
const purchases = await getAvailablePurchases();
|
|
24
|
+
const currentTime = Date.now();
|
|
25
|
+
const activeSubscriptions: ActiveSubscription[] = [];
|
|
26
|
+
|
|
27
|
+
// Filter purchases to find active subscriptions
|
|
28
|
+
const filteredPurchases = purchases.filter((purchase) => {
|
|
29
|
+
// If specific IDs provided, filter by them
|
|
30
|
+
if (subscriptionIds && subscriptionIds.length > 0) {
|
|
31
|
+
if (!subscriptionIds.includes(purchase.id)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check if this purchase has subscription-specific fields
|
|
37
|
+
const hasSubscriptionFields =
|
|
38
|
+
('expirationDateIos' in purchase && purchase.expirationDateIos) ||
|
|
39
|
+
'autoRenewingAndroid' in purchase;
|
|
40
|
+
|
|
41
|
+
if (!hasSubscriptionFields) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check if it's actually active
|
|
46
|
+
if (Platform.OS === 'ios') {
|
|
47
|
+
if ('expirationDateIos' in purchase && purchase.expirationDateIos) {
|
|
48
|
+
return purchase.expirationDateIos > currentTime;
|
|
49
|
+
}
|
|
50
|
+
if (
|
|
51
|
+
'environmentIos' in purchase &&
|
|
52
|
+
purchase.environmentIos === 'Sandbox'
|
|
53
|
+
) {
|
|
54
|
+
const dayInMs = 24 * 60 * 60 * 1000;
|
|
55
|
+
if (
|
|
56
|
+
purchase.transactionDate &&
|
|
57
|
+
currentTime - purchase.transactionDate < dayInMs
|
|
58
|
+
) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} else if (Platform.OS === 'android') {
|
|
63
|
+
// For Android, if it's in the purchases list, it's active
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return false;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Convert to ActiveSubscription format
|
|
71
|
+
for (const purchase of filteredPurchases) {
|
|
72
|
+
const subscription: ActiveSubscription = {
|
|
73
|
+
productId: purchase.id,
|
|
74
|
+
isActive: true,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Add platform-specific details
|
|
78
|
+
if (Platform.OS === 'ios') {
|
|
79
|
+
if ('expirationDateIos' in purchase && purchase.expirationDateIos) {
|
|
80
|
+
const expirationDate = new Date(purchase.expirationDateIos);
|
|
81
|
+
subscription.expirationDateIOS = expirationDate;
|
|
82
|
+
|
|
83
|
+
// Calculate days until expiration
|
|
84
|
+
const daysUntilExpiration = Math.floor(
|
|
85
|
+
(purchase.expirationDateIos - currentTime) / (1000 * 60 * 60 * 24),
|
|
86
|
+
);
|
|
87
|
+
subscription.daysUntilExpirationIOS = daysUntilExpiration;
|
|
88
|
+
subscription.willExpireSoon = daysUntilExpiration <= 7;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if ('environmentIos' in purchase) {
|
|
92
|
+
subscription.environmentIOS = purchase.environmentIos;
|
|
93
|
+
}
|
|
94
|
+
} else if (Platform.OS === 'android') {
|
|
95
|
+
if ('autoRenewingAndroid' in purchase) {
|
|
96
|
+
subscription.autoRenewingAndroid = purchase.autoRenewingAndroid;
|
|
97
|
+
// If auto-renewing is false, subscription will expire soon
|
|
98
|
+
subscription.willExpireSoon = !purchase.autoRenewingAndroid;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
activeSubscriptions.push(subscription);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return activeSubscriptions;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error('Error getting active subscriptions:', error);
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if user has any active subscriptions
|
|
114
|
+
* @param subscriptionIds - Optional array of subscription product IDs to check. If not provided, checks all subscriptions.
|
|
115
|
+
* @returns Promise<boolean> true if user has at least one active subscription
|
|
116
|
+
*/
|
|
117
|
+
export const hasActiveSubscriptions = async (
|
|
118
|
+
subscriptionIds?: string[],
|
|
119
|
+
): Promise<boolean> => {
|
|
120
|
+
const subscriptions = await getActiveSubscriptions(subscriptionIds);
|
|
121
|
+
return subscriptions.length > 0;
|
|
122
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,13 @@ export * from './modules/android';
|
|
|
36
36
|
export * from './modules/ios';
|
|
37
37
|
export type {AppTransactionIOS} from './types/ExpoIapIos.types';
|
|
38
38
|
|
|
39
|
+
// Export subscription helpers
|
|
40
|
+
export {
|
|
41
|
+
getActiveSubscriptions,
|
|
42
|
+
hasActiveSubscriptions,
|
|
43
|
+
type ActiveSubscription,
|
|
44
|
+
} from './helpers/subscription';
|
|
45
|
+
|
|
39
46
|
// Get the native constant value
|
|
40
47
|
export const PI = ExpoIapModule.PI;
|
|
41
48
|
|
|
@@ -326,8 +333,9 @@ export const getAvailablePurchases = ({
|
|
|
326
333
|
),
|
|
327
334
|
android: async () => {
|
|
328
335
|
const products = await ExpoIapModule.getAvailableItemsByType('inapp');
|
|
329
|
-
const subscriptions =
|
|
330
|
-
|
|
336
|
+
const subscriptions = await ExpoIapModule.getAvailableItemsByType(
|
|
337
|
+
'subs',
|
|
338
|
+
);
|
|
331
339
|
return products.concat(subscriptions);
|
|
332
340
|
},
|
|
333
341
|
}) || (() => Promise.resolve([]))
|
|
@@ -555,10 +563,10 @@ export const finishTransaction = ({
|
|
|
555
563
|
return (
|
|
556
564
|
Platform.select({
|
|
557
565
|
ios: async () => {
|
|
558
|
-
const transactionId = purchase.
|
|
566
|
+
const transactionId = purchase.id;
|
|
559
567
|
if (!transactionId) {
|
|
560
568
|
return Promise.reject(
|
|
561
|
-
new Error('
|
|
569
|
+
new Error('purchase.id required to finish iOS transaction'),
|
|
562
570
|
);
|
|
563
571
|
}
|
|
564
572
|
await ExpoIapModule.finishTransaction(transactionId);
|
package/src/useIap.ts
CHANGED
|
@@ -16,6 +16,9 @@ import {
|
|
|
16
16
|
requestPurchase as requestPurchaseInternal,
|
|
17
17
|
requestProducts,
|
|
18
18
|
validateReceipt as validateReceiptInternal,
|
|
19
|
+
getActiveSubscriptions,
|
|
20
|
+
hasActiveSubscriptions,
|
|
21
|
+
type ActiveSubscription,
|
|
19
22
|
} from './';
|
|
20
23
|
import {
|
|
21
24
|
syncIOS,
|
|
@@ -47,6 +50,7 @@ type UseIap = {
|
|
|
47
50
|
currentPurchase?: ProductPurchase;
|
|
48
51
|
currentPurchaseError?: PurchaseError;
|
|
49
52
|
promotedProductIOS?: Product;
|
|
53
|
+
activeSubscriptions: ActiveSubscription[];
|
|
50
54
|
clearCurrentPurchase: () => void;
|
|
51
55
|
clearCurrentPurchaseError: () => void;
|
|
52
56
|
finishTransaction: ({
|
|
@@ -88,6 +92,10 @@ type UseIap = {
|
|
|
88
92
|
restorePurchases: () => Promise<void>; // 구매 복원 함수 추가
|
|
89
93
|
getPromotedProductIOS: () => Promise<any | null>;
|
|
90
94
|
buyPromotedProductIOS: () => Promise<void>;
|
|
95
|
+
getActiveSubscriptions: (
|
|
96
|
+
subscriptionIds?: string[],
|
|
97
|
+
) => Promise<ActiveSubscription[]>;
|
|
98
|
+
hasActiveSubscriptions: (subscriptionIds?: string[]) => Promise<boolean>;
|
|
91
99
|
};
|
|
92
100
|
|
|
93
101
|
export interface UseIAPOptions {
|
|
@@ -116,6 +124,9 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
116
124
|
const [currentPurchaseError, setCurrentPurchaseError] =
|
|
117
125
|
useState<PurchaseError>();
|
|
118
126
|
const [promotedProductIdIOS] = useState<string>();
|
|
127
|
+
const [activeSubscriptions, setActiveSubscriptions] = useState<
|
|
128
|
+
ActiveSubscription[]
|
|
129
|
+
>([]);
|
|
119
130
|
|
|
120
131
|
const optionsRef = useRef<UseIAPOptions | undefined>(options);
|
|
121
132
|
|
|
@@ -241,6 +252,33 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
241
252
|
}
|
|
242
253
|
}, []);
|
|
243
254
|
|
|
255
|
+
const getActiveSubscriptionsInternal = useCallback(
|
|
256
|
+
async (subscriptionIds?: string[]): Promise<ActiveSubscription[]> => {
|
|
257
|
+
try {
|
|
258
|
+
const result = await getActiveSubscriptions(subscriptionIds);
|
|
259
|
+
setActiveSubscriptions(result);
|
|
260
|
+
return result;
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error('Error getting active subscriptions:', error);
|
|
263
|
+
setActiveSubscriptions([]);
|
|
264
|
+
return [];
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
[],
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const hasActiveSubscriptionsInternal = useCallback(
|
|
271
|
+
async (subscriptionIds?: string[]): Promise<boolean> => {
|
|
272
|
+
try {
|
|
273
|
+
return await hasActiveSubscriptions(subscriptionIds);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error('Error checking active subscriptions:', error);
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
[],
|
|
280
|
+
);
|
|
281
|
+
|
|
244
282
|
const getPurchaseHistoriesInternal = useCallback(async (): Promise<void> => {
|
|
245
283
|
setPurchaseHistories(await getPurchaseHistories());
|
|
246
284
|
}, []);
|
|
@@ -408,6 +446,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
408
446
|
currentPurchase,
|
|
409
447
|
currentPurchaseError,
|
|
410
448
|
promotedProductIOS,
|
|
449
|
+
activeSubscriptions,
|
|
411
450
|
clearCurrentPurchase,
|
|
412
451
|
clearCurrentPurchaseError,
|
|
413
452
|
getAvailablePurchases: getAvailablePurchasesInternal,
|
|
@@ -420,5 +459,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
420
459
|
getSubscriptions: getSubscriptionsInternal,
|
|
421
460
|
getPromotedProductIOS,
|
|
422
461
|
buyPromotedProductIOS,
|
|
462
|
+
getActiveSubscriptions: getActiveSubscriptionsInternal,
|
|
463
|
+
hasActiveSubscriptions: hasActiveSubscriptionsInternal,
|
|
423
464
|
};
|
|
424
465
|
}
|
package/bun.lockb
DELETED
|
Binary file
|