@umituz/react-native-design-system 2.3.1 → 2.3.3

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.
Files changed (54) hide show
  1. package/package.json +15 -3
  2. package/src/atoms/AtomicInput.tsx +0 -1
  3. package/src/atoms/AtomicPicker.tsx +0 -1
  4. package/src/atoms/picker/components/PickerChips.tsx +0 -1
  5. package/src/atoms/picker/components/PickerModal.tsx +1 -3
  6. package/src/atoms/picker/styles/pickerStyles.ts +1 -1
  7. package/src/{responsive → device/detection}/deviceDetection.ts +7 -7
  8. package/src/device/detection/iPadBreakpoints.ts +55 -0
  9. package/src/device/detection/iPadDetection.ts +48 -0
  10. package/src/device/detection/iPadLayoutUtils.ts +95 -0
  11. package/src/device/detection/iPadModalUtils.ts +98 -0
  12. package/src/device/detection/index.ts +54 -0
  13. package/src/device/domain/entities/Device.ts +207 -0
  14. package/src/device/domain/entities/DeviceMemoryUtils.ts +62 -0
  15. package/src/device/domain/entities/DeviceTypeUtils.ts +66 -0
  16. package/src/device/domain/entities/__tests__/DeviceMemoryUtils.test.ts +118 -0
  17. package/src/device/domain/entities/__tests__/DeviceTypeUtils.test.ts +104 -0
  18. package/src/device/domain/entities/__tests__/DeviceUtils.test.ts +167 -0
  19. package/src/device/index.ts +104 -0
  20. package/src/device/infrastructure/services/ApplicationInfoService.ts +86 -0
  21. package/src/device/infrastructure/services/DeviceCapabilityService.ts +60 -0
  22. package/src/device/infrastructure/services/DeviceIdService.ts +70 -0
  23. package/src/device/infrastructure/services/DeviceInfoService.ts +95 -0
  24. package/src/device/infrastructure/services/DeviceService.ts +104 -0
  25. package/src/device/infrastructure/services/PersistentDeviceIdService.ts +132 -0
  26. package/src/device/infrastructure/services/UserFriendlyIdService.ts +68 -0
  27. package/src/device/infrastructure/utils/__tests__/nativeModuleUtils.test.ts +158 -0
  28. package/src/device/infrastructure/utils/__tests__/stringUtils.test.ts +120 -0
  29. package/src/device/infrastructure/utils/nativeModuleUtils.ts +69 -0
  30. package/src/device/infrastructure/utils/stringUtils.ts +59 -0
  31. package/src/device/presentation/hooks/useAnonymousUser.ts +117 -0
  32. package/src/device/presentation/hooks/useDeviceInfo.ts +222 -0
  33. package/src/molecules/ConfirmationModalContent.tsx +4 -4
  34. package/src/molecules/ConfirmationModalMain.tsx +1 -1
  35. package/src/molecules/ScreenHeader.tsx +2 -2
  36. package/src/molecules/confirmation-modal/components.tsx +1 -1
  37. package/src/molecules/confirmation-modal/styles/confirmationModalStyles.ts +6 -7
  38. package/src/presentation/utils/variants/__tests__/core.test.ts +0 -1
  39. package/src/responsive/gridUtils.ts +1 -1
  40. package/src/responsive/index.ts +36 -20
  41. package/src/responsive/responsive.ts +2 -2
  42. package/src/responsive/responsiveLayout.ts +1 -1
  43. package/src/responsive/responsiveModal.ts +1 -1
  44. package/src/responsive/responsiveSizing.ts +1 -1
  45. package/src/responsive/useResponsive.ts +1 -1
  46. package/src/safe-area/__tests__/components/SafeAreaProvider.test.tsx +2 -2
  47. package/src/safe-area/__tests__/hooks/useContentSafeAreaPadding.test.tsx +2 -2
  48. package/src/safe-area/__tests__/hooks/useHeaderSafeAreaPadding.test.tsx +2 -2
  49. package/src/safe-area/__tests__/hooks/useSafeAreaInsets.test.tsx +2 -2
  50. package/src/safe-area/__tests__/hooks/useStatusBarSafeAreaPadding.test.tsx +2 -2
  51. package/src/safe-area/__tests__/integration/completeFlow.test.tsx +5 -4
  52. package/src/safe-area/__tests__/utils/testUtils.tsx +5 -4
  53. package/src/theme/infrastructure/stores/themeStore.ts +0 -2
  54. package/src/typography/presentation/utils/textColorUtils.ts +0 -1
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Persistent Device ID Service
3
+ *
4
+ * Provides a stable, persistent device identifier that survives app restarts.
5
+ * Uses native device ID when available, falls back to generated UUID.
6
+ * Stores the ID in AsyncStorage for persistence.
7
+ *
8
+ * @domain device
9
+ * @layer infrastructure/services
10
+ */
11
+
12
+ import AsyncStorage from '@react-native-async-storage/async-storage';
13
+ import { DeviceIdService } from './DeviceIdService';
14
+
15
+ const STORAGE_KEY = '@device/persistent_id';
16
+
17
+ /** Cached ID to avoid repeated AsyncStorage reads */
18
+ let cachedDeviceId: string | null = null;
19
+
20
+ /** Promise to prevent race conditions during initialization */
21
+ let initializationPromise: Promise<string> | null = null;
22
+
23
+ /**
24
+ * Generate a UUID v4 without external dependencies
25
+ */
26
+ function generateUUID(): string {
27
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
28
+ const r = (Math.random() * 16) | 0;
29
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
30
+ return v.toString(16);
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Service for managing persistent device identifiers
36
+ */
37
+ export class PersistentDeviceIdService {
38
+ /**
39
+ * Get or create a persistent device ID
40
+ *
41
+ * This method:
42
+ * 1. Returns cached ID if available (fastest)
43
+ * 2. Checks AsyncStorage for previously stored ID
44
+ * 3. If not found, gets native device ID or generates UUID
45
+ * 4. Stores and caches the result for future calls
46
+ *
47
+ * Thread-safe: Multiple concurrent calls return the same promise
48
+ */
49
+ static async getDeviceId(): Promise<string> {
50
+ if (cachedDeviceId) {
51
+ return cachedDeviceId;
52
+ }
53
+
54
+ if (initializationPromise) {
55
+ return initializationPromise;
56
+ }
57
+
58
+ initializationPromise = this.initializeDeviceId();
59
+ return initializationPromise;
60
+ }
61
+
62
+ /**
63
+ * Initialize and persist device ID
64
+ */
65
+ private static async initializeDeviceId(): Promise<string> {
66
+ try {
67
+ const storedId = await AsyncStorage.getItem(STORAGE_KEY);
68
+
69
+ if (storedId) {
70
+ cachedDeviceId = storedId;
71
+ return storedId;
72
+ }
73
+
74
+ const newId = await this.createNewDeviceId();
75
+ await AsyncStorage.setItem(STORAGE_KEY, newId);
76
+ cachedDeviceId = newId;
77
+
78
+ return newId;
79
+ } catch {
80
+ const fallbackId = generateUUID();
81
+ cachedDeviceId = fallbackId;
82
+ return fallbackId;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Create a new device ID from native source or generate one
88
+ */
89
+ private static async createNewDeviceId(): Promise<string> {
90
+ const nativeId = await DeviceIdService.getDeviceId();
91
+
92
+ if (nativeId) {
93
+ return `device_${nativeId}`;
94
+ }
95
+
96
+ return `generated_${generateUUID()}`;
97
+ }
98
+
99
+ /**
100
+ * Check if device ID exists in storage
101
+ */
102
+ static async hasStoredId(): Promise<boolean> {
103
+ try {
104
+ const storedId = await AsyncStorage.getItem(STORAGE_KEY);
105
+ return storedId !== null;
106
+ } catch {
107
+ return false;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Clear stored device ID (use with caution)
113
+ * This will generate a new ID on next getDeviceId() call
114
+ */
115
+ static async clearStoredId(): Promise<void> {
116
+ try {
117
+ await AsyncStorage.removeItem(STORAGE_KEY);
118
+ cachedDeviceId = null;
119
+ initializationPromise = null;
120
+ } catch {
121
+ // Silent fail - non-critical operation
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Get the cached device ID without async operation
127
+ * Returns null if not yet initialized
128
+ */
129
+ static getCachedId(): string | null {
130
+ return cachedDeviceId;
131
+ }
132
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * User Friendly ID Service
3
+ *
4
+ * Single Responsibility: Generate user-friendly device identifiers
5
+ * Follows SOLID principles - only handles ID generation logic
6
+ */
7
+
8
+ import { Platform } from 'react-native';
9
+ import type { DeviceInfo } from '../../domain/entities/Device';
10
+ import { DeviceInfoService } from './DeviceInfoService';
11
+ import { DeviceIdService } from './DeviceIdService';
12
+ import {
13
+ cleanModelName,
14
+ extractIdPart,
15
+ generateRandomId,
16
+ getPlatformPrefix,
17
+ } from '../utils/stringUtils';
18
+
19
+ /**
20
+ * Service for generating user-friendly device IDs
21
+ */
22
+ export class UserFriendlyIdService {
23
+ /**
24
+ * Get user friendly device ID (e.g. "iPhone13-A8F2")
25
+ *
26
+ * Useful for displaying a readable user identifier in profiles.
27
+ * Combines cleaned model name with short device hash.
28
+ *
29
+ * SAFE: This method has multiple fallback layers to prevent native module crashes.
30
+ * If native modules are not ready, it will return a safe fallback ID.
31
+ */
32
+ static async getUserFriendlyId(): Promise<string> {
33
+ // Web platform - no native modules needed
34
+ if (Platform.OS === 'web') {
35
+ return `WebUser-${generateRandomId()}`;
36
+ }
37
+
38
+ try {
39
+ // Try to get device info and ID
40
+ const deviceInfo: DeviceInfo = await DeviceInfoService.getDeviceInfo();
41
+ const deviceId: string | null = await DeviceIdService.getDeviceId();
42
+
43
+ // If we got device info, use it
44
+ if (deviceInfo && (deviceInfo.modelName || deviceInfo.deviceName)) {
45
+ const model = deviceInfo.modelName || deviceInfo.deviceName || 'Device';
46
+ const cleanModel = cleanModelName(model);
47
+ const idPart = extractIdPart(deviceId, 6);
48
+
49
+ return `${cleanModel}-${idPart}`;
50
+ }
51
+
52
+ // Fallback: Use platform + random ID
53
+ return this.generateFallbackId();
54
+ } catch {
55
+ // Final fallback: Generate safe random ID
56
+ return this.generateFallbackId();
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Generate fallback ID when native modules are not available
62
+ */
63
+ private static generateFallbackId(): string {
64
+ const platformPrefix = getPlatformPrefix(Platform.OS);
65
+ return `${platformPrefix}-${generateRandomId()}`;
66
+ }
67
+ }
68
+
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Native Module Utils Tests
3
+ */
4
+
5
+ import { withTimeout, safeAccess, withTimeoutAll } from '../nativeModuleUtils';
6
+
7
+ describe('Native Module Utils', () => {
8
+ describe('withTimeout', () => {
9
+ beforeEach(() => {
10
+ jest.useFakeTimers();
11
+ });
12
+
13
+ afterEach(() => {
14
+ jest.useRealTimers();
15
+ });
16
+
17
+ it('should resolve with operation result when successful', async () => {
18
+ const operation = jest.fn().mockResolvedValue('success');
19
+ const promise = withTimeout(operation, 1000);
20
+
21
+ jest.advanceTimersByTime(0);
22
+ await expect(promise).resolves.toBe('success');
23
+ expect(operation).toHaveBeenCalledTimes(1);
24
+ });
25
+
26
+ it('should return null when operation times out', async () => {
27
+ const operation = jest.fn().mockImplementation(() => new Promise(() => {}));
28
+ const promise = withTimeout(operation, 1000);
29
+
30
+ jest.advanceTimersByTime(1000);
31
+ await expect(promise).resolves.toBeNull();
32
+ });
33
+
34
+ it('should return null when operation throws', async () => {
35
+ const operation = jest.fn().mockRejectedValue(new Error('Test error'));
36
+ const promise = withTimeout(operation, 1000);
37
+
38
+ jest.advanceTimersByTime(0);
39
+ await expect(promise).resolves.toBeNull();
40
+ });
41
+
42
+ it('should use default timeout when not specified', async () => {
43
+ const operation = jest.fn().mockImplementation(() => new Promise(() => {}));
44
+ const promise = withTimeout(operation);
45
+
46
+ jest.advanceTimersByTime(1000);
47
+ await expect(promise).resolves.toBeNull();
48
+ });
49
+ });
50
+
51
+ describe('safeAccess', () => {
52
+ it('should return value when accessor succeeds', () => {
53
+ const accessor = () => 'test value';
54
+ const result = safeAccess(accessor, 'fallback');
55
+ expect(result).toBe('test value');
56
+ });
57
+
58
+ it('should return fallback when accessor throws', () => {
59
+ const accessor = () => {
60
+ throw new Error('Test error');
61
+ };
62
+ const result = safeAccess(accessor, 'fallback');
63
+ expect(result).toBe('fallback');
64
+ });
65
+
66
+ it('should return fallback when accessor returns null/undefined', () => {
67
+ const accessor1 = () => null;
68
+ const accessor2 = () => undefined;
69
+
70
+ expect(safeAccess(accessor1, 'fallback')).toBe('fallback');
71
+ expect(safeAccess(accessor2, 'fallback')).toBe('fallback');
72
+ });
73
+
74
+ it('should return value when accessor returns falsy but not null/undefined', () => {
75
+ const accessor1 = () => '';
76
+ const accessor2 = () => 0;
77
+ const accessor3 = () => false;
78
+
79
+ expect(safeAccess(accessor1, 'fallback')).toBe('');
80
+ expect(safeAccess(accessor2, 'fallback')).toBe(0);
81
+ expect(safeAccess(accessor3, 'fallback')).toBe(false);
82
+ });
83
+ });
84
+
85
+ describe('withTimeoutAll', () => {
86
+ beforeEach(() => {
87
+ jest.useFakeTimers();
88
+ });
89
+
90
+ afterEach(() => {
91
+ jest.useRealTimers();
92
+ });
93
+
94
+ it('should resolve all operations when successful', async () => {
95
+ const operations = [
96
+ jest.fn().mockResolvedValue('result1'),
97
+ jest.fn().mockResolvedValue('result2'),
98
+ jest.fn().mockResolvedValue('result3'),
99
+ ];
100
+ const promise = withTimeoutAll(operations, 1000);
101
+
102
+ jest.advanceTimersByTime(0);
103
+ const results = await promise;
104
+
105
+ expect(results).toEqual(['result1', 'result2', 'result3']);
106
+ operations.forEach(op => expect(op).toHaveBeenCalledTimes(1));
107
+ });
108
+
109
+ it('should handle individual operation failures', async () => {
110
+ const operations = [
111
+ jest.fn().mockResolvedValue('result1'),
112
+ jest.fn().mockRejectedValue(new Error('Test error')),
113
+ jest.fn().mockResolvedValue('result3'),
114
+ ];
115
+ const promise = withTimeoutAll(operations, 1000);
116
+
117
+ jest.advanceTimersByTime(0);
118
+ const results = await promise;
119
+
120
+ expect(results).toEqual(['result1', null, 'result3']);
121
+ });
122
+
123
+ it('should return null for all operations when timeout occurs', async () => {
124
+ const operations = [
125
+ jest.fn().mockImplementation(() => new Promise(() => {})),
126
+ jest.fn().mockResolvedValue('result2'),
127
+ ];
128
+ const promise = withTimeoutAll(operations, 1000);
129
+
130
+ jest.advanceTimersByTime(1000);
131
+ const results = await promise;
132
+
133
+ expect(results).toEqual([null, null]);
134
+ });
135
+
136
+ it('should use default timeout when not specified', async () => {
137
+ const operations = [
138
+ jest.fn().mockImplementation(() => new Promise(() => {})),
139
+ ];
140
+ const promise = withTimeoutAll(operations);
141
+
142
+ jest.advanceTimersByTime(2000);
143
+ const results = await promise;
144
+
145
+ expect(results).toEqual([null]);
146
+ });
147
+
148
+ it('should handle empty operations array', async () => {
149
+ const operations: Array<() => Promise<string>> = [];
150
+ const promise = withTimeoutAll(operations, 1000);
151
+
152
+ jest.advanceTimersByTime(0);
153
+ const results = await promise;
154
+
155
+ expect(results).toEqual([]);
156
+ });
157
+ });
158
+ });
@@ -0,0 +1,120 @@
1
+ /**
2
+ * String Utils Tests
3
+ */
4
+
5
+ import { cleanModelName, extractIdPart, generateRandomId, getPlatformPrefix } from '../stringUtils';
6
+
7
+ describe('String Utils', () => {
8
+ describe('cleanModelName', () => {
9
+ it('should remove special characters from model name', () => {
10
+ const result = cleanModelName('iPhone 14 Pro Max');
11
+ expect(result).toBe('iPhone14ProMax');
12
+ });
13
+
14
+ it('should handle null/undefined input', () => {
15
+ expect(cleanModelName(null)).toBe('Device');
16
+ expect(cleanModelName(undefined)).toBe('Device');
17
+ });
18
+
19
+ it('should handle empty string', () => {
20
+ const result = cleanModelName('');
21
+ expect(result).toBe('Device');
22
+ });
23
+
24
+ it('should handle model with only special characters', () => {
25
+ const result = cleanModelName('!@#$%^&*()');
26
+ expect(result).toBe('Device');
27
+ });
28
+
29
+ it('should preserve alphanumeric characters', () => {
30
+ const result = cleanModelName('SM-G998B');
31
+ expect(result).toBe('SMG998B');
32
+ });
33
+ });
34
+
35
+ describe('extractIdPart', () => {
36
+ it('should extract last N characters from device ID', () => {
37
+ const deviceId = '12345678-1234-1234-1234-123456789012';
38
+ const result = extractIdPart(deviceId, 6);
39
+ expect(result).toBe('789012');
40
+ });
41
+
42
+ it('should use default length of 6 when not specified', () => {
43
+ const deviceId = '12345678-1234-1234-1234-123456789012';
44
+ const result = extractIdPart(deviceId);
45
+ expect(result).toBe('789012');
46
+ });
47
+
48
+ it('should handle null device ID by generating random ID', () => {
49
+ const result = extractIdPart(null, 6);
50
+ expect(result).toMatch(/^[A-Z0-9]{6}$/);
51
+ });
52
+
53
+ it('should handle empty device ID by generating random ID', () => {
54
+ const result = extractIdPart('', 6);
55
+ expect(result).toMatch(/^[A-Z0-9]{6}$/);
56
+ });
57
+
58
+ it('should handle device ID shorter than requested length', () => {
59
+ const deviceId = 'ABC';
60
+ const result = extractIdPart(deviceId, 6);
61
+ expect(result).toBe('ABC');
62
+ });
63
+
64
+ it('should convert to uppercase', () => {
65
+ const deviceId = 'abcdef';
66
+ const result = extractIdPart(deviceId, 3);
67
+ expect(result).toBe('DEF');
68
+ });
69
+ });
70
+
71
+ describe('generateRandomId', () => {
72
+ it('should generate random ID with default length', () => {
73
+ const result = generateRandomId();
74
+ expect(result).toMatch(/^[A-Z0-9]{6}$/);
75
+ });
76
+
77
+ it('should generate random ID with specified length', () => {
78
+ const result = generateRandomId(10);
79
+ expect(result).toMatch(/^[A-Z0-9]{10}$/);
80
+ });
81
+
82
+ it('should generate different IDs on multiple calls', () => {
83
+ const id1 = generateRandomId(6);
84
+ const id2 = generateRandomId(6);
85
+ expect(id1).not.toBe(id2);
86
+ });
87
+
88
+ it('should handle length of 1', () => {
89
+ const result = generateRandomId(1);
90
+ expect(result).toMatch(/^[A-Z0-9]{1}$/);
91
+ });
92
+ });
93
+
94
+ describe('getPlatformPrefix', () => {
95
+ it('should return iOS for ios platform', () => {
96
+ const result = getPlatformPrefix('ios');
97
+ expect(result).toBe('iOS');
98
+ });
99
+
100
+ it('should return Android for android platform', () => {
101
+ const result = getPlatformPrefix('android');
102
+ expect(result).toBe('Android');
103
+ });
104
+
105
+ it('should return Device for unknown platform', () => {
106
+ const result = getPlatformPrefix('windows');
107
+ expect(result).toBe('Device');
108
+ });
109
+
110
+ it('should return Device for empty string', () => {
111
+ const result = getPlatformPrefix('');
112
+ expect(result).toBe('Device');
113
+ });
114
+
115
+ it('should be case sensitive', () => {
116
+ expect(getPlatformPrefix('IOS')).toBe('Device');
117
+ expect(getPlatformPrefix('ANDROID')).toBe('Device');
118
+ });
119
+ });
120
+ });
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Native Module Utilities
3
+ *
4
+ * Safe wrappers for native module access with timeout and error handling
5
+ * Prevents crashes when native modules are not ready
6
+ */
7
+
8
+ /**
9
+ * Execute a native module call with timeout
10
+ * @param operation - Async operation to execute
11
+ * @param timeoutMs - Timeout in milliseconds (default: 1000)
12
+ * @returns Result of operation or null if timeout/error
13
+ */
14
+ export async function withTimeout<T>(
15
+ operation: () => Promise<T>,
16
+ timeoutMs: number = 1000,
17
+ ): Promise<T | null> {
18
+ try {
19
+ const timeoutPromise = new Promise<never>((_, reject) => {
20
+ setTimeout(() => reject(new Error('Operation timeout')), timeoutMs);
21
+ });
22
+
23
+ return await Promise.race([operation(), timeoutPromise]);
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Safely access a native module property
31
+ * @param accessor - Function that accesses the property
32
+ * @param fallback - Fallback value if access fails
33
+ * @returns Property value or fallback
34
+ */
35
+ export function safeAccess<T>(accessor: () => T, fallback: T): T {
36
+ try {
37
+ const value = accessor();
38
+ return value ?? fallback;
39
+ } catch {
40
+ return fallback;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Execute multiple native module operations with timeout
46
+ * @param operations - Array of async operations
47
+ * @param timeoutMs - Timeout in milliseconds (default: 2000)
48
+ * @returns Array of results (null for failed operations)
49
+ */
50
+ export async function withTimeoutAll<T>(
51
+ operations: Array<() => Promise<T>>,
52
+ timeoutMs: number = 2000,
53
+ ): Promise<Array<T | null>> {
54
+ try {
55
+ const timeoutPromise = new Promise<never>((_, reject) => {
56
+ setTimeout(() => reject(new Error('Operations timeout')), timeoutMs);
57
+ });
58
+
59
+ const results = await Promise.race([
60
+ Promise.all(operations.map((op) => op().catch(() => null))),
61
+ timeoutPromise,
62
+ ]).catch(() => operations.map(() => null));
63
+
64
+ return results as Array<T | null>;
65
+ } catch {
66
+ return operations.map(() => null);
67
+ }
68
+ }
69
+
@@ -0,0 +1,59 @@
1
+ /**
2
+ * String Utilities
3
+ *
4
+ * Pure utility functions for string manipulation
5
+ * No dependencies, no side effects
6
+ */
7
+
8
+ /**
9
+ * Clean model name by removing special characters
10
+ * @param model - Device model name
11
+ * @returns Cleaned model name with only alphanumeric characters
12
+ */
13
+ export function cleanModelName(model: string | null | undefined): string {
14
+ if (!model) {
15
+ return 'Device';
16
+ }
17
+ const cleaned = model.replace(/[^a-zA-Z0-9]/g, '');
18
+ return cleaned || 'Device';
19
+ }
20
+
21
+ /**
22
+ * Extract ID part from device ID
23
+ * @param deviceId - Full device ID
24
+ * @param length - Length of ID part to extract (default: 6)
25
+ * @returns Last N characters of device ID in uppercase
26
+ */
27
+ export function extractIdPart(deviceId: string | null, length: number = 6): string {
28
+ if (!deviceId) {
29
+ return generateRandomId(length);
30
+ }
31
+ const start = Math.max(0, deviceId.length - length);
32
+ return deviceId.substring(start).toUpperCase();
33
+ }
34
+
35
+ /**
36
+ * Generate random alphanumeric ID
37
+ * @param length - Length of ID to generate (default: 6)
38
+ * @returns Random ID in uppercase
39
+ */
40
+ export function generateRandomId(length: number = 6): string {
41
+ return Math.random().toString(36).substring(2, 2 + length).toUpperCase();
42
+ }
43
+
44
+ /**
45
+ * Get platform prefix for device ID
46
+ * @param platform - Platform OS
47
+ * @returns Platform prefix string
48
+ */
49
+ export function getPlatformPrefix(platform: string): string {
50
+ switch (platform) {
51
+ case 'ios':
52
+ return 'iOS';
53
+ case 'android':
54
+ return 'Android';
55
+ default:
56
+ return 'Device';
57
+ }
58
+ }
59
+