@umituz/react-native-auth 3.2.11 → 3.2.12
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 +1 -1
- package/src/__tests__/services/AnonymousModeService.test.ts +197 -0
- package/src/__tests__/services/AuthPackage.test.ts +18 -18
- package/src/application/ports/IAuthService.ts +4 -4
- package/src/domain/utils/{guestNameGenerator.ts → anonymousNameGenerator.ts} +11 -11
- package/src/index.ts +3 -4
- package/src/infrastructure/services/{GuestModeService.ts → AnonymousModeService.ts} +19 -19
- package/src/infrastructure/services/AuthEventService.ts +6 -6
- package/src/infrastructure/services/AuthPackage.ts +4 -4
- package/src/infrastructure/services/AuthService.ts +19 -30
- package/src/infrastructure/storage/{GuestModeStorage.ts → AnonymousModeStorage.ts} +9 -9
- package/src/infrastructure/utils/AuthEventEmitter.ts +2 -2
- package/src/presentation/components/AuthBottomSheet.tsx +3 -3
- package/src/presentation/hooks/mutations/useAuthMutations.ts +2 -2
- package/src/presentation/hooks/useAuth.ts +17 -26
- package/src/presentation/hooks/useAuthRequired.ts +1 -1
- package/src/presentation/hooks/useLoginForm.ts +7 -7
- package/src/presentation/hooks/useUserProfile.ts +8 -8
- package/src/presentation/stores/auth.selectors.ts +6 -6
- package/src/presentation/stores/authStore.ts +12 -18
- package/src/presentation/stores/initializeAuthListener.ts +5 -5
- package/src/types/auth-store.types.ts +5 -5
- package/src/__tests__/services/GuestModeService.test.ts +0 -194
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-auth",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.12",
|
|
4
4
|
"description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design with dependency injection, configurable validation, and comprehensive error handling.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AnonymousModeService Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { AnonymousModeService } from '../../infrastructure/services/AnonymousModeService';
|
|
6
|
+
import type { IStorageProvider } from '../../infrastructure/services/AuthPackage';
|
|
7
|
+
|
|
8
|
+
describe('AnonymousModeService', () => {
|
|
9
|
+
let anonymousModeService: AnonymousModeService;
|
|
10
|
+
let mockStorageProvider: jest.Mocked<IStorageProvider>;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
mockStorageProvider = {
|
|
14
|
+
get: jest.fn(),
|
|
15
|
+
set: jest.fn(),
|
|
16
|
+
remove: jest.fn(),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
anonymousModeService = new AnonymousModeService('@test_anonymous_mode');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('constructor', () => {
|
|
23
|
+
it('should use default storage key when none provided', () => {
|
|
24
|
+
const service = new AnonymousModeService();
|
|
25
|
+
expect(service.getIsAnonymousMode()).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should use custom storage key when provided', () => {
|
|
29
|
+
const service = new AnonymousModeService('@custom_key');
|
|
30
|
+
expect(service.getIsAnonymousMode()).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('load', () => {
|
|
35
|
+
it('should load false when storage returns null', async () => {
|
|
36
|
+
mockStorageProvider.get.mockResolvedValue(null);
|
|
37
|
+
|
|
38
|
+
const result = await anonymousModeService.load(mockStorageProvider);
|
|
39
|
+
|
|
40
|
+
expect(result).toBe(false);
|
|
41
|
+
expect(mockStorageProvider.get).toHaveBeenCalledWith('@test_anonymous_mode');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should load false when storage returns "false"', async () => {
|
|
45
|
+
mockStorageProvider.get.mockResolvedValue('false');
|
|
46
|
+
|
|
47
|
+
const result = await anonymousModeService.load(mockStorageProvider);
|
|
48
|
+
|
|
49
|
+
expect(result).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should load true when storage returns "true"', async () => {
|
|
53
|
+
mockStorageProvider.get.mockResolvedValue('true');
|
|
54
|
+
|
|
55
|
+
const result = await anonymousModeService.load(mockStorageProvider);
|
|
56
|
+
|
|
57
|
+
expect(result).toBe(true);
|
|
58
|
+
expect(anonymousModeService.getIsAnonymousMode()).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should handle storage errors gracefully', async () => {
|
|
62
|
+
mockStorageProvider.get.mockRejectedValue(new Error('Storage error'));
|
|
63
|
+
|
|
64
|
+
const result = await anonymousModeService.load(mockStorageProvider);
|
|
65
|
+
|
|
66
|
+
expect(result).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('save', () => {
|
|
71
|
+
it('should save true to storage when anonymous mode is enabled', async () => {
|
|
72
|
+
anonymousModeService.setAnonymousMode(true);
|
|
73
|
+
|
|
74
|
+
await anonymousModeService.save(mockStorageProvider);
|
|
75
|
+
|
|
76
|
+
expect(mockStorageProvider.set).toHaveBeenCalledWith('@test_anonymous_mode', 'true');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should save false to storage when anonymous mode is disabled', async () => {
|
|
80
|
+
anonymousModeService.setAnonymousMode(false);
|
|
81
|
+
|
|
82
|
+
await anonymousModeService.save(mockStorageProvider);
|
|
83
|
+
|
|
84
|
+
expect(mockStorageProvider.set).toHaveBeenCalledWith('@test_anonymous_mode', 'false');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should handle storage errors gracefully', async () => {
|
|
88
|
+
anonymousModeService.setAnonymousMode(true);
|
|
89
|
+
mockStorageProvider.set.mockRejectedValue(new Error('Storage error'));
|
|
90
|
+
|
|
91
|
+
await expect(anonymousModeService.save(mockStorageProvider)).resolves.not.toThrow();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('clear', () => {
|
|
96
|
+
it('should clear anonymous mode and remove from storage', async () => {
|
|
97
|
+
anonymousModeService.setAnonymousMode(true);
|
|
98
|
+
|
|
99
|
+
await anonymousModeService.clear(mockStorageProvider);
|
|
100
|
+
|
|
101
|
+
expect(anonymousModeService.getIsAnonymousMode()).toBe(false);
|
|
102
|
+
expect(mockStorageProvider.remove).toHaveBeenCalledWith('@test_anonymous_mode');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should handle storage errors gracefully', async () => {
|
|
106
|
+
mockStorageProvider.remove.mockRejectedValue(new Error('Storage error'));
|
|
107
|
+
|
|
108
|
+
await expect(anonymousModeService.clear(mockStorageProvider)).resolves.not.toThrow();
|
|
109
|
+
expect(anonymousModeService.getIsAnonymousMode()).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('enable', () => {
|
|
114
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
115
|
+
let mockAuthProvider: any;
|
|
116
|
+
|
|
117
|
+
beforeEach(() => {
|
|
118
|
+
mockAuthProvider = {
|
|
119
|
+
getCurrentUser: jest.fn(),
|
|
120
|
+
signOut: jest.fn(),
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should enable anonymous mode without provider', async () => {
|
|
125
|
+
await anonymousModeService.enable(mockStorageProvider);
|
|
126
|
+
|
|
127
|
+
expect(anonymousModeService.getIsAnonymousMode()).toBe(true);
|
|
128
|
+
expect(mockStorageProvider.set).toHaveBeenCalledWith('@test_anonymous_mode', 'true');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should sign out provider if user is logged in', async () => {
|
|
132
|
+
mockAuthProvider.getCurrentUser.mockReturnValue({ uid: 'test-user' });
|
|
133
|
+
mockAuthProvider.signOut.mockResolvedValue(undefined);
|
|
134
|
+
|
|
135
|
+
await anonymousModeService.enable(mockStorageProvider, mockAuthProvider);
|
|
136
|
+
|
|
137
|
+
expect(mockAuthProvider.signOut).toHaveBeenCalled();
|
|
138
|
+
expect(anonymousModeService.getIsAnonymousMode()).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should ignore sign out errors', async () => {
|
|
142
|
+
mockAuthProvider.getCurrentUser.mockReturnValue({ uid: 'test-user' });
|
|
143
|
+
mockAuthProvider.signOut.mockRejectedValue(new Error('Sign out error'));
|
|
144
|
+
|
|
145
|
+
await expect(anonymousModeService.enable(mockStorageProvider, mockAuthProvider)).resolves.not.toThrow();
|
|
146
|
+
expect(anonymousModeService.getIsAnonymousMode()).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('wrapAuthStateCallback', () => {
|
|
151
|
+
it('should call callback with user when not in anonymous mode', () => {
|
|
152
|
+
const callback = jest.fn();
|
|
153
|
+
const wrappedCallback = anonymousModeService.wrapAuthStateCallback(callback);
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
155
|
+
const mockUser = { uid: 'test-user' } as any;
|
|
156
|
+
|
|
157
|
+
wrappedCallback(mockUser);
|
|
158
|
+
|
|
159
|
+
expect(callback).toHaveBeenCalledWith(mockUser);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should call callback with null when in anonymous mode', () => {
|
|
163
|
+
anonymousModeService.setAnonymousMode(true);
|
|
164
|
+
const callback = jest.fn();
|
|
165
|
+
const wrappedCallback = anonymousModeService.wrapAuthStateCallback(callback);
|
|
166
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
167
|
+
const mockUser = { uid: 'test-user' } as any;
|
|
168
|
+
|
|
169
|
+
wrappedCallback(mockUser);
|
|
170
|
+
|
|
171
|
+
expect(callback).toHaveBeenCalledWith(null);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should call callback with null when user is null', () => {
|
|
175
|
+
const callback = jest.fn();
|
|
176
|
+
const wrappedCallback = anonymousModeService.wrapAuthStateCallback(callback);
|
|
177
|
+
|
|
178
|
+
wrappedCallback(null);
|
|
179
|
+
|
|
180
|
+
expect(callback).toHaveBeenCalledWith(null);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('getIsAnonymousMode and setAnonymousMode', () => {
|
|
185
|
+
it('should return initial anonymous mode state', () => {
|
|
186
|
+
expect(anonymousModeService.getIsAnonymousMode()).toBe(false);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should set and get anonymous mode state', () => {
|
|
190
|
+
anonymousModeService.setAnonymousMode(true);
|
|
191
|
+
expect(anonymousModeService.getIsAnonymousMode()).toBe(true);
|
|
192
|
+
|
|
193
|
+
anonymousModeService.setAnonymousMode(false);
|
|
194
|
+
expect(anonymousModeService.getIsAnonymousMode()).toBe(false);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -48,11 +48,11 @@ describe('AuthPackage', () => {
|
|
|
48
48
|
it('should merge custom config with defaults', () => {
|
|
49
49
|
const customConfig = {
|
|
50
50
|
storageKeys: {
|
|
51
|
-
|
|
51
|
+
anonymousMode: '@custom_anonymous_mode',
|
|
52
52
|
showRegister: 'custom_show_register',
|
|
53
53
|
},
|
|
54
54
|
features: {
|
|
55
|
-
|
|
55
|
+
anonymousMode: false,
|
|
56
56
|
registration: false,
|
|
57
57
|
passwordStrength: false,
|
|
58
58
|
},
|
|
@@ -60,7 +60,7 @@ describe('AuthPackage', () => {
|
|
|
60
60
|
|
|
61
61
|
const authPackage = new AuthPackage(customConfig);
|
|
62
62
|
const config = authPackage.getConfig();
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
expect(config.storageKeys).toEqual(customConfig.storageKeys);
|
|
65
65
|
expect(config.features).toEqual(customConfig.features);
|
|
66
66
|
expect(config.validation).toEqual(DEFAULT_AUTH_PACKAGE_CONFIG.validation);
|
|
@@ -117,7 +117,7 @@ describe('AuthPackage', () => {
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
it('should return feature status from config', () => {
|
|
120
|
-
expect(authPackage.isFeatureEnabled('
|
|
120
|
+
expect(authPackage.isFeatureEnabled('anonymousMode')).toBe(true);
|
|
121
121
|
expect(authPackage.isFeatureEnabled('registration')).toBe(true);
|
|
122
122
|
expect(authPackage.isFeatureEnabled('passwordStrength')).toBe(true);
|
|
123
123
|
});
|
|
@@ -125,15 +125,15 @@ describe('AuthPackage', () => {
|
|
|
125
125
|
it('should return custom feature status', () => {
|
|
126
126
|
const customConfig = {
|
|
127
127
|
features: {
|
|
128
|
-
|
|
128
|
+
anonymousMode: false,
|
|
129
129
|
registration: false,
|
|
130
130
|
passwordStrength: false,
|
|
131
131
|
},
|
|
132
132
|
};
|
|
133
133
|
|
|
134
134
|
const customPackage = new AuthPackage(customConfig);
|
|
135
|
-
|
|
136
|
-
expect(customPackage.isFeatureEnabled('
|
|
135
|
+
|
|
136
|
+
expect(customPackage.isFeatureEnabled('anonymousMode')).toBe(false);
|
|
137
137
|
expect(customPackage.isFeatureEnabled('registration')).toBe(false);
|
|
138
138
|
expect(customPackage.isFeatureEnabled('passwordStrength')).toBe(false);
|
|
139
139
|
});
|
|
@@ -143,12 +143,12 @@ describe('AuthPackage', () => {
|
|
|
143
143
|
it('should initialize package globally', () => {
|
|
144
144
|
const customConfig = {
|
|
145
145
|
storageKeys: {
|
|
146
|
-
|
|
146
|
+
anonymousMode: '@global_anonymous_mode',
|
|
147
147
|
},
|
|
148
148
|
};
|
|
149
149
|
|
|
150
150
|
const packageInstance = initializeAuthPackage(customConfig);
|
|
151
|
-
expect(packageInstance.getConfig().storageKeys.
|
|
151
|
+
expect(packageInstance.getConfig().storageKeys.anonymousMode).toBe('@global_anonymous_mode');
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
it('should return existing package instance', () => {
|
|
@@ -167,11 +167,11 @@ describe('AuthPackage', () => {
|
|
|
167
167
|
it('should not reinitialize when already initialized', () => {
|
|
168
168
|
const firstInstance = initializeAuthPackage();
|
|
169
169
|
const secondInstance = initializeAuthPackage({
|
|
170
|
-
storageKeys: {
|
|
170
|
+
storageKeys: { anonymousMode: '@different' },
|
|
171
171
|
});
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
expect(firstInstance).toBe(secondInstance);
|
|
174
|
-
expect(secondInstance.getConfig().storageKeys.
|
|
174
|
+
expect(secondInstance.getConfig().storageKeys.anonymousMode).toBe('@auth_anonymous_mode');
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
it('should reset package instance', () => {
|
|
@@ -204,23 +204,23 @@ describe('AuthPackage', () => {
|
|
|
204
204
|
it('should handle empty config', () => {
|
|
205
205
|
const authPackage = new AuthPackage({});
|
|
206
206
|
const config = authPackage.getConfig();
|
|
207
|
-
|
|
207
|
+
|
|
208
208
|
expect(config).toEqual(DEFAULT_AUTH_PACKAGE_CONFIG);
|
|
209
209
|
});
|
|
210
210
|
|
|
211
211
|
it('should handle partial config', () => {
|
|
212
212
|
const partialConfig = {
|
|
213
213
|
features: {
|
|
214
|
-
|
|
214
|
+
anonymousMode: false,
|
|
215
215
|
},
|
|
216
216
|
};
|
|
217
217
|
|
|
218
218
|
const authPackage = new AuthPackage(partialConfig);
|
|
219
219
|
const config = authPackage.getConfig();
|
|
220
|
-
|
|
221
|
-
expect(config.features.
|
|
222
|
-
expect(config.features.registration).toBe(true);
|
|
223
|
-
expect(config.features.passwordStrength).toBe(true);
|
|
220
|
+
|
|
221
|
+
expect(config.features.anonymousMode).toBe(false);
|
|
222
|
+
expect(config.features.registration).toBe(true);
|
|
223
|
+
expect(config.features.passwordStrength).toBe(true);
|
|
224
224
|
});
|
|
225
225
|
});
|
|
226
226
|
});
|
|
@@ -33,14 +33,14 @@ export interface IAuthService {
|
|
|
33
33
|
signOut(): Promise<void>;
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* Set
|
|
36
|
+
* Set anonymous mode (no authentication)
|
|
37
37
|
*/
|
|
38
|
-
|
|
38
|
+
setAnonymousMode(): Promise<void>;
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Check if currently in
|
|
41
|
+
* Check if currently in anonymous mode
|
|
42
42
|
*/
|
|
43
|
-
|
|
43
|
+
getIsAnonymousMode(): boolean;
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* Get current authenticated user
|
|
@@ -27,19 +27,19 @@ const DEFAULT_NAMES = [
|
|
|
27
27
|
'Phoenix',
|
|
28
28
|
];
|
|
29
29
|
|
|
30
|
-
export interface
|
|
30
|
+
export interface AnonymousNameConfig {
|
|
31
31
|
names?: string[];
|
|
32
32
|
prefixes?: string[];
|
|
33
33
|
usePrefixes?: boolean;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* Generate a random
|
|
37
|
+
* Generate a random anonymous name
|
|
38
38
|
* Uses userId to ensure consistency per user
|
|
39
39
|
*/
|
|
40
|
-
export function
|
|
40
|
+
export function generateAnonymousName(
|
|
41
41
|
userId?: string,
|
|
42
|
-
config?:
|
|
42
|
+
config?: AnonymousNameConfig,
|
|
43
43
|
): string {
|
|
44
44
|
const names = config?.names || DEFAULT_NAMES;
|
|
45
45
|
const prefixes = config?.prefixes || [];
|
|
@@ -47,7 +47,7 @@ export function generateGuestName(
|
|
|
47
47
|
|
|
48
48
|
if (!userId) {
|
|
49
49
|
const randomIndex = Math.floor(Math.random() * names.length);
|
|
50
|
-
return names[randomIndex] ?? "
|
|
50
|
+
return names[randomIndex] ?? "Anonymous";
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// Use userId hash for consistent name per user
|
|
@@ -56,7 +56,7 @@ export function generateGuestName(
|
|
|
56
56
|
}, 0);
|
|
57
57
|
|
|
58
58
|
const nameIndex = Math.abs(hash) % names.length;
|
|
59
|
-
const name = names[nameIndex] ?? "
|
|
59
|
+
const name = names[nameIndex] ?? "Anonymous";
|
|
60
60
|
|
|
61
61
|
if (usePrefixes && prefixes.length > 0) {
|
|
62
62
|
const prefixIndex = Math.abs(hash >> 8) % prefixes.length;
|
|
@@ -67,13 +67,13 @@ export function generateGuestName(
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
|
-
* Get
|
|
70
|
+
* Get anonymous display name with fallback
|
|
71
71
|
*/
|
|
72
|
-
export function
|
|
72
|
+
export function getAnonymousDisplayName(
|
|
73
73
|
userId?: string,
|
|
74
|
-
fallback = '
|
|
75
|
-
config?:
|
|
74
|
+
fallback = 'Anonymous',
|
|
75
|
+
config?: AnonymousNameConfig,
|
|
76
76
|
): string {
|
|
77
77
|
if (!userId) return fallback;
|
|
78
|
-
return
|
|
78
|
+
return generateAnonymousName(userId, config);
|
|
79
79
|
}
|
package/src/index.ts
CHANGED
|
@@ -146,9 +146,9 @@ export type { UseAppleAuthResult } from './presentation/hooks/useAppleAuth';
|
|
|
146
146
|
|
|
147
147
|
export type { UserProfile, UpdateProfileParams } from './domain/entities/UserProfile';
|
|
148
148
|
|
|
149
|
-
// Domain Utils -
|
|
150
|
-
export {
|
|
151
|
-
export type {
|
|
149
|
+
// Domain Utils - Anonymous Names
|
|
150
|
+
export { generateAnonymousName, getAnonymousDisplayName } from './domain/utils/anonymousNameGenerator';
|
|
151
|
+
export type { AnonymousNameConfig } from './domain/utils/anonymousNameGenerator';
|
|
152
152
|
|
|
153
153
|
// =============================================================================
|
|
154
154
|
// PRESENTATION LAYER - Screens & Navigation
|
|
@@ -216,7 +216,6 @@ export {
|
|
|
216
216
|
getUserId,
|
|
217
217
|
getUserType,
|
|
218
218
|
getIsAuthenticated,
|
|
219
|
-
getIsGuest,
|
|
220
219
|
getIsAnonymous,
|
|
221
220
|
} from './presentation/stores/authStore';
|
|
222
221
|
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Handles
|
|
2
|
+
* Anonymous Mode Service
|
|
3
|
+
* Handles anonymous mode functionality
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { IAuthProvider } from "../../application/ports/IAuthProvider";
|
|
7
7
|
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
8
|
-
import {
|
|
8
|
+
import { emitAnonymousModeEnabled } from "../utils/AuthEventEmitter";
|
|
9
9
|
import type { IStorageProvider } from "./AuthPackage";
|
|
10
10
|
|
|
11
|
-
export class
|
|
12
|
-
private
|
|
11
|
+
export class AnonymousModeService {
|
|
12
|
+
private isAnonymousMode: boolean = false;
|
|
13
13
|
private storageKey: string;
|
|
14
14
|
|
|
15
|
-
constructor(storageKey: string = "@
|
|
15
|
+
constructor(storageKey: string = "@auth_anonymous_mode") {
|
|
16
16
|
this.storageKey = storageKey;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async load(storageProvider: IStorageProvider): Promise<boolean> {
|
|
20
20
|
try {
|
|
21
21
|
const value = await storageProvider.get(this.storageKey);
|
|
22
|
-
this.
|
|
23
|
-
return this.
|
|
22
|
+
this.isAnonymousMode = value === "true";
|
|
23
|
+
return this.isAnonymousMode;
|
|
24
24
|
} catch {
|
|
25
25
|
return false;
|
|
26
26
|
}
|
|
@@ -28,7 +28,7 @@ export class GuestModeService {
|
|
|
28
28
|
|
|
29
29
|
async save(storageProvider: IStorageProvider): Promise<void> {
|
|
30
30
|
try {
|
|
31
|
-
await storageProvider.set(this.storageKey, this.
|
|
31
|
+
await storageProvider.set(this.storageKey, this.isAnonymousMode.toString());
|
|
32
32
|
} catch {
|
|
33
33
|
// Silently fail storage operations
|
|
34
34
|
}
|
|
@@ -40,7 +40,7 @@ export class GuestModeService {
|
|
|
40
40
|
} catch {
|
|
41
41
|
// Silently fail storage operations
|
|
42
42
|
}
|
|
43
|
-
this.
|
|
43
|
+
this.isAnonymousMode = false;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
async enable(storageProvider: IStorageProvider, provider?: IAuthProvider): Promise<void> {
|
|
@@ -49,29 +49,29 @@ export class GuestModeService {
|
|
|
49
49
|
try {
|
|
50
50
|
await provider.signOut();
|
|
51
51
|
} catch {
|
|
52
|
-
// Ignore sign out errors when switching to
|
|
52
|
+
// Ignore sign out errors when switching to anonymous mode
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
this.
|
|
56
|
+
this.isAnonymousMode = true;
|
|
57
57
|
await this.save(storageProvider);
|
|
58
|
-
|
|
58
|
+
emitAnonymousModeEnabled();
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
return this.
|
|
61
|
+
getIsAnonymousMode(): boolean {
|
|
62
|
+
return this.isAnonymousMode;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
this.
|
|
65
|
+
setAnonymousMode(enabled: boolean): void {
|
|
66
|
+
this.isAnonymousMode = enabled;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
wrapAuthStateCallback(
|
|
70
70
|
callback: (user: AuthUser | null) => void
|
|
71
71
|
): (user: AuthUser | null) => void {
|
|
72
72
|
return (user: AuthUser | null) => {
|
|
73
|
-
// Don't update if in
|
|
74
|
-
if (!this.
|
|
73
|
+
// Don't update if in anonymous mode
|
|
74
|
+
if (!this.isAnonymousMode) {
|
|
75
75
|
callback(user);
|
|
76
76
|
} else {
|
|
77
77
|
callback(null);
|
|
@@ -38,13 +38,13 @@ export class AuthEventService {
|
|
|
38
38
|
this.notifyListeners("user-authenticated", payload);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
emitAnonymousModeEnabled(): void {
|
|
42
42
|
const payload: AuthEventPayload = {
|
|
43
43
|
timestamp: Date.now(),
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
DeviceEventEmitter.emit("
|
|
47
|
-
this.notifyListeners("
|
|
46
|
+
DeviceEventEmitter.emit("anonymous-mode-enabled", payload);
|
|
47
|
+
this.notifyListeners("anonymous-mode-enabled", payload);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
emitAuthError(error: string): void {
|
|
@@ -99,13 +99,13 @@ export class AuthEventService {
|
|
|
99
99
|
// Export singleton instance for backward compatibility
|
|
100
100
|
export const authEventService = AuthEventService.getInstance();
|
|
101
101
|
|
|
102
|
-
//
|
|
102
|
+
// Helper functions
|
|
103
103
|
export function emitUserAuthenticated(userId: string): void {
|
|
104
104
|
authEventService.emitUserAuthenticated(userId);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
export function
|
|
108
|
-
authEventService.
|
|
107
|
+
export function emitAnonymousModeEnabled(): void {
|
|
108
|
+
authEventService.emitAnonymousModeEnabled();
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
export function emitAuthError(error: string): void {
|
|
@@ -8,7 +8,7 @@ import type { PasswordConfig } from "../../domain/value-objects/AuthConfig";
|
|
|
8
8
|
|
|
9
9
|
export interface AuthPackageConfig {
|
|
10
10
|
storageKeys: {
|
|
11
|
-
|
|
11
|
+
anonymousMode: string;
|
|
12
12
|
showRegister: string;
|
|
13
13
|
};
|
|
14
14
|
validation: {
|
|
@@ -21,7 +21,7 @@ export interface AuthPackageConfig {
|
|
|
21
21
|
localization?: any;
|
|
22
22
|
};
|
|
23
23
|
features: {
|
|
24
|
-
|
|
24
|
+
anonymousMode: boolean;
|
|
25
25
|
registration: boolean;
|
|
26
26
|
passwordStrength: boolean;
|
|
27
27
|
};
|
|
@@ -29,7 +29,7 @@ export interface AuthPackageConfig {
|
|
|
29
29
|
|
|
30
30
|
export const DEFAULT_AUTH_PACKAGE_CONFIG: AuthPackageConfig = {
|
|
31
31
|
storageKeys: {
|
|
32
|
-
|
|
32
|
+
anonymousMode: "@auth_anonymous_mode",
|
|
33
33
|
showRegister: "auth_show_register",
|
|
34
34
|
},
|
|
35
35
|
validation: {
|
|
@@ -47,7 +47,7 @@ export const DEFAULT_AUTH_PACKAGE_CONFIG: AuthPackageConfig = {
|
|
|
47
47
|
localization: undefined,
|
|
48
48
|
},
|
|
49
49
|
features: {
|
|
50
|
-
|
|
50
|
+
anonymousMode: true,
|
|
51
51
|
registration: true,
|
|
52
52
|
passwordStrength: true,
|
|
53
53
|
},
|