@croacroa/react-native-template 2.1.0 → 3.2.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.
Files changed (172) hide show
  1. package/.env.example +5 -0
  2. package/.eslintrc.js +8 -0
  3. package/.github/workflows/ci.yml +187 -187
  4. package/.github/workflows/eas-build.yml +55 -55
  5. package/.github/workflows/eas-update.yml +50 -50
  6. package/.github/workflows/npm-publish.yml +57 -0
  7. package/CHANGELOG.md +195 -106
  8. package/CONTRIBUTING.md +377 -377
  9. package/LICENSE +21 -21
  10. package/README.md +446 -402
  11. package/__tests__/accessibility/components.test.tsx +285 -0
  12. package/__tests__/components/Button.test.tsx +2 -4
  13. package/__tests__/components/__snapshots__/snapshots.test.tsx.snap +512 -0
  14. package/__tests__/components/snapshots.test.tsx +131 -131
  15. package/__tests__/helpers/a11y.ts +54 -0
  16. package/__tests__/hooks/useAnalytics.test.ts +100 -0
  17. package/__tests__/hooks/useAnimations.test.ts +70 -0
  18. package/__tests__/hooks/useAuth.test.tsx +71 -28
  19. package/__tests__/hooks/useMedia.test.ts +318 -0
  20. package/__tests__/hooks/usePayments.test.tsx +307 -0
  21. package/__tests__/hooks/usePermission.test.ts +230 -0
  22. package/__tests__/hooks/useWebSocket.test.ts +329 -0
  23. package/__tests__/integration/auth-api.test.tsx +224 -227
  24. package/__tests__/performance/VirtualizedList.perf.test.tsx +385 -362
  25. package/__tests__/services/api.test.ts +24 -6
  26. package/app/(auth)/home.tsx +11 -9
  27. package/app/(auth)/profile.tsx +8 -6
  28. package/app/(auth)/settings.tsx +11 -9
  29. package/app/(public)/forgot-password.tsx +25 -15
  30. package/app/(public)/login.tsx +48 -12
  31. package/app/(public)/onboarding.tsx +5 -5
  32. package/app/(public)/register.tsx +24 -15
  33. package/app/_layout.tsx +6 -3
  34. package/app.config.ts +27 -2
  35. package/assets/images/.gitkeep +7 -7
  36. package/assets/images/adaptive-icon.png +0 -0
  37. package/assets/images/favicon.png +0 -0
  38. package/assets/images/icon.png +0 -0
  39. package/assets/images/notification-icon.png +0 -0
  40. package/assets/images/splash.png +0 -0
  41. package/components/ErrorBoundary.tsx +73 -28
  42. package/components/auth/SocialLoginButtons.tsx +168 -0
  43. package/components/forms/FormInput.tsx +5 -3
  44. package/components/onboarding/OnboardingScreen.tsx +370 -370
  45. package/components/onboarding/index.ts +2 -2
  46. package/components/providers/AnalyticsProvider.tsx +67 -0
  47. package/components/providers/SuspenseBoundary.tsx +359 -357
  48. package/components/providers/index.ts +24 -21
  49. package/components/ui/AnimatedButton.tsx +1 -9
  50. package/components/ui/AnimatedList.tsx +98 -0
  51. package/components/ui/AnimatedScreen.tsx +89 -0
  52. package/components/ui/Avatar.tsx +319 -316
  53. package/components/ui/Badge.tsx +416 -416
  54. package/components/ui/BottomSheet.tsx +307 -307
  55. package/components/ui/Button.tsx +11 -3
  56. package/components/ui/Checkbox.tsx +261 -261
  57. package/components/ui/FeatureGate.tsx +57 -0
  58. package/components/ui/ForceUpdateScreen.tsx +108 -0
  59. package/components/ui/ImagePickerButton.tsx +180 -0
  60. package/components/ui/Input.stories.tsx +2 -10
  61. package/components/ui/Input.tsx +2 -10
  62. package/components/ui/OptimizedImage.tsx +369 -369
  63. package/components/ui/Paywall.tsx +253 -0
  64. package/components/ui/PermissionGate.tsx +155 -0
  65. package/components/ui/PurchaseButton.tsx +84 -0
  66. package/components/ui/Select.tsx +240 -240
  67. package/components/ui/Skeleton.tsx +3 -1
  68. package/components/ui/Toast.tsx +427 -418
  69. package/components/ui/UploadProgress.tsx +189 -0
  70. package/components/ui/VirtualizedList.tsx +288 -285
  71. package/components/ui/index.ts +28 -30
  72. package/constants/config.ts +135 -97
  73. package/docs/adr/001-state-management.md +79 -79
  74. package/docs/adr/002-styling-approach.md +130 -130
  75. package/docs/adr/003-data-fetching.md +155 -155
  76. package/docs/adr/004-auth-adapter-pattern.md +144 -144
  77. package/docs/adr/README.md +78 -78
  78. package/docs/guides/analytics-posthog.md +121 -0
  79. package/docs/guides/auth-supabase.md +162 -0
  80. package/docs/guides/feature-flags-launchdarkly.md +150 -0
  81. package/docs/guides/payments-revenuecat.md +169 -0
  82. package/docs/plans/2026-02-22-phase6-implementation.md +3222 -0
  83. package/docs/plans/2026-02-22-phase6-template-completion-design.md +196 -0
  84. package/docs/plans/2026-02-23-npm-publish-design.md +31 -0
  85. package/docs/plans/2026-02-23-phase7-polish-documentation-design.md +79 -0
  86. package/docs/plans/2026-02-23-phase8-additional-features-design.md +136 -0
  87. package/eas.json +2 -1
  88. package/hooks/index.ts +70 -40
  89. package/hooks/useAnimatedEntry.ts +204 -0
  90. package/hooks/useApi.ts +5 -4
  91. package/hooks/useAuth.tsx +7 -3
  92. package/hooks/useBiometrics.ts +295 -295
  93. package/hooks/useChannel.ts +111 -0
  94. package/hooks/useDeepLinking.ts +256 -256
  95. package/hooks/useExperiment.ts +36 -0
  96. package/hooks/useFeatureFlag.ts +59 -0
  97. package/hooks/useForceUpdate.ts +91 -0
  98. package/hooks/useImagePicker.ts +281 -375
  99. package/hooks/useInAppReview.ts +64 -0
  100. package/hooks/useMFA.ts +509 -499
  101. package/hooks/useParallax.ts +142 -0
  102. package/hooks/usePerformance.ts +434 -434
  103. package/hooks/usePermission.ts +190 -0
  104. package/hooks/usePresence.ts +129 -0
  105. package/hooks/useProducts.ts +36 -0
  106. package/hooks/usePurchase.ts +103 -0
  107. package/hooks/useRateLimit.ts +70 -0
  108. package/hooks/useSubscription.ts +49 -0
  109. package/hooks/useTrackEvent.ts +52 -0
  110. package/hooks/useTrackScreen.ts +40 -0
  111. package/hooks/useUpdates.ts +358 -358
  112. package/hooks/useUpload.ts +165 -0
  113. package/hooks/useWebSocket.ts +111 -0
  114. package/i18n/index.ts +197 -194
  115. package/i18n/locales/ar.json +170 -101
  116. package/i18n/locales/de.json +170 -101
  117. package/i18n/locales/en.json +170 -101
  118. package/i18n/locales/es.json +170 -101
  119. package/i18n/locales/fr.json +170 -101
  120. package/jest.config.js +1 -1
  121. package/maestro/README.md +113 -113
  122. package/maestro/config.yaml +35 -35
  123. package/maestro/flows/login.yaml +62 -62
  124. package/maestro/flows/mfa-login.yaml +92 -92
  125. package/maestro/flows/mfa-setup.yaml +86 -86
  126. package/maestro/flows/navigation.yaml +68 -68
  127. package/maestro/flows/offline-conflict.yaml +101 -101
  128. package/maestro/flows/offline-sync.yaml +128 -128
  129. package/maestro/flows/offline.yaml +60 -60
  130. package/maestro/flows/register.yaml +94 -94
  131. package/package.json +188 -176
  132. package/scripts/generate-placeholders.js +38 -0
  133. package/services/analytics/adapters/console.ts +50 -0
  134. package/services/analytics/analytics-adapter.ts +94 -0
  135. package/services/analytics/types.ts +73 -0
  136. package/services/analytics.ts +428 -428
  137. package/services/api.ts +419 -340
  138. package/services/auth/social/apple.ts +110 -0
  139. package/services/auth/social/google.ts +159 -0
  140. package/services/auth/social/social-auth.ts +100 -0
  141. package/services/auth/social/types.ts +80 -0
  142. package/services/authAdapter.ts +333 -333
  143. package/services/backgroundSync.ts +652 -626
  144. package/services/feature-flags/adapters/mock.ts +108 -0
  145. package/services/feature-flags/feature-flag-adapter.ts +174 -0
  146. package/services/feature-flags/types.ts +79 -0
  147. package/services/force-update.ts +140 -0
  148. package/services/index.ts +116 -54
  149. package/services/media/compression.ts +91 -0
  150. package/services/media/media-picker.ts +151 -0
  151. package/services/media/media-upload.ts +160 -0
  152. package/services/payments/adapters/mock.ts +159 -0
  153. package/services/payments/payment-adapter.ts +118 -0
  154. package/services/payments/types.ts +131 -0
  155. package/services/permissions/permission-manager.ts +284 -0
  156. package/services/permissions/types.ts +104 -0
  157. package/services/realtime/types.ts +100 -0
  158. package/services/realtime/websocket-manager.ts +441 -0
  159. package/services/security.ts +289 -286
  160. package/services/sentry.ts +4 -4
  161. package/stores/appStore.ts +9 -0
  162. package/stores/notificationStore.ts +3 -1
  163. package/tailwind.config.js +47 -47
  164. package/tsconfig.json +37 -13
  165. package/types/user.ts +1 -1
  166. package/utils/accessibility.ts +446 -446
  167. package/utils/animations/presets.ts +182 -0
  168. package/utils/animations/transitions.ts +62 -0
  169. package/utils/index.ts +63 -52
  170. package/utils/toast.ts +9 -2
  171. package/utils/validation.ts +4 -1
  172. package/utils/withAccessibility.tsx +272 -272
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @fileoverview Payment and subscription type definitions
3
+ * Defines the adapter interface, product/purchase models, and subscription types
4
+ * for the payment system.
5
+ * @module services/payments/types
6
+ */
7
+
8
+ // ============================================================================
9
+ // Product Types
10
+ // ============================================================================
11
+
12
+ /** The type of in-app product */
13
+ export type ProductType = "consumable" | "non_consumable" | "subscription";
14
+
15
+ /** Subscription billing period */
16
+ export type SubscriptionPeriod = "weekly" | "monthly" | "quarterly" | "yearly";
17
+
18
+ /**
19
+ * Represents a purchasable product from the app store.
20
+ */
21
+ export interface Product {
22
+ /** Unique product identifier (matches store product ID) */
23
+ id: string;
24
+ /** Display title of the product */
25
+ title: string;
26
+ /** Short description of the product */
27
+ description: string;
28
+ /** Numeric price in the smallest currency unit */
29
+ price: number;
30
+ /** Localized price string for display (e.g. "$9.99") */
31
+ priceString: string;
32
+ /** ISO 4217 currency code (e.g. "USD") */
33
+ currency: string;
34
+ /** The type of product */
35
+ type: ProductType;
36
+ /** Billing period for subscription products */
37
+ subscriptionPeriod?: SubscriptionPeriod;
38
+ }
39
+
40
+ // ============================================================================
41
+ // Purchase Types
42
+ // ============================================================================
43
+
44
+ /**
45
+ * Represents a completed purchase transaction.
46
+ */
47
+ export interface Purchase {
48
+ /** Unique purchase/transaction identifier */
49
+ id: string;
50
+ /** The product that was purchased */
51
+ productId: string;
52
+ /** ISO 8601 date string of the transaction */
53
+ transactionDate: string;
54
+ /** Optional receipt data for server-side validation */
55
+ transactionReceipt?: string;
56
+ }
57
+
58
+ // ============================================================================
59
+ // Subscription Types
60
+ // ============================================================================
61
+
62
+ /** Current status of a user's subscription */
63
+ export type SubscriptionStatus =
64
+ | "active"
65
+ | "expired"
66
+ | "cancelled"
67
+ | "grace_period"
68
+ | "none";
69
+
70
+ /**
71
+ * Detailed information about a user's subscription.
72
+ */
73
+ export interface SubscriptionInfo {
74
+ /** Current subscription status */
75
+ status: SubscriptionStatus;
76
+ /** The subscribed product ID, or null if no subscription */
77
+ productId: string | null;
78
+ /** ISO 8601 expiration date, or null if no subscription */
79
+ expiresAt: string | null;
80
+ /** Whether the subscription will auto-renew */
81
+ willRenew: boolean;
82
+ }
83
+
84
+ // ============================================================================
85
+ // Adapter Interface
86
+ // ============================================================================
87
+
88
+ /**
89
+ * Interface that all payment adapters must implement.
90
+ * Swap adapters to switch between providers (RevenueCat, Qonversion, etc.)
91
+ * without changing application code.
92
+ */
93
+ export interface PaymentAdapter {
94
+ /**
95
+ * Initialize the payment provider.
96
+ * Called once when the app starts.
97
+ */
98
+ initialize(): Promise<void>;
99
+
100
+ /**
101
+ * Fetch available products by their store IDs.
102
+ *
103
+ * @param ids - Array of product identifiers to fetch
104
+ * @returns Array of available products
105
+ */
106
+ getProducts(ids: string[]): Promise<Product[]>;
107
+
108
+ /**
109
+ * Initiate a purchase for the given product.
110
+ *
111
+ * @param productId - The product to purchase
112
+ * @returns The completed purchase record
113
+ * @throws Error if the purchase fails or is cancelled
114
+ */
115
+ purchase(productId: string): Promise<Purchase>;
116
+
117
+ /**
118
+ * Restore previously completed purchases.
119
+ * Useful when a user reinstalls the app or switches devices.
120
+ *
121
+ * @returns Array of restored purchases
122
+ */
123
+ restorePurchases(): Promise<Purchase[]>;
124
+
125
+ /**
126
+ * Get the current subscription status for the user.
127
+ *
128
+ * @returns Current subscription information
129
+ */
130
+ getSubscriptionStatus(): Promise<SubscriptionInfo>;
131
+ }
@@ -0,0 +1,284 @@
1
+ /**
2
+ * @fileoverview Centralized permission management service
3
+ * Provides a unified API for checking, requesting, and tracking permissions
4
+ * across different Expo modules.
5
+ * @module services/permissions/permission-manager
6
+ */
7
+
8
+ import { Platform, Linking } from "react-native";
9
+ import { Camera } from "expo-camera";
10
+ import * as Location from "expo-location";
11
+ import * as Contacts from "expo-contacts";
12
+ import * as MediaLibrary from "expo-media-library";
13
+ import * as Notifications from "expo-notifications";
14
+ import AsyncStorage from "@react-native-async-storage/async-storage";
15
+
16
+ import { STORAGE_KEYS } from "@/constants/config";
17
+ import type {
18
+ PermissionType,
19
+ PermissionResult,
20
+ PermissionStatus,
21
+ } from "./types";
22
+
23
+ /** AsyncStorage key prefix for tracking asked permissions */
24
+ const PERMISSION_ASKED_PREFIX = STORAGE_KEYS.PERMISSION_PREFIX;
25
+
26
+ /**
27
+ * Normalize native Expo permission status to our unified PermissionStatus.
28
+ * Handles the various status strings returned by different Expo modules.
29
+ *
30
+ * @param nativeStatus - The status string from an Expo permission response
31
+ * @param canAskAgain - Whether the system allows re-requesting the permission
32
+ * @returns Normalized PermissionStatus
33
+ */
34
+ export function normalizeStatus(
35
+ nativeStatus: string,
36
+ canAskAgain: boolean
37
+ ): PermissionStatus {
38
+ switch (nativeStatus) {
39
+ case "granted":
40
+ return "granted";
41
+ case "undetermined":
42
+ return "undetermined";
43
+ case "denied":
44
+ return canAskAgain ? "denied" : "blocked";
45
+ default:
46
+ return "undetermined";
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Permission handler interface for each permission type.
52
+ * Each handler provides check and request methods that return
53
+ * a normalized PermissionResult.
54
+ */
55
+ interface PermissionHandler {
56
+ check: () => Promise<PermissionResult>;
57
+ request: () => Promise<PermissionResult>;
58
+ }
59
+
60
+ /**
61
+ * Map of permission handlers for each supported PermissionType.
62
+ * Each handler wraps the corresponding Expo module's permission API.
63
+ */
64
+ const permissionHandlers: Record<PermissionType, PermissionHandler> = {
65
+ camera: {
66
+ check: async () => {
67
+ const result = await Camera.getCameraPermissionsAsync();
68
+ return {
69
+ status: normalizeStatus(result.status, result.canAskAgain),
70
+ canAskAgain: result.canAskAgain,
71
+ };
72
+ },
73
+ request: async () => {
74
+ const result = await Camera.requestCameraPermissionsAsync();
75
+ return {
76
+ status: normalizeStatus(result.status, result.canAskAgain),
77
+ canAskAgain: result.canAskAgain,
78
+ };
79
+ },
80
+ },
81
+
82
+ location: {
83
+ check: async () => {
84
+ const result = await Location.getForegroundPermissionsAsync();
85
+ return {
86
+ status: normalizeStatus(result.status, result.canAskAgain),
87
+ canAskAgain: result.canAskAgain,
88
+ };
89
+ },
90
+ request: async () => {
91
+ const result = await Location.requestForegroundPermissionsAsync();
92
+ return {
93
+ status: normalizeStatus(result.status, result.canAskAgain),
94
+ canAskAgain: result.canAskAgain,
95
+ };
96
+ },
97
+ },
98
+
99
+ locationAlways: {
100
+ check: async () => {
101
+ const result = await Location.getBackgroundPermissionsAsync();
102
+ return {
103
+ status: normalizeStatus(result.status, result.canAskAgain),
104
+ canAskAgain: result.canAskAgain,
105
+ };
106
+ },
107
+ request: async () => {
108
+ const result = await Location.requestBackgroundPermissionsAsync();
109
+ return {
110
+ status: normalizeStatus(result.status, result.canAskAgain),
111
+ canAskAgain: result.canAskAgain,
112
+ };
113
+ },
114
+ },
115
+
116
+ contacts: {
117
+ check: async () => {
118
+ const result = await Contacts.getPermissionsAsync();
119
+ return {
120
+ status: normalizeStatus(result.status, result.canAskAgain),
121
+ canAskAgain: result.canAskAgain,
122
+ };
123
+ },
124
+ request: async () => {
125
+ const result = await Contacts.requestPermissionsAsync();
126
+ return {
127
+ status: normalizeStatus(result.status, result.canAskAgain),
128
+ canAskAgain: result.canAskAgain,
129
+ };
130
+ },
131
+ },
132
+
133
+ mediaLibrary: {
134
+ check: async () => {
135
+ const result = await MediaLibrary.getPermissionsAsync();
136
+ return {
137
+ status: normalizeStatus(result.status, result.canAskAgain),
138
+ canAskAgain: result.canAskAgain,
139
+ };
140
+ },
141
+ request: async () => {
142
+ const result = await MediaLibrary.requestPermissionsAsync();
143
+ return {
144
+ status: normalizeStatus(result.status, result.canAskAgain),
145
+ canAskAgain: result.canAskAgain,
146
+ };
147
+ },
148
+ },
149
+
150
+ microphone: {
151
+ check: async () => {
152
+ const result = await Camera.getMicrophonePermissionsAsync();
153
+ return {
154
+ status: normalizeStatus(result.status, result.canAskAgain),
155
+ canAskAgain: result.canAskAgain,
156
+ };
157
+ },
158
+ request: async () => {
159
+ const result = await Camera.requestMicrophonePermissionsAsync();
160
+ return {
161
+ status: normalizeStatus(result.status, result.canAskAgain),
162
+ canAskAgain: result.canAskAgain,
163
+ };
164
+ },
165
+ },
166
+
167
+ notifications: {
168
+ check: async () => {
169
+ const result = await Notifications.getPermissionsAsync();
170
+ return {
171
+ status: normalizeStatus(result.status, result.canAskAgain),
172
+ canAskAgain: result.canAskAgain,
173
+ };
174
+ },
175
+ request: async () => {
176
+ const result = await Notifications.requestPermissionsAsync();
177
+ return {
178
+ status: normalizeStatus(result.status, result.canAskAgain),
179
+ canAskAgain: result.canAskAgain,
180
+ };
181
+ },
182
+ },
183
+ };
184
+
185
+ /**
186
+ * Centralized permission manager.
187
+ * Provides a unified API for all permission operations across the app.
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * import { PermissionManager } from '@/services/permissions/permission-manager';
192
+ *
193
+ * // Check camera permission
194
+ * const result = await PermissionManager.check('camera');
195
+ * if (result.status === 'granted') {
196
+ * // Camera is available
197
+ * }
198
+ *
199
+ * // Request notification permission
200
+ * const notifResult = await PermissionManager.request('notifications');
201
+ * if (notifResult.status === 'blocked') {
202
+ * await PermissionManager.openSettings();
203
+ * }
204
+ * ```
205
+ */
206
+ export const PermissionManager = {
207
+ /**
208
+ * Check the current status of a permission without requesting it.
209
+ *
210
+ * @param type - The permission type to check
211
+ * @returns The current permission result
212
+ */
213
+ async check(type: PermissionType): Promise<PermissionResult> {
214
+ try {
215
+ const handler = permissionHandlers[type];
216
+ return await handler.check();
217
+ } catch (error) {
218
+ console.error(`[PermissionManager] Failed to check ${type}:`, error);
219
+ return { status: "undetermined", canAskAgain: true };
220
+ }
221
+ },
222
+
223
+ /**
224
+ * Request a permission from the user.
225
+ * Records that the permission has been asked in AsyncStorage.
226
+ *
227
+ * @param type - The permission type to request
228
+ * @returns The permission result after the request
229
+ */
230
+ async request(type: PermissionType): Promise<PermissionResult> {
231
+ try {
232
+ const handler = permissionHandlers[type];
233
+ const result = await handler.request();
234
+
235
+ // Track that we've asked for this permission
236
+ await AsyncStorage.setItem(`${PERMISSION_ASKED_PREFIX}${type}`, "true");
237
+
238
+ return result;
239
+ } catch (error) {
240
+ console.error(`[PermissionManager] Failed to request ${type}:`, error);
241
+ return { status: "undetermined", canAskAgain: true };
242
+ }
243
+ },
244
+
245
+ /**
246
+ * Open the device settings so the user can manually change permissions.
247
+ * On iOS, opens the app-specific settings page.
248
+ * On Android, uses Linking.openSettings() to open app info.
249
+ */
250
+ async openSettings(): Promise<void> {
251
+ try {
252
+ if (Platform.OS === "ios") {
253
+ await Linking.openURL("app-settings:");
254
+ } else {
255
+ await Linking.openSettings();
256
+ }
257
+ } catch (error) {
258
+ console.error("[PermissionManager] Failed to open settings:", error);
259
+ }
260
+ },
261
+
262
+ /**
263
+ * Check whether a permission has been previously asked.
264
+ * Useful for determining whether to show a pre-permission explanation
265
+ * before the system dialog.
266
+ *
267
+ * @param type - The permission type to check
268
+ * @returns Whether the permission has been previously requested
269
+ */
270
+ async hasBeenAsked(type: PermissionType): Promise<boolean> {
271
+ try {
272
+ const value = await AsyncStorage.getItem(
273
+ `${PERMISSION_ASKED_PREFIX}${type}`
274
+ );
275
+ return value === "true";
276
+ } catch (error) {
277
+ console.error(
278
+ `[PermissionManager] Failed to check if ${type} was asked:`,
279
+ error
280
+ );
281
+ return false;
282
+ }
283
+ },
284
+ };
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @fileoverview Permission types and default configurations
3
+ * Centralized type definitions for the permission management system.
4
+ * @module services/permissions/types
5
+ */
6
+
7
+ /**
8
+ * Supported permission types across the app.
9
+ * Maps to corresponding Expo permission modules.
10
+ */
11
+ export type PermissionType =
12
+ | "camera"
13
+ | "location"
14
+ | "locationAlways"
15
+ | "contacts"
16
+ | "mediaLibrary"
17
+ | "microphone"
18
+ | "notifications";
19
+
20
+ /**
21
+ * Normalized permission status across platforms.
22
+ * - 'undetermined': Permission has not been requested yet
23
+ * - 'granted': Permission has been granted
24
+ * - 'denied': Permission was denied but can be requested again
25
+ * - 'blocked': Permission was denied and cannot be requested again (must open Settings)
26
+ */
27
+ export type PermissionStatus =
28
+ | "undetermined"
29
+ | "granted"
30
+ | "denied"
31
+ | "blocked";
32
+
33
+ /**
34
+ * Result of a permission check or request
35
+ */
36
+ export interface PermissionResult {
37
+ /** Normalized permission status */
38
+ status: PermissionStatus;
39
+ /** Whether the system will show the permission dialog if requested again */
40
+ canAskAgain: boolean;
41
+ }
42
+
43
+ /**
44
+ * Configuration for permission request UI
45
+ */
46
+ export interface PermissionConfig {
47
+ /** Title shown in the permission request UI */
48
+ title: string;
49
+ /** Descriptive message explaining why the permission is needed */
50
+ message: string;
51
+ /** Ionicons icon name to display */
52
+ icon: string;
53
+ }
54
+
55
+ /**
56
+ * Default UI configurations for each permission type.
57
+ * These can be overridden per-usage via the usePermission hook or PermissionGate component.
58
+ */
59
+ export const DEFAULT_PERMISSION_CONFIGS: Record<
60
+ PermissionType,
61
+ PermissionConfig
62
+ > = {
63
+ camera: {
64
+ title: "Camera Access",
65
+ message: "We need access to your camera to take photos and scan codes.",
66
+ icon: "camera-outline",
67
+ },
68
+ location: {
69
+ title: "Location Access",
70
+ message:
71
+ "We need access to your location to provide location-based features.",
72
+ icon: "location-outline",
73
+ },
74
+ locationAlways: {
75
+ title: "Background Location",
76
+ message:
77
+ "We need background location access to provide continuous location-based services.",
78
+ icon: "navigate-outline",
79
+ },
80
+ contacts: {
81
+ title: "Contacts Access",
82
+ message:
83
+ "We need access to your contacts to help you connect with friends.",
84
+ icon: "people-outline",
85
+ },
86
+ mediaLibrary: {
87
+ title: "Photo Library Access",
88
+ message:
89
+ "We need access to your photo library to let you select and save photos.",
90
+ icon: "images-outline",
91
+ },
92
+ microphone: {
93
+ title: "Microphone Access",
94
+ message:
95
+ "We need access to your microphone for audio recording and voice features.",
96
+ icon: "mic-outline",
97
+ },
98
+ notifications: {
99
+ title: "Push Notifications",
100
+ message:
101
+ "We need permission to send you notifications about important updates and messages.",
102
+ icon: "notifications-outline",
103
+ },
104
+ };
@@ -0,0 +1,100 @@
1
+ /**
2
+ * @fileoverview WebSocket and real-time communication type definitions
3
+ * Provides shared types for the WebSocket manager and React hooks.
4
+ * @module services/realtime/types
5
+ */
6
+
7
+ /**
8
+ * Possible states of a WebSocket connection.
9
+ * - 'connecting': Initial connection attempt in progress
10
+ * - 'connected': Connection is open and ready to send/receive
11
+ * - 'disconnected': Connection is closed (either intentionally or due to error)
12
+ * - 'reconnecting': Attempting to re-establish a lost connection
13
+ */
14
+ export type ConnectionStatus =
15
+ | "connecting"
16
+ | "connected"
17
+ | "disconnected"
18
+ | "reconnecting";
19
+
20
+ /**
21
+ * A typed WebSocket message with metadata.
22
+ * All messages sent or received through the WebSocket system use this envelope.
23
+ *
24
+ * @typeParam T - The shape of the message payload
25
+ */
26
+ export interface WebSocketMessage<T = unknown> {
27
+ /** Message type identifier (e.g. 'chat:message', 'presence_join') */
28
+ type: string;
29
+ /** Optional channel this message belongs to */
30
+ channel?: string;
31
+ /** The message payload */
32
+ payload: T;
33
+ /** ISO 8601 timestamp of when the message was created */
34
+ timestamp: string;
35
+ }
36
+
37
+ /**
38
+ * Configuration for the WebSocket connection.
39
+ */
40
+ export interface WebSocketConfig {
41
+ /** WebSocket server URL (ws:// or wss://) */
42
+ url: string;
43
+ /**
44
+ * Async function that returns an auth token.
45
+ * If provided, the token is appended as a query parameter on connect.
46
+ */
47
+ getToken?: () => Promise<string | null>;
48
+ /**
49
+ * Whether to automatically reconnect on unexpected disconnection.
50
+ * @default true
51
+ */
52
+ autoReconnect?: boolean;
53
+ /**
54
+ * Maximum number of reconnection attempts before giving up.
55
+ * @default 10
56
+ */
57
+ maxReconnectAttempts?: number;
58
+ /**
59
+ * Base delay in milliseconds for exponential backoff reconnection.
60
+ * The actual delay is `min(baseDelay * 2^attempt, 30000)`.
61
+ * @default 1000
62
+ */
63
+ reconnectBaseDelay?: number;
64
+ /**
65
+ * Interval in milliseconds between heartbeat (ping) messages.
66
+ * @default 30000
67
+ */
68
+ heartbeatInterval?: number;
69
+ /**
70
+ * Timeout in milliseconds for the initial connection attempt.
71
+ * @default 10000
72
+ */
73
+ connectionTimeout?: number;
74
+ }
75
+
76
+ /**
77
+ * Handler callback for typed WebSocket messages.
78
+ *
79
+ * @typeParam T - The shape of the message payload
80
+ */
81
+ export type MessageHandler<T = unknown> = (
82
+ message: WebSocketMessage<T>
83
+ ) => void;
84
+
85
+ /**
86
+ * Handler callback for connection status changes.
87
+ */
88
+ export type StatusHandler = (status: ConnectionStatus) => void;
89
+
90
+ /**
91
+ * User presence information for real-time presence tracking.
92
+ */
93
+ export interface PresenceUser {
94
+ /** Unique user identifier */
95
+ id: string;
96
+ /** Optional display name */
97
+ name?: string;
98
+ /** ISO 8601 timestamp of the user's last known activity */
99
+ lastSeen: string;
100
+ }