@croacroa/react-native-template 2.0.1 → 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 -0
  10. package/README.md +446 -399
  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 -0
  69. package/components/ui/UploadProgress.tsx +189 -0
  70. package/components/ui/VirtualizedList.tsx +288 -285
  71. package/components/ui/index.ts +28 -23
  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 -27
  89. package/hooks/useAnimatedEntry.ts +204 -0
  90. package/hooks/useApi.ts +64 -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 -0
  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 -175
  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
@@ -1,286 +1,289 @@
1
- /**
2
- * @fileoverview Security utilities for enhanced app protection
3
- * Provides SSL pinning validation, request signing, and security checks.
4
- * @module services/security
5
- */
6
-
7
- import { SECURITY, IS_DEV } from "@/constants/config";
8
-
9
- /**
10
- * Validate SSL certificate pins for a given hostname.
11
- * Note: In React Native, actual certificate pinning requires native module configuration.
12
- * This service provides the configuration and validation helpers.
13
- *
14
- * For full SSL pinning, configure your native modules:
15
- * - iOS: Use TrustKit or configure ATS in Info.plist
16
- * - Android: Configure network_security_config.xml
17
- *
18
- * @param hostname - The hostname to validate pins for
19
- * @returns Array of certificate pins for the hostname, or empty array if not configured
20
- *
21
- * @example
22
- * ```ts
23
- * const pins = getCertificatePins('api.yourapp.com');
24
- * if (pins.length > 0) {
25
- * // Use pins for validation
26
- * }
27
- * ```
28
- */
29
- export function getCertificatePins(hostname: string): string[] {
30
- if (!SECURITY.ENABLE_SSL_PINNING) {
31
- return [];
32
- }
33
-
34
- return SECURITY.SSL_PINS[hostname] || [];
35
- }
36
-
37
- /**
38
- * Check if SSL pinning is enabled for a hostname
39
- */
40
- export function isSslPinningEnabled(hostname: string): boolean {
41
- if (!SECURITY.ENABLE_SSL_PINNING) {
42
- return false;
43
- }
44
-
45
- const pins = SECURITY.SSL_PINS[hostname];
46
- return Array.isArray(pins) && pins.length > 0;
47
- }
48
-
49
- /**
50
- * Generate a request signature for API requests.
51
- * Useful for preventing request tampering and replay attacks.
52
- *
53
- * @param method - HTTP method
54
- * @param url - Request URL
55
- * @param body - Request body (optional)
56
- * @param timestamp - Request timestamp
57
- * @returns Signature string or null if signing is disabled
58
- *
59
- * @example
60
- * ```ts
61
- * const signature = generateRequestSignature('POST', '/api/users', { name: 'John' }, Date.now());
62
- * if (signature) {
63
- * headers['X-Request-Signature'] = signature;
64
- * }
65
- * ```
66
- */
67
- export function generateRequestSignature(
68
- method: string,
69
- url: string,
70
- body: unknown,
71
- timestamp: number
72
- ): string | null {
73
- if (!SECURITY.REQUEST_SIGNING.ENABLED) {
74
- return null;
75
- }
76
-
77
- // Create payload to sign
78
- const payload = [
79
- method.toUpperCase(),
80
- url,
81
- body ? JSON.stringify(body) : "",
82
- timestamp.toString(),
83
- ].join(":");
84
-
85
- // In production, use a proper HMAC implementation with a secret key
86
- // This is a placeholder - implement actual signing based on your backend requirements
87
- if (IS_DEV) {
88
- console.log("[Security] Would sign payload:", payload);
89
- }
90
-
91
- // TODO: Implement actual HMAC signing
92
- // const hmac = crypto.createHmac(SECURITY.REQUEST_SIGNING.ALGORITHM, SECRET_KEY);
93
- // return hmac.update(payload).digest('base64');
94
-
95
- return null;
96
- }
97
-
98
- /**
99
- * Security headers to add to API requests
100
- */
101
- export function getSecurityHeaders(): Record<string, string> {
102
- const headers: Record<string, string> = {};
103
-
104
- // Add timestamp for request signing/replay protection
105
- if (SECURITY.REQUEST_SIGNING.ENABLED) {
106
- headers["X-Request-Timestamp"] = Date.now().toString();
107
- }
108
-
109
- return headers;
110
- }
111
-
112
- /**
113
- * Validate that a URL is allowed (not blocked by security policy)
114
- * Useful for preventing open redirects and SSRF
115
- *
116
- * @param url - URL to validate
117
- * @param allowedHosts - List of allowed hostnames
118
- * @returns True if URL is allowed
119
- */
120
- export function isUrlAllowed(url: string, allowedHosts: string[]): boolean {
121
- try {
122
- const parsed = new URL(url);
123
- return allowedHosts.some((host) => {
124
- // Support wildcards (e.g., *.yourapp.com)
125
- if (host.startsWith("*.")) {
126
- const domain = host.slice(2);
127
- return (
128
- parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`)
129
- );
130
- }
131
- return parsed.hostname === host;
132
- });
133
- } catch {
134
- // Invalid URL
135
- return false;
136
- }
137
- }
138
-
139
- /**
140
- * Sanitize user input to prevent XSS
141
- * Basic sanitization - use a proper library for comprehensive XSS protection
142
- */
143
- export function sanitizeInput(input: string): string {
144
- return input
145
- .replace(/&/g, "&amp;")
146
- .replace(/</g, "&lt;")
147
- .replace(/>/g, "&gt;")
148
- .replace(/"/g, "&quot;")
149
- .replace(/'/g, "&#x27;");
150
- }
151
-
152
- /**
153
- * Check if the app is running in a potentially compromised environment
154
- * (rooted/jailbroken device, debugger attached, etc.)
155
- *
156
- * Note: This requires additional native modules for full detection:
157
- * - iOS: Consider using a jailbreak detection library
158
- * - Android: Consider using SafetyNet Attestation or Play Integrity API
159
- */
160
- export async function checkSecurityEnvironment(): Promise<{
161
- isSecure: boolean;
162
- warnings: string[];
163
- }> {
164
- const warnings: string[] = [];
165
-
166
- // Check for development mode
167
- if (IS_DEV) {
168
- warnings.push("Running in development mode");
169
- }
170
-
171
- // Check for debugger
172
- // Note: __DEV__ is automatically true when debugger is attached
173
- if (__DEV__) {
174
- warnings.push("Debugger may be attached");
175
- }
176
-
177
- // Additional checks would require native modules:
178
- // - Jailbreak/root detection
179
- // - Frida/debugging tools detection
180
- // - Emulator detection
181
- // - Hook detection
182
-
183
- return {
184
- isSecure: warnings.length === 0,
185
- warnings,
186
- };
187
- }
188
-
189
- /**
190
- * SSL Pinning configuration for native modules.
191
- * Export this configuration to use with native SSL pinning libraries.
192
- *
193
- * For iOS (TrustKit), add to Info.plist or configure programmatically.
194
- * For Android, use network_security_config.xml.
195
- */
196
- export const SSL_PINNING_CONFIG = {
197
- enabled: SECURITY.ENABLE_SSL_PINNING,
198
- pins: SECURITY.SSL_PINS,
199
-
200
- /**
201
- * Generate Android network_security_config.xml content
202
- * Save this to android/app/src/main/res/xml/network_security_config.xml
203
- * and reference it in AndroidManifest.xml:
204
- * <application android:networkSecurityConfig="@xml/network_security_config">
205
- */
206
- getAndroidConfig(): string {
207
- const pinEntries = Object.entries(SECURITY.SSL_PINS)
208
- .map(([domain, pins]) => {
209
- const pinElements = pins
210
- .map((pin) => ` <pin digest="SHA-256">${pin.replace("sha256/", "")}</pin>`)
211
- .join("\n");
212
-
213
- return ` <domain-config cleartextTrafficPermitted="false">
214
- <domain includeSubdomains="true">${domain}</domain>
215
- <pin-set expiration="2026-12-31">
216
- ${pinElements}
217
- </pin-set>
218
- </domain-config>`;
219
- })
220
- .join("\n");
221
-
222
- return `<?xml version="1.0" encoding="utf-8"?>
223
- <network-security-config>
224
- <base-config cleartextTrafficPermitted="false">
225
- <trust-anchors>
226
- <certificates src="system" />
227
- </trust-anchors>
228
- </base-config>
229
- ${pinEntries}
230
- </network-security-config>`;
231
- },
232
-
233
- /**
234
- * Generate iOS TrustKit configuration dictionary
235
- * Add this to your AppDelegate.mm or use expo-build-properties plugin
236
- *
237
- * For Expo managed workflow, add to app.config.ts:
238
- * ```
239
- * plugins: [
240
- * ["expo-build-properties", {
241
- * ios: {
242
- * infoPlist: SSL_PINNING_CONFIG.getIOSInfoPlist()
243
- * }
244
- * }]
245
- * ]
246
- * ```
247
- */
248
- getIOSConfig(): Record<string, unknown> {
249
- const pinnedDomains: Record<string, unknown> = {};
250
-
251
- Object.entries(SECURITY.SSL_PINS).forEach(([domain, pins]) => {
252
- pinnedDomains[domain] = {
253
- TSKIncludeSubdomains: true,
254
- TSKEnforcePinning: true,
255
- TSKPublicKeyHashes: pins.map((pin) => pin.replace("sha256/", "")),
256
- };
257
- });
258
-
259
- return {
260
- TSKSwizzleNetworkDelegates: true,
261
- TSKPinnedDomains: pinnedDomains,
262
- };
263
- },
264
-
265
- /**
266
- * Generate iOS Info.plist entries for SSL pinning
267
- */
268
- getIOSInfoPlist(): Record<string, unknown> {
269
- return {
270
- NSAppTransportSecurity: {
271
- NSAllowsArbitraryLoads: false,
272
- NSPinnedDomains: Object.fromEntries(
273
- Object.entries(SECURITY.SSL_PINS).map(([domain, pins]) => [
274
- domain,
275
- {
276
- NSIncludesSubdomains: true,
277
- NSPinnedLeafIdentities: pins.map((pin) => ({
278
- "SPKI-SHA256-BASE64": pin.replace("sha256/", ""),
279
- })),
280
- },
281
- ])
282
- ),
283
- },
284
- };
285
- },
286
- };
1
+ /**
2
+ * @fileoverview Security utilities for enhanced app protection
3
+ * Provides SSL pinning validation, request signing, and security checks.
4
+ * @module services/security
5
+ */
6
+
7
+ import { SECURITY, IS_DEV } from "@/constants/config";
8
+
9
+ /**
10
+ * Validate SSL certificate pins for a given hostname.
11
+ * Note: In React Native, actual certificate pinning requires native module configuration.
12
+ * This service provides the configuration and validation helpers.
13
+ *
14
+ * For full SSL pinning, configure your native modules:
15
+ * - iOS: Use TrustKit or configure ATS in Info.plist
16
+ * - Android: Configure network_security_config.xml
17
+ *
18
+ * @param hostname - The hostname to validate pins for
19
+ * @returns Array of certificate pins for the hostname, or empty array if not configured
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const pins = getCertificatePins('api.yourapp.com');
24
+ * if (pins.length > 0) {
25
+ * // Use pins for validation
26
+ * }
27
+ * ```
28
+ */
29
+ export function getCertificatePins(hostname: string): string[] {
30
+ if (!SECURITY.ENABLE_SSL_PINNING) {
31
+ return [];
32
+ }
33
+
34
+ return SECURITY.SSL_PINS[hostname] || [];
35
+ }
36
+
37
+ /**
38
+ * Check if SSL pinning is enabled for a hostname
39
+ */
40
+ export function isSslPinningEnabled(hostname: string): boolean {
41
+ if (!SECURITY.ENABLE_SSL_PINNING) {
42
+ return false;
43
+ }
44
+
45
+ const pins = SECURITY.SSL_PINS[hostname];
46
+ return Array.isArray(pins) && pins.length > 0;
47
+ }
48
+
49
+ /**
50
+ * Generate a request signature for API requests.
51
+ * Useful for preventing request tampering and replay attacks.
52
+ *
53
+ * @param method - HTTP method
54
+ * @param url - Request URL
55
+ * @param body - Request body (optional)
56
+ * @param timestamp - Request timestamp
57
+ * @returns Signature string or null if signing is disabled
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const signature = generateRequestSignature('POST', '/api/users', { name: 'John' }, Date.now());
62
+ * if (signature) {
63
+ * headers['X-Request-Signature'] = signature;
64
+ * }
65
+ * ```
66
+ */
67
+ export function generateRequestSignature(
68
+ method: string,
69
+ url: string,
70
+ body: unknown,
71
+ timestamp: number
72
+ ): string | null {
73
+ if (!SECURITY.REQUEST_SIGNING.ENABLED) {
74
+ return null;
75
+ }
76
+
77
+ // Create payload to sign
78
+ const payload = [
79
+ method.toUpperCase(),
80
+ url,
81
+ body ? JSON.stringify(body) : "",
82
+ timestamp.toString(),
83
+ ].join(":");
84
+
85
+ // In production, use a proper HMAC implementation with a secret key
86
+ // This is a placeholder - implement actual signing based on your backend requirements
87
+ if (IS_DEV) {
88
+ console.log("[Security] Would sign payload:", payload);
89
+ }
90
+
91
+ // TODO: Implement actual HMAC signing
92
+ // const hmac = crypto.createHmac(SECURITY.REQUEST_SIGNING.ALGORITHM, SECRET_KEY);
93
+ // return hmac.update(payload).digest('base64');
94
+
95
+ return null;
96
+ }
97
+
98
+ /**
99
+ * Security headers to add to API requests
100
+ */
101
+ export function getSecurityHeaders(): Record<string, string> {
102
+ const headers: Record<string, string> = {};
103
+
104
+ // Add timestamp for request signing/replay protection
105
+ if (SECURITY.REQUEST_SIGNING.ENABLED) {
106
+ headers["X-Request-Timestamp"] = Date.now().toString();
107
+ }
108
+
109
+ return headers;
110
+ }
111
+
112
+ /**
113
+ * Validate that a URL is allowed (not blocked by security policy)
114
+ * Useful for preventing open redirects and SSRF
115
+ *
116
+ * @param url - URL to validate
117
+ * @param allowedHosts - List of allowed hostnames
118
+ * @returns True if URL is allowed
119
+ */
120
+ export function isUrlAllowed(url: string, allowedHosts: string[]): boolean {
121
+ try {
122
+ const parsed = new URL(url);
123
+ return allowedHosts.some((host) => {
124
+ // Support wildcards (e.g., *.yourapp.com)
125
+ if (host.startsWith("*.")) {
126
+ const domain = host.slice(2);
127
+ return (
128
+ parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`)
129
+ );
130
+ }
131
+ return parsed.hostname === host;
132
+ });
133
+ } catch {
134
+ // Invalid URL
135
+ return false;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Sanitize user input to prevent XSS
141
+ * Basic sanitization - use a proper library for comprehensive XSS protection
142
+ */
143
+ export function sanitizeInput(input: string): string {
144
+ return input
145
+ .replace(/&/g, "&amp;")
146
+ .replace(/</g, "&lt;")
147
+ .replace(/>/g, "&gt;")
148
+ .replace(/"/g, "&quot;")
149
+ .replace(/'/g, "&#x27;");
150
+ }
151
+
152
+ /**
153
+ * Check if the app is running in a potentially compromised environment
154
+ * (rooted/jailbroken device, debugger attached, etc.)
155
+ *
156
+ * Note: This requires additional native modules for full detection:
157
+ * - iOS: Consider using a jailbreak detection library
158
+ * - Android: Consider using SafetyNet Attestation or Play Integrity API
159
+ */
160
+ export async function checkSecurityEnvironment(): Promise<{
161
+ isSecure: boolean;
162
+ warnings: string[];
163
+ }> {
164
+ const warnings: string[] = [];
165
+
166
+ // Check for development mode
167
+ if (IS_DEV) {
168
+ warnings.push("Running in development mode");
169
+ }
170
+
171
+ // Check for debugger
172
+ // Note: __DEV__ is automatically true when debugger is attached
173
+ if (__DEV__) {
174
+ warnings.push("Debugger may be attached");
175
+ }
176
+
177
+ // Additional checks would require native modules:
178
+ // - Jailbreak/root detection
179
+ // - Frida/debugging tools detection
180
+ // - Emulator detection
181
+ // - Hook detection
182
+
183
+ return {
184
+ isSecure: warnings.length === 0,
185
+ warnings,
186
+ };
187
+ }
188
+
189
+ /**
190
+ * SSL Pinning configuration for native modules.
191
+ * Export this configuration to use with native SSL pinning libraries.
192
+ *
193
+ * For iOS (TrustKit), add to Info.plist or configure programmatically.
194
+ * For Android, use network_security_config.xml.
195
+ */
196
+ export const SSL_PINNING_CONFIG = {
197
+ enabled: SECURITY.ENABLE_SSL_PINNING,
198
+ pins: SECURITY.SSL_PINS,
199
+
200
+ /**
201
+ * Generate Android network_security_config.xml content
202
+ * Save this to android/app/src/main/res/xml/network_security_config.xml
203
+ * and reference it in AndroidManifest.xml:
204
+ * <application android:networkSecurityConfig="@xml/network_security_config">
205
+ */
206
+ getAndroidConfig(): string {
207
+ const pinEntries = Object.entries(SECURITY.SSL_PINS)
208
+ .map(([domain, pins]) => {
209
+ const pinElements = pins
210
+ .map(
211
+ (pin) =>
212
+ ` <pin digest="SHA-256">${pin.replace("sha256/", "")}</pin>`
213
+ )
214
+ .join("\n");
215
+
216
+ return ` <domain-config cleartextTrafficPermitted="false">
217
+ <domain includeSubdomains="true">${domain}</domain>
218
+ <pin-set expiration="2026-12-31">
219
+ ${pinElements}
220
+ </pin-set>
221
+ </domain-config>`;
222
+ })
223
+ .join("\n");
224
+
225
+ return `<?xml version="1.0" encoding="utf-8"?>
226
+ <network-security-config>
227
+ <base-config cleartextTrafficPermitted="false">
228
+ <trust-anchors>
229
+ <certificates src="system" />
230
+ </trust-anchors>
231
+ </base-config>
232
+ ${pinEntries}
233
+ </network-security-config>`;
234
+ },
235
+
236
+ /**
237
+ * Generate iOS TrustKit configuration dictionary
238
+ * Add this to your AppDelegate.mm or use expo-build-properties plugin
239
+ *
240
+ * For Expo managed workflow, add to app.config.ts:
241
+ * ```
242
+ * plugins: [
243
+ * ["expo-build-properties", {
244
+ * ios: {
245
+ * infoPlist: SSL_PINNING_CONFIG.getIOSInfoPlist()
246
+ * }
247
+ * }]
248
+ * ]
249
+ * ```
250
+ */
251
+ getIOSConfig(): Record<string, unknown> {
252
+ const pinnedDomains: Record<string, unknown> = {};
253
+
254
+ Object.entries(SECURITY.SSL_PINS).forEach(([domain, pins]) => {
255
+ pinnedDomains[domain] = {
256
+ TSKIncludeSubdomains: true,
257
+ TSKEnforcePinning: true,
258
+ TSKPublicKeyHashes: pins.map((pin) => pin.replace("sha256/", "")),
259
+ };
260
+ });
261
+
262
+ return {
263
+ TSKSwizzleNetworkDelegates: true,
264
+ TSKPinnedDomains: pinnedDomains,
265
+ };
266
+ },
267
+
268
+ /**
269
+ * Generate iOS Info.plist entries for SSL pinning
270
+ */
271
+ getIOSInfoPlist(): Record<string, unknown> {
272
+ return {
273
+ NSAppTransportSecurity: {
274
+ NSAllowsArbitraryLoads: false,
275
+ NSPinnedDomains: Object.fromEntries(
276
+ Object.entries(SECURITY.SSL_PINS).map(([domain, pins]) => [
277
+ domain,
278
+ {
279
+ NSIncludesSubdomains: true,
280
+ NSPinnedLeafIdentities: pins.map((pin) => ({
281
+ "SPKI-SHA256-BASE64": pin.replace("sha256/", ""),
282
+ })),
283
+ },
284
+ ])
285
+ ),
286
+ },
287
+ };
288
+ },
289
+ };
@@ -32,9 +32,7 @@ export function initSentry() {
32
32
  // replaysOnErrorSampleRate: 1.0,
33
33
 
34
34
  // Integrations
35
- integrations: [
36
- Sentry.reactNativeTracingIntegration(),
37
- ],
35
+ integrations: [Sentry.reactNativeTracingIntegration()],
38
36
 
39
37
  // Filter out certain errors
40
38
  beforeSend(event) {
@@ -89,7 +87,9 @@ export function captureMessage(
89
87
  /**
90
88
  * Set user context for error tracking
91
89
  */
92
- export function setUser(user: { id: string; email?: string; name?: string } | null) {
90
+ export function setUser(
91
+ user: { id: string; email?: string; name?: string } | null
92
+ ) {
93
93
  if (user) {
94
94
  Sentry.setUser({
95
95
  id: user.id,
@@ -15,6 +15,10 @@ interface AppState {
15
15
  featureFlags: Record<string, boolean>;
16
16
  setFeatureFlag: (key: string, value: boolean) => void;
17
17
 
18
+ // Session tracking
19
+ sessionCount: number;
20
+ incrementSessionCount: () => void;
21
+
18
22
  // Reset all state
19
23
  reset: () => void;
20
24
  }
@@ -23,6 +27,7 @@ const initialState = {
23
27
  isLoading: false,
24
28
  hasCompletedOnboarding: false,
25
29
  featureFlags: {},
30
+ sessionCount: 0,
26
31
  };
27
32
 
28
33
  export const useAppStore = create<AppState>()(
@@ -40,6 +45,9 @@ export const useAppStore = create<AppState>()(
40
45
  featureFlags: { ...state.featureFlags, [key]: value },
41
46
  })),
42
47
 
48
+ incrementSessionCount: () =>
49
+ set((state) => ({ sessionCount: state.sessionCount + 1 })),
50
+
43
51
  reset: () => set(initialState),
44
52
  }),
45
53
  {
@@ -48,6 +56,7 @@ export const useAppStore = create<AppState>()(
48
56
  partialize: (state) => ({
49
57
  hasCompletedOnboarding: state.hasCompletedOnboarding,
50
58
  featureFlags: state.featureFlags,
59
+ sessionCount: state.sessionCount,
51
60
  }),
52
61
  }
53
62
  )
@@ -10,7 +10,9 @@ interface NotificationState {
10
10
  setPushToken: (token: string | null) => void;
11
11
  setIsEnabled: (enabled: boolean) => void;
12
12
  toggleNotifications: () => void;
13
- setLastNotification: (notification: Notifications.Notification | null) => void;
13
+ setLastNotification: (
14
+ notification: Notifications.Notification | null
15
+ ) => void;
14
16
  }
15
17
 
16
18
  export const useNotificationStore = create<NotificationState>()(