@umituz/react-native-subscription 2.4.0 → 2.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -35,6 +35,7 @@
35
35
  "@umituz/react-native-firestore": "latest",
36
36
  "@umituz/react-native-legal": "latest",
37
37
  "@umituz/react-native-localization": "latest",
38
+ "@umituz/react-native-sentry": "latest",
38
39
  "expo-constants": ">=16.0.0",
39
40
  "firebase": ">=10.0.0",
40
41
  "react": ">=18.2.0",
@@ -47,6 +48,7 @@
47
48
  "@umituz/react-native-firestore": "latest",
48
49
  "@umituz/react-native-legal": "latest",
49
50
  "@umituz/react-native-localization": "latest",
51
+ "@umituz/react-native-sentry": "latest",
50
52
  "@tanstack/react-query": "^5.0.0",
51
53
  "expo-constants": "~16.0.0",
52
54
  "firebase": "^10.0.0",
package/src/index.ts CHANGED
@@ -305,6 +305,12 @@ export {
305
305
  resetRevenueCatService,
306
306
  } from "./revenuecat/infrastructure/services/RevenueCatService";
307
307
 
308
+ export {
309
+ SubscriptionManager,
310
+ type SubscriptionManagerConfig,
311
+ type PremiumStatus,
312
+ } from "./revenuecat/infrastructure/managers/SubscriptionManager";
313
+
308
314
  // =============================================================================
309
315
  // REVENUECAT - Hooks
310
316
  // =============================================================================
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Package Handler
3
+ * Handles package operations (fetch, purchase, restore, premium status)
4
+ */
5
+
6
+ import type { PurchasesPackage } from "react-native-purchases";
7
+ import type { IRevenueCatService } from "../../application/ports/IRevenueCatService";
8
+ import { getPremiumEntitlement } from "../../domain/types/RevenueCatTypes";
9
+ import {
10
+ trackPackageError,
11
+ addPackageBreadcrumb,
12
+ trackPackageWarning,
13
+ } from "@umituz/react-native-sentry";
14
+
15
+ export interface PremiumStatus {
16
+ isPremium: boolean;
17
+ expirationDate: Date | null;
18
+ }
19
+
20
+ export class PackageHandler {
21
+ constructor(
22
+ private service: IRevenueCatService | null,
23
+ private entitlementId: string
24
+ ) {}
25
+
26
+ setService(service: IRevenueCatService | null): void {
27
+ this.service = service;
28
+ }
29
+
30
+ async fetchPackages(): Promise<PurchasesPackage[]> {
31
+ if (!this.service?.isInitialized()) {
32
+ trackPackageWarning("subscription", "Fetch packages called but not initialized", {});
33
+ return [];
34
+ }
35
+
36
+ try {
37
+ const offering = await this.service.fetchOfferings();
38
+
39
+ addPackageBreadcrumb("subscription", "Packages fetched", {
40
+ identifier: offering?.identifier,
41
+ count: offering?.availablePackages?.length ?? 0,
42
+ });
43
+
44
+ return offering?.availablePackages ?? [];
45
+ } catch (error) {
46
+ trackPackageError(error instanceof Error ? error : new Error(String(error)), {
47
+ packageName: "subscription",
48
+ operation: "fetch_packages",
49
+ });
50
+ return [];
51
+ }
52
+ }
53
+
54
+ async purchase(pkg: PurchasesPackage, userId: string): Promise<boolean> {
55
+ if (!this.service?.isInitialized()) {
56
+ trackPackageWarning("subscription", "Purchase attempted but not initialized", {
57
+ productId: pkg.product.identifier,
58
+ });
59
+ return false;
60
+ }
61
+
62
+ try {
63
+ const result = await this.service.purchasePackage(pkg, userId);
64
+ return result.success;
65
+ } catch (error) {
66
+ trackPackageError(error instanceof Error ? error : new Error(String(error)), {
67
+ packageName: "subscription",
68
+ operation: "purchase",
69
+ userId,
70
+ productId: pkg.product.identifier,
71
+ });
72
+ return false;
73
+ }
74
+ }
75
+
76
+ async restore(userId: string): Promise<boolean> {
77
+ if (!this.service?.isInitialized()) {
78
+ trackPackageWarning("subscription", "Restore attempted but not initialized", {});
79
+ return false;
80
+ }
81
+
82
+ try {
83
+ const result = await this.service.restorePurchases(userId);
84
+ return result.success;
85
+ } catch (error) {
86
+ trackPackageError(error instanceof Error ? error : new Error(String(error)), {
87
+ packageName: "subscription",
88
+ operation: "restore",
89
+ userId,
90
+ });
91
+ return false;
92
+ }
93
+ }
94
+
95
+ async checkPremiumStatus(userId: string): Promise<PremiumStatus> {
96
+ if (!this.service?.isInitialized()) {
97
+ return { isPremium: false, expirationDate: null };
98
+ }
99
+
100
+ try {
101
+ const restoreResult = await this.service.restorePurchases(userId);
102
+
103
+ if (restoreResult.customerInfo) {
104
+ const entitlement = getPremiumEntitlement(
105
+ restoreResult.customerInfo,
106
+ this.entitlementId
107
+ );
108
+ if (entitlement) {
109
+ return {
110
+ isPremium: true,
111
+ expirationDate: entitlement.expirationDate
112
+ ? new Date(entitlement.expirationDate)
113
+ : null,
114
+ };
115
+ }
116
+ }
117
+
118
+ return { isPremium: restoreResult.isPremium, expirationDate: null };
119
+ } catch (error) {
120
+ trackPackageError(error instanceof Error ? error : new Error(String(error)), {
121
+ packageName: "subscription",
122
+ operation: "check_premium_status",
123
+ userId,
124
+ });
125
+ return { isPremium: false, expirationDate: null };
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Subscription Manager
3
+ * Facade for subscription operations
4
+ * Coordinates UserIdProvider, InitializationCache, and PackageHandler
5
+ */
6
+
7
+ import type { PurchasesPackage } from "react-native-purchases";
8
+ import type { RevenueCatConfig } from "../../domain/value-objects/RevenueCatConfig";
9
+ import type { IRevenueCatService } from "../../application/ports/IRevenueCatService";
10
+ import { initializeRevenueCatService, getRevenueCatService } from "../services/RevenueCatService";
11
+ import { UserIdProvider } from "../utils/UserIdProvider";
12
+ import { InitializationCache } from "../utils/InitializationCache";
13
+ import { PackageHandler, type PremiumStatus } from "../handlers/PackageHandler";
14
+ import {
15
+ trackPackageError,
16
+ addPackageBreadcrumb,
17
+ trackPackageWarning,
18
+ } from "@umituz/react-native-sentry";
19
+
20
+ export interface SubscriptionManagerConfig {
21
+ config: RevenueCatConfig;
22
+ apiKey: string;
23
+ getAnonymousUserId?: () => Promise<string>;
24
+ }
25
+
26
+ class SubscriptionManagerImpl {
27
+ private managerConfig: SubscriptionManagerConfig | null = null;
28
+ private serviceInstance: IRevenueCatService | null = null;
29
+ private userIdProvider = new UserIdProvider();
30
+ private initCache = new InitializationCache();
31
+ private packageHandler: PackageHandler | null = null;
32
+
33
+ configure(config: SubscriptionManagerConfig): void {
34
+ this.managerConfig = config;
35
+ this.packageHandler = new PackageHandler(null, config.config.entitlementIdentifier);
36
+
37
+ if (config.getAnonymousUserId) {
38
+ this.userIdProvider.configure(config.getAnonymousUserId);
39
+ }
40
+
41
+ addPackageBreadcrumb("subscription", "Manager configured", {
42
+ entitlementId: config.config.entitlementIdentifier,
43
+ });
44
+ }
45
+
46
+ private ensureConfigured(): void {
47
+ if (!this.managerConfig || !this.packageHandler) {
48
+ const error = new Error("SubscriptionManager not configured");
49
+ trackPackageError(error, {
50
+ packageName: "subscription",
51
+ operation: "ensure_configured",
52
+ });
53
+ throw error;
54
+ }
55
+ }
56
+
57
+ private async performInitialization(userId: string): Promise<boolean> {
58
+ this.ensureConfigured();
59
+
60
+ try {
61
+ await initializeRevenueCatService(this.managerConfig!.config);
62
+ this.serviceInstance = getRevenueCatService();
63
+
64
+ if (!this.serviceInstance) {
65
+ trackPackageWarning("subscription", "Service instance not created", { userId });
66
+ return false;
67
+ }
68
+
69
+ this.packageHandler!.setService(this.serviceInstance);
70
+
71
+ const result = await this.serviceInstance.initialize(userId, this.managerConfig!.apiKey);
72
+ return result.success;
73
+ } catch (error) {
74
+ trackPackageError(error instanceof Error ? error : new Error(String(error)), {
75
+ packageName: "subscription",
76
+ operation: "initialize",
77
+ userId,
78
+ });
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ async initialize(userId?: string): Promise<boolean> {
84
+ this.ensureConfigured();
85
+
86
+ const effectiveUserId = userId || (await this.userIdProvider.getOrCreateAnonymousUserId());
87
+
88
+ if (!this.initCache.shouldReinitialize(effectiveUserId)) {
89
+ const existingPromise = this.initCache.getExistingPromise();
90
+ if (existingPromise) return existingPromise;
91
+ }
92
+
93
+ if (this.initCache.shouldReinitialize(effectiveUserId) && this.serviceInstance) {
94
+ await this.serviceInstance.reset();
95
+ }
96
+
97
+ const promise = this.performInitialization(effectiveUserId);
98
+ this.initCache.setPromise(promise, effectiveUserId);
99
+
100
+ return promise;
101
+ }
102
+
103
+ isInitialized(): boolean {
104
+ return this.serviceInstance?.isInitialized() ?? false;
105
+ }
106
+
107
+ isInitializedForUser(userId: string): boolean {
108
+ return this.serviceInstance?.isInitialized() === true &&
109
+ this.initCache.getCurrentUserId() === userId;
110
+ }
111
+
112
+ async getPackages(): Promise<PurchasesPackage[]> {
113
+ this.ensureConfigured();
114
+ if (!this.serviceInstance) {
115
+ this.serviceInstance = getRevenueCatService();
116
+ this.packageHandler!.setService(this.serviceInstance);
117
+ }
118
+ return this.packageHandler!.fetchPackages();
119
+ }
120
+
121
+ async purchasePackage(pkg: PurchasesPackage): Promise<boolean> {
122
+ this.ensureConfigured();
123
+ const userId = this.initCache.getCurrentUserId();
124
+ if (!userId) return false;
125
+ return this.packageHandler!.purchase(pkg, userId);
126
+ }
127
+
128
+ async restore(): Promise<boolean> {
129
+ this.ensureConfigured();
130
+ const userId = this.initCache.getCurrentUserId();
131
+ if (!userId) return false;
132
+ return this.packageHandler!.restore(userId);
133
+ }
134
+
135
+ async checkPremiumStatus(): Promise<PremiumStatus> {
136
+ this.ensureConfigured();
137
+ const userId = this.initCache.getCurrentUserId();
138
+ if (!userId) return { isPremium: false, expirationDate: null };
139
+ return this.packageHandler!.checkPremiumStatus(userId);
140
+ }
141
+
142
+ async reset(): Promise<void> {
143
+ if (this.serviceInstance) {
144
+ await this.serviceInstance.reset();
145
+ }
146
+ this.initCache.reset();
147
+ this.userIdProvider.reset();
148
+ this.serviceInstance = null;
149
+ addPackageBreadcrumb("subscription", "Manager reset completed", {});
150
+ }
151
+ }
152
+
153
+ export const SubscriptionManager = new SubscriptionManagerImpl();
@@ -18,6 +18,10 @@ import {
18
18
  syncPremiumStatus,
19
19
  notifyPurchaseCompleted,
20
20
  } from "../utils/PremiumStatusSyncer";
21
+ import {
22
+ trackPackageError,
23
+ addPackageBreadcrumb,
24
+ } from "@umituz/react-native-sentry";
21
25
 
22
26
  export interface PurchaseHandlerDeps {
23
27
  config: RevenueCatConfig;
@@ -42,12 +46,20 @@ export async function handlePurchase(
42
46
  pkg: PurchasesPackage,
43
47
  userId: string
44
48
  ): Promise<PurchaseResult> {
45
- if (__DEV__) {
46
- console.log("[RevenueCat] handlePurchase() called for:", pkg.product.identifier);
47
- }
49
+ addPackageBreadcrumb("subscription", "Purchase started", {
50
+ productId: pkg.product.identifier,
51
+ userId,
52
+ });
48
53
 
49
54
  if (!deps.isInitialized()) {
50
- throw new RevenueCatInitializationError();
55
+ const error = new RevenueCatInitializationError();
56
+ trackPackageError(error, {
57
+ packageName: "subscription",
58
+ operation: "purchase",
59
+ userId,
60
+ productId: pkg.product.identifier,
61
+ });
62
+ throw error;
51
63
  }
52
64
 
53
65
 
@@ -85,15 +97,35 @@ export async function handlePurchase(
85
97
  return { success: true, isPremium: true, customerInfo };
86
98
  }
87
99
 
88
- throw new RevenueCatPurchaseError(
100
+ const entitlementError = new RevenueCatPurchaseError(
89
101
  "Purchase completed but premium entitlement not active",
90
102
  pkg.product.identifier
91
103
  );
104
+ trackPackageError(entitlementError, {
105
+ packageName: "subscription",
106
+ operation: "purchase",
107
+ userId,
108
+ productId: pkg.product.identifier,
109
+ reason: "entitlement_not_active",
110
+ });
111
+ throw entitlementError;
92
112
  } catch (error) {
93
113
  if (isUserCancelledError(error)) {
114
+ addPackageBreadcrumb("subscription", "Purchase cancelled by user", {
115
+ productId: pkg.product.identifier,
116
+ userId,
117
+ });
94
118
  return { success: false, isPremium: false };
95
119
  }
96
120
  const errorMessage = getErrorMessage(error, "Purchase failed");
97
- throw new RevenueCatPurchaseError(errorMessage, pkg.product.identifier);
121
+ const purchaseError = new RevenueCatPurchaseError(errorMessage, pkg.product.identifier);
122
+ trackPackageError(purchaseError, {
123
+ packageName: "subscription",
124
+ operation: "purchase",
125
+ userId,
126
+ productId: pkg.product.identifier,
127
+ originalError: error instanceof Error ? error.message : String(error),
128
+ });
129
+ throw purchaseError;
98
130
  }
99
131
  }
@@ -15,6 +15,10 @@ import {
15
15
  syncPremiumStatus,
16
16
  notifyRestoreCompleted,
17
17
  } from "../utils/PremiumStatusSyncer";
18
+ import {
19
+ trackPackageError,
20
+ addPackageBreadcrumb,
21
+ } from "@umituz/react-native-sentry";
18
22
 
19
23
  export interface RestoreHandlerDeps {
20
24
  config: RevenueCatConfig;
@@ -29,12 +33,18 @@ export async function handleRestore(
29
33
  deps: RestoreHandlerDeps,
30
34
  userId: string
31
35
  ): Promise<RestoreResult> {
36
+ addPackageBreadcrumb("subscription", "Restore started", { userId });
37
+
32
38
  if (!deps.isInitialized()) {
33
- throw new RevenueCatInitializationError();
39
+ const error = new RevenueCatInitializationError();
40
+ trackPackageError(error, {
41
+ packageName: "subscription",
42
+ operation: "restore",
43
+ userId,
44
+ });
45
+ throw error;
34
46
  }
35
47
 
36
-
37
-
38
48
  try {
39
49
  const customerInfo = await Purchases.restorePurchases();
40
50
  const entitlementIdentifier = deps.config.entitlementIdentifier;
@@ -42,12 +52,28 @@ export async function handleRestore(
42
52
 
43
53
  if (isPremium) {
44
54
  await syncPremiumStatus(deps.config, userId, customerInfo);
55
+ addPackageBreadcrumb("subscription", "Restore successful - premium active", {
56
+ userId,
57
+ entitlementId: entitlementIdentifier,
58
+ });
59
+ } else {
60
+ addPackageBreadcrumb("subscription", "Restore completed - no premium found", {
61
+ userId,
62
+ });
45
63
  }
64
+
46
65
  await notifyRestoreCompleted(deps.config, userId, isPremium, customerInfo);
47
66
 
48
67
  return { success: isPremium, isPremium, customerInfo };
49
68
  } catch (error) {
50
69
  const errorMessage = getErrorMessage(error, "Restore failed");
51
- throw new RevenueCatRestoreError(errorMessage);
70
+ const restoreError = new RevenueCatRestoreError(errorMessage);
71
+ trackPackageError(restoreError, {
72
+ packageName: "subscription",
73
+ operation: "restore",
74
+ userId,
75
+ originalError: error instanceof Error ? error.message : String(error),
76
+ });
77
+ throw restoreError;
52
78
  }
53
79
  }
@@ -19,6 +19,11 @@ import { handlePurchase } from "./PurchaseHandler";
19
19
  import { handleRestore } from "./RestoreHandler";
20
20
  import { CustomerInfoListenerManager } from "./CustomerInfoListenerManager";
21
21
  import { ServiceStateManager } from "./ServiceStateManager";
22
+ import {
23
+ trackPackageError,
24
+ addPackageBreadcrumb,
25
+ trackPackageWarning,
26
+ } from "@umituz/react-native-sentry";
22
27
 
23
28
  export class RevenueCatService implements IRevenueCatService {
24
29
  private stateManager: ServiceStateManager;
@@ -50,31 +55,46 @@ export class RevenueCatService implements IRevenueCatService {
50
55
  async initialize(userId: string, apiKey?: string): Promise<InitializeResult> {
51
56
  // If already initialized for this user, return success immediately
52
57
  if (this.isInitialized() && this.getCurrentUserId() === userId) {
53
- if (__DEV__) {
54
- console.log("[RevenueCat] Already initialized for user:", userId);
55
- }
56
- return { success: true, offering: (await this.fetchOfferings()), hasPremium: false }; // fetchOfferings handles cache usually
58
+ addPackageBreadcrumb("subscription", "Already initialized", { userId });
59
+ return { success: true, offering: (await this.fetchOfferings()), hasPremium: false };
57
60
  }
58
61
 
59
- const result = await initializeSDK(
60
- {
61
- config: this.stateManager.getConfig(),
62
- isUsingTestStore: () => this.isUsingTestStore(),
63
- isInitialized: () => this.isInitialized(),
64
- getCurrentUserId: () => this.stateManager.getCurrentUserId(),
65
- setInitialized: (value) => this.stateManager.setInitialized(value),
66
- setCurrentUserId: (id) => this.stateManager.setCurrentUserId(id),
67
- },
68
- userId,
69
- apiKey
70
- );
62
+ addPackageBreadcrumb("subscription", "Initialization started", { userId });
71
63
 
72
- if (result.success) {
73
- this.listenerManager.setUserId(userId);
74
- this.listenerManager.setupListener(this.stateManager.getConfig());
75
- }
64
+ try {
65
+ const result = await initializeSDK(
66
+ {
67
+ config: this.stateManager.getConfig(),
68
+ isUsingTestStore: () => this.isUsingTestStore(),
69
+ isInitialized: () => this.isInitialized(),
70
+ getCurrentUserId: () => this.stateManager.getCurrentUserId(),
71
+ setInitialized: (value) => this.stateManager.setInitialized(value),
72
+ setCurrentUserId: (id) => this.stateManager.setCurrentUserId(id),
73
+ },
74
+ userId,
75
+ apiKey
76
+ );
77
+
78
+ if (result.success) {
79
+ this.listenerManager.setUserId(userId);
80
+ this.listenerManager.setupListener(this.stateManager.getConfig());
81
+ addPackageBreadcrumb("subscription", "Initialization successful", { userId });
82
+ } else {
83
+ trackPackageWarning("subscription", "Initialization failed", {
84
+ userId,
85
+ hasOffering: !!result.offering,
86
+ });
87
+ }
76
88
 
77
- return result;
89
+ return result;
90
+ } catch (error) {
91
+ trackPackageError(error instanceof Error ? error : new Error(String(error)), {
92
+ packageName: "subscription",
93
+ operation: "initialize",
94
+ userId,
95
+ });
96
+ throw error;
97
+ }
78
98
  }
79
99
 
80
100
  async fetchOfferings(): Promise<PurchasesOffering | null> {
@@ -113,13 +133,20 @@ export class RevenueCatService implements IRevenueCatService {
113
133
  async reset(): Promise<void> {
114
134
  if (!this.isInitialized()) return;
115
135
 
136
+ addPackageBreadcrumb("subscription", "Reset started", {
137
+ userId: this.getCurrentUserId(),
138
+ });
139
+
116
140
  this.listenerManager.destroy();
117
141
 
118
142
  try {
119
143
  await Purchases.logOut();
120
144
  this.stateManager.setInitialized(false);
121
- } catch {
122
- // Reset errors are non-critical
145
+ addPackageBreadcrumb("subscription", "Reset successful", {});
146
+ } catch (error) {
147
+ trackPackageWarning("subscription", "Reset failed (non-critical)", {
148
+ error: error instanceof Error ? error.message : String(error),
149
+ });
123
150
  }
124
151
  }
125
152
  }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Initialization Cache
3
+ * Manages promise caching and user state for initialization
4
+ */
5
+
6
+ import { addPackageBreadcrumb } from "@umituz/react-native-sentry";
7
+
8
+ export class InitializationCache {
9
+ private initPromise: Promise<boolean> | null = null;
10
+ private currentUserId: string | null = null;
11
+
12
+ shouldReinitialize(userId: string): boolean {
13
+ if (!this.initPromise) {
14
+ return true;
15
+ }
16
+
17
+ if (this.currentUserId !== userId) {
18
+ addPackageBreadcrumb("subscription", "User changed, reinitialize needed", {
19
+ oldUserId: this.currentUserId,
20
+ newUserId: userId,
21
+ });
22
+ return true;
23
+ }
24
+
25
+ return false;
26
+ }
27
+
28
+ getExistingPromise(): Promise<boolean> | null {
29
+ return this.initPromise;
30
+ }
31
+
32
+ setPromise(promise: Promise<boolean>, userId: string): void {
33
+ this.initPromise = promise;
34
+ this.currentUserId = userId;
35
+ }
36
+
37
+ getCurrentUserId(): string | null {
38
+ return this.currentUserId;
39
+ }
40
+
41
+ reset(): void {
42
+ this.initPromise = null;
43
+ this.currentUserId = null;
44
+ }
45
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * User ID Provider
3
+ * Manages user ID retrieval (anonymous or authenticated)
4
+ */
5
+
6
+ import {
7
+ trackPackageError,
8
+ addPackageBreadcrumb,
9
+ } from "@umituz/react-native-sentry";
10
+
11
+ export class UserIdProvider {
12
+ private cachedAnonUserId: string | null = null;
13
+ private getAnonymousUserIdFn: (() => Promise<string>) | null = null;
14
+
15
+ configure(getAnonymousUserId: () => Promise<string>): void {
16
+ this.getAnonymousUserIdFn = getAnonymousUserId;
17
+ addPackageBreadcrumb("subscription", "UserIdProvider configured", {});
18
+ }
19
+
20
+ async getOrCreateAnonymousUserId(): Promise<string> {
21
+ if (this.cachedAnonUserId) {
22
+ return this.cachedAnonUserId;
23
+ }
24
+
25
+ if (!this.getAnonymousUserIdFn) {
26
+ const error = new Error("Anonymous user ID provider not configured");
27
+ trackPackageError(error, {
28
+ packageName: "subscription",
29
+ operation: "get_anon_user_id",
30
+ });
31
+ throw error;
32
+ }
33
+
34
+ this.cachedAnonUserId = await this.getAnonymousUserIdFn();
35
+ addPackageBreadcrumb("subscription", "Anonymous user ID created", {
36
+ userId: this.cachedAnonUserId,
37
+ });
38
+
39
+ return this.cachedAnonUserId;
40
+ }
41
+
42
+ reset(): void {
43
+ this.cachedAnonUserId = null;
44
+ }
45
+ }