@harkenapp/sdk-react-native 0.0.1-alpha.1

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 (235) hide show
  1. package/README.md +67 -0
  2. package/app.plugin.cjs +135 -0
  3. package/app.plugin.js +1 -0
  4. package/dist/api/client.d.ts +67 -0
  5. package/dist/api/client.d.ts.map +1 -0
  6. package/dist/api/client.js +163 -0
  7. package/dist/api/client.js.map +1 -0
  8. package/dist/api/errors.d.ts +46 -0
  9. package/dist/api/errors.d.ts.map +1 -0
  10. package/dist/api/errors.js +72 -0
  11. package/dist/api/errors.js.map +1 -0
  12. package/dist/api/index.d.ts +7 -0
  13. package/dist/api/index.d.ts.map +1 -0
  14. package/dist/api/index.js +20 -0
  15. package/dist/api/index.js.map +1 -0
  16. package/dist/api/retry.d.ts +29 -0
  17. package/dist/api/retry.d.ts.map +1 -0
  18. package/dist/api/retry.js +74 -0
  19. package/dist/api/retry.js.map +1 -0
  20. package/dist/attachments/FeedbackSheet.d.ts +88 -0
  21. package/dist/attachments/FeedbackSheet.d.ts.map +1 -0
  22. package/dist/attachments/FeedbackSheet.js +250 -0
  23. package/dist/attachments/FeedbackSheet.js.map +1 -0
  24. package/dist/attachments/index.d.ts +20 -0
  25. package/dist/attachments/index.d.ts.map +1 -0
  26. package/dist/attachments/index.js +40 -0
  27. package/dist/attachments/index.js.map +1 -0
  28. package/dist/components/AttachmentGrid.d.ts +94 -0
  29. package/dist/components/AttachmentGrid.d.ts.map +1 -0
  30. package/dist/components/AttachmentGrid.js +132 -0
  31. package/dist/components/AttachmentGrid.js.map +1 -0
  32. package/dist/components/AttachmentPicker.d.ts +98 -0
  33. package/dist/components/AttachmentPicker.d.ts.map +1 -0
  34. package/dist/components/AttachmentPicker.js +297 -0
  35. package/dist/components/AttachmentPicker.js.map +1 -0
  36. package/dist/components/AttachmentPreview.d.ts +78 -0
  37. package/dist/components/AttachmentPreview.d.ts.map +1 -0
  38. package/dist/components/AttachmentPreview.js +133 -0
  39. package/dist/components/AttachmentPreview.js.map +1 -0
  40. package/dist/components/CategorySelector.d.ts +77 -0
  41. package/dist/components/CategorySelector.d.ts.map +1 -0
  42. package/dist/components/CategorySelector.js +117 -0
  43. package/dist/components/CategorySelector.js.map +1 -0
  44. package/dist/components/FeedbackForm.d.ts +50 -0
  45. package/dist/components/FeedbackForm.d.ts.map +1 -0
  46. package/dist/components/FeedbackForm.js +141 -0
  47. package/dist/components/FeedbackForm.js.map +1 -0
  48. package/dist/components/FeedbackSheet.d.ts +75 -0
  49. package/dist/components/FeedbackSheet.d.ts.map +1 -0
  50. package/dist/components/FeedbackSheet.js +215 -0
  51. package/dist/components/FeedbackSheet.js.map +1 -0
  52. package/dist/components/ThemedButton.d.ts +23 -0
  53. package/dist/components/ThemedButton.d.ts.map +1 -0
  54. package/dist/components/ThemedButton.js +77 -0
  55. package/dist/components/ThemedButton.js.map +1 -0
  56. package/dist/components/ThemedText.d.ts +16 -0
  57. package/dist/components/ThemedText.d.ts.map +1 -0
  58. package/dist/components/ThemedText.js +44 -0
  59. package/dist/components/ThemedText.js.map +1 -0
  60. package/dist/components/ThemedTextInput.d.ts +13 -0
  61. package/dist/components/ThemedTextInput.d.ts.map +1 -0
  62. package/dist/components/ThemedTextInput.js +76 -0
  63. package/dist/components/ThemedTextInput.js.map +1 -0
  64. package/dist/components/UploadStatusOverlay.d.ts +82 -0
  65. package/dist/components/UploadStatusOverlay.d.ts.map +1 -0
  66. package/dist/components/UploadStatusOverlay.js +319 -0
  67. package/dist/components/UploadStatusOverlay.js.map +1 -0
  68. package/dist/components/index.d.ts +19 -0
  69. package/dist/components/index.d.ts.map +1 -0
  70. package/dist/components/index.js +28 -0
  71. package/dist/components/index.js.map +1 -0
  72. package/dist/context/HarkenContext.d.ts +62 -0
  73. package/dist/context/HarkenContext.d.ts.map +1 -0
  74. package/dist/context/HarkenContext.js +128 -0
  75. package/dist/context/HarkenContext.js.map +1 -0
  76. package/dist/context/index.d.ts +3 -0
  77. package/dist/context/index.d.ts.map +1 -0
  78. package/dist/context/index.js +7 -0
  79. package/dist/context/index.js.map +1 -0
  80. package/dist/domain/index.d.ts +3 -0
  81. package/dist/domain/index.d.ts.map +1 -0
  82. package/dist/domain/index.js +7 -0
  83. package/dist/domain/index.js.map +1 -0
  84. package/dist/domain/upload-queue.d.ts +116 -0
  85. package/dist/domain/upload-queue.d.ts.map +1 -0
  86. package/dist/domain/upload-queue.js +34 -0
  87. package/dist/domain/upload-queue.js.map +1 -0
  88. package/dist/hooks/index.d.ts +6 -0
  89. package/dist/hooks/index.d.ts.map +1 -0
  90. package/dist/hooks/index.js +16 -0
  91. package/dist/hooks/index.js.map +1 -0
  92. package/dist/hooks/useAnonymousId.d.ts +28 -0
  93. package/dist/hooks/useAnonymousId.d.ts.map +1 -0
  94. package/dist/hooks/useAnonymousId.js +59 -0
  95. package/dist/hooks/useAnonymousId.js.map +1 -0
  96. package/dist/hooks/useAttachmentPicker.d.ts +84 -0
  97. package/dist/hooks/useAttachmentPicker.d.ts.map +1 -0
  98. package/dist/hooks/useAttachmentPicker.js +181 -0
  99. package/dist/hooks/useAttachmentPicker.js.map +1 -0
  100. package/dist/hooks/useAttachmentStatus.d.ts +51 -0
  101. package/dist/hooks/useAttachmentStatus.d.ts.map +1 -0
  102. package/dist/hooks/useAttachmentStatus.js +69 -0
  103. package/dist/hooks/useAttachmentStatus.js.map +1 -0
  104. package/dist/hooks/useAttachmentUpload.d.ts +101 -0
  105. package/dist/hooks/useAttachmentUpload.d.ts.map +1 -0
  106. package/dist/hooks/useAttachmentUpload.js +293 -0
  107. package/dist/hooks/useAttachmentUpload.js.map +1 -0
  108. package/dist/hooks/useFeedback.d.ts +55 -0
  109. package/dist/hooks/useFeedback.d.ts.map +1 -0
  110. package/dist/hooks/useFeedback.js +96 -0
  111. package/dist/hooks/useFeedback.js.map +1 -0
  112. package/dist/hooks/useHarkenContext.d.ts +25 -0
  113. package/dist/hooks/useHarkenContext.d.ts.map +1 -0
  114. package/dist/hooks/useHarkenContext.js +35 -0
  115. package/dist/hooks/useHarkenContext.js.map +1 -0
  116. package/dist/hooks/useHarkenTheme.d.ts +26 -0
  117. package/dist/hooks/useHarkenTheme.d.ts.map +1 -0
  118. package/dist/hooks/useHarkenTheme.js +36 -0
  119. package/dist/hooks/useHarkenTheme.js.map +1 -0
  120. package/dist/index.d.ts +49 -0
  121. package/dist/index.d.ts.map +1 -0
  122. package/dist/index.js +91 -0
  123. package/dist/index.js.map +1 -0
  124. package/dist/services/index.d.ts +4 -0
  125. package/dist/services/index.d.ts.map +1 -0
  126. package/dist/services/index.js +9 -0
  127. package/dist/services/index.js.map +1 -0
  128. package/dist/services/uploadQueueService.d.ts +193 -0
  129. package/dist/services/uploadQueueService.d.ts.map +1 -0
  130. package/dist/services/uploadQueueService.js +623 -0
  131. package/dist/services/uploadQueueService.js.map +1 -0
  132. package/dist/services/uploadQueueStorage.d.ts +30 -0
  133. package/dist/services/uploadQueueStorage.d.ts.map +1 -0
  134. package/dist/services/uploadQueueStorage.js +77 -0
  135. package/dist/services/uploadQueueStorage.js.map +1 -0
  136. package/dist/storage/IdentityStore.d.ts +38 -0
  137. package/dist/storage/IdentityStore.d.ts.map +1 -0
  138. package/dist/storage/IdentityStore.js +83 -0
  139. package/dist/storage/IdentityStore.js.map +1 -0
  140. package/dist/storage/SecureStoreAdapter.d.ts +28 -0
  141. package/dist/storage/SecureStoreAdapter.d.ts.map +1 -0
  142. package/dist/storage/SecureStoreAdapter.js +52 -0
  143. package/dist/storage/SecureStoreAdapter.js.map +1 -0
  144. package/dist/storage/defaultStorage.d.ts +20 -0
  145. package/dist/storage/defaultStorage.d.ts.map +1 -0
  146. package/dist/storage/defaultStorage.js +131 -0
  147. package/dist/storage/defaultStorage.js.map +1 -0
  148. package/dist/storage/index.d.ts +6 -0
  149. package/dist/storage/index.d.ts.map +1 -0
  150. package/dist/storage/index.js +13 -0
  151. package/dist/storage/index.js.map +1 -0
  152. package/dist/storage/types.d.ts +32 -0
  153. package/dist/storage/types.d.ts.map +1 -0
  154. package/dist/storage/types.js +11 -0
  155. package/dist/storage/types.js.map +1 -0
  156. package/dist/theme/defaults.d.ts +43 -0
  157. package/dist/theme/defaults.d.ts.map +1 -0
  158. package/dist/theme/defaults.js +128 -0
  159. package/dist/theme/defaults.js.map +1 -0
  160. package/dist/theme/index.d.ts +3 -0
  161. package/dist/theme/index.d.ts.map +1 -0
  162. package/dist/theme/index.js +14 -0
  163. package/dist/theme/index.js.map +1 -0
  164. package/dist/theme/types.d.ts +136 -0
  165. package/dist/theme/types.d.ts.map +1 -0
  166. package/dist/theme/types.js +3 -0
  167. package/dist/theme/types.js.map +1 -0
  168. package/dist/types/config.d.ts +100 -0
  169. package/dist/types/config.d.ts.map +1 -0
  170. package/dist/types/config.js +3 -0
  171. package/dist/types/config.js.map +1 -0
  172. package/dist/types/index.d.ts +3 -0
  173. package/dist/types/index.d.ts.map +1 -0
  174. package/dist/types/index.js +3 -0
  175. package/dist/types/index.js.map +1 -0
  176. package/dist/types/openapi.d.ts +601 -0
  177. package/dist/types/openapi.d.ts.map +1 -0
  178. package/dist/types/openapi.js +7 -0
  179. package/dist/types/openapi.js.map +1 -0
  180. package/dist/utils/index.d.ts +2 -0
  181. package/dist/utils/index.d.ts.map +1 -0
  182. package/dist/utils/index.js +6 -0
  183. package/dist/utils/index.js.map +1 -0
  184. package/dist/utils/uuid.d.ts +10 -0
  185. package/dist/utils/uuid.d.ts.map +1 -0
  186. package/dist/utils/uuid.js +60 -0
  187. package/dist/utils/uuid.js.map +1 -0
  188. package/package.json +124 -0
  189. package/src/@types/expo-file-system-legacy.d.ts +13 -0
  190. package/src/api/client.ts +250 -0
  191. package/src/api/errors.ts +84 -0
  192. package/src/api/index.ts +15 -0
  193. package/src/api/retry.ts +99 -0
  194. package/src/attachments/FeedbackSheet.tsx +400 -0
  195. package/src/attachments/index.ts +70 -0
  196. package/src/components/AttachmentGrid.tsx +247 -0
  197. package/src/components/AttachmentPicker.tsx +391 -0
  198. package/src/components/AttachmentPreview.tsx +210 -0
  199. package/src/components/CategorySelector.tsx +174 -0
  200. package/src/components/FeedbackForm.tsx +216 -0
  201. package/src/components/FeedbackSheet.tsx +321 -0
  202. package/src/components/ThemedButton.tsx +127 -0
  203. package/src/components/ThemedText.tsx +65 -0
  204. package/src/components/ThemedTextInput.tsx +65 -0
  205. package/src/components/UploadStatusOverlay.tsx +440 -0
  206. package/src/components/index.ts +39 -0
  207. package/src/context/HarkenContext.tsx +129 -0
  208. package/src/context/index.ts +2 -0
  209. package/src/domain/index.ts +12 -0
  210. package/src/domain/upload-queue.ts +131 -0
  211. package/src/hooks/index.ts +10 -0
  212. package/src/hooks/useAnonymousId.ts +68 -0
  213. package/src/hooks/useAttachmentPicker.ts +243 -0
  214. package/src/hooks/useAttachmentStatus.ts +86 -0
  215. package/src/hooks/useAttachmentUpload.ts +370 -0
  216. package/src/hooks/useFeedback.ts +139 -0
  217. package/src/hooks/useHarkenContext.ts +35 -0
  218. package/src/hooks/useHarkenTheme.ts +36 -0
  219. package/src/index.ts +168 -0
  220. package/src/services/index.ts +11 -0
  221. package/src/services/uploadQueueService.ts +727 -0
  222. package/src/services/uploadQueueStorage.ts +78 -0
  223. package/src/storage/IdentityStore.ts +89 -0
  224. package/src/storage/SecureStoreAdapter.ts +59 -0
  225. package/src/storage/defaultStorage.ts +109 -0
  226. package/src/storage/index.ts +5 -0
  227. package/src/storage/types.ts +34 -0
  228. package/src/theme/defaults.ts +151 -0
  229. package/src/theme/index.ts +23 -0
  230. package/src/theme/types.ts +157 -0
  231. package/src/types/config.ts +112 -0
  232. package/src/types/index.ts +10 -0
  233. package/src/types/openapi.ts +601 -0
  234. package/src/utils/index.ts +1 -0
  235. package/src/utils/uuid.ts +77 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Persistent storage for the upload queue.
3
+ *
4
+ * Uses AsyncStorage to persist queue state across app restarts.
5
+ */
6
+
7
+ import AsyncStorage from '@react-native-async-storage/async-storage';
8
+ import type { PersistedQueue, QueueItem } from '../domain';
9
+
10
+ const STORAGE_KEY = '@harkenapp/upload-queue';
11
+ const CURRENT_VERSION = 1;
12
+
13
+ /**
14
+ * Manages persistent storage of the upload queue.
15
+ */
16
+ export class UploadQueueStorage {
17
+ /**
18
+ * Load queue items from persistent storage.
19
+ * Returns empty array if no queue exists or on error.
20
+ */
21
+ async loadQueue(): Promise<QueueItem[]> {
22
+ try {
23
+ const raw = await AsyncStorage.getItem(STORAGE_KEY);
24
+ if (!raw) {
25
+ return [];
26
+ }
27
+
28
+ const parsed = JSON.parse(raw) as PersistedQueue;
29
+
30
+ // Handle version migrations
31
+ if (parsed.version !== CURRENT_VERSION) {
32
+ return this.migrateQueue(parsed);
33
+ }
34
+
35
+ return parsed.items;
36
+ } catch (error) {
37
+ console.error('[UploadQueueStorage] Failed to load queue:', error);
38
+ return [];
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Save queue items to persistent storage.
44
+ */
45
+ async saveQueue(items: QueueItem[]): Promise<void> {
46
+ try {
47
+ const data: PersistedQueue = {
48
+ version: CURRENT_VERSION,
49
+ items,
50
+ };
51
+ await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(data));
52
+ } catch (error) {
53
+ console.error('[UploadQueueStorage] Failed to save queue:', error);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Clear all persisted queue data.
59
+ */
60
+ async clearQueue(): Promise<void> {
61
+ try {
62
+ await AsyncStorage.removeItem(STORAGE_KEY);
63
+ } catch (error) {
64
+ console.error('[UploadQueueStorage] Failed to clear queue:', error);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Migrate queue data from older versions.
70
+ * For now, unknown versions are reset to empty.
71
+ */
72
+ private migrateQueue(data: PersistedQueue): QueueItem[] {
73
+ console.warn(
74
+ `[UploadQueueStorage] Unknown queue version ${data.version}, resetting`
75
+ );
76
+ return [];
77
+ }
78
+ }
@@ -0,0 +1,89 @@
1
+ import type { SecureStorage } from './types';
2
+ import { STORAGE_KEYS } from './types';
3
+ import { generateUUID } from '../utils';
4
+
5
+ /**
6
+ * Manages anonymous identity persistence.
7
+ *
8
+ * The identity store generates and persists a stable anonymous ID
9
+ * that uniquely identifies the app installation without collecting PII.
10
+ */
11
+ export class IdentityStore {
12
+ private storage: SecureStorage;
13
+ private cachedAnonId: string | null = null;
14
+ private initPromise: Promise<string> | null = null;
15
+
16
+ constructor(storage: SecureStorage) {
17
+ this.storage = storage;
18
+ }
19
+
20
+ /**
21
+ * Get the anonymous ID, creating one if it doesn't exist.
22
+ *
23
+ * This method is safe to call multiple times concurrently.
24
+ * The ID is cached after first retrieval/creation.
25
+ *
26
+ * @returns The stable anonymous ID for this installation
27
+ */
28
+ async getAnonymousId(): Promise<string> {
29
+ // Return cached value if available
30
+ if (this.cachedAnonId) {
31
+ return this.cachedAnonId;
32
+ }
33
+
34
+ // Ensure only one initialization happens
35
+ if (!this.initPromise) {
36
+ this.initPromise = this.initializeAnonymousId();
37
+ }
38
+
39
+ return this.initPromise;
40
+ }
41
+
42
+ /**
43
+ * Initialize the anonymous ID by loading from storage or generating a new one.
44
+ */
45
+ private async initializeAnonymousId(): Promise<string> {
46
+ try {
47
+ // Try to load existing ID
48
+ const existingId = await this.storage.getItem(STORAGE_KEYS.ANON_ID);
49
+
50
+ if (existingId && this.isValidUUID(existingId)) {
51
+ this.cachedAnonId = existingId;
52
+ return existingId;
53
+ }
54
+
55
+ // Generate and persist a new ID
56
+ const newId = generateUUID();
57
+ await this.storage.setItem(STORAGE_KEYS.ANON_ID, newId);
58
+ this.cachedAnonId = newId;
59
+ return newId;
60
+ } catch {
61
+ // If storage fails, generate a transient ID
62
+ // This ensures the SDK still works even if storage is unavailable
63
+ const fallbackId = generateUUID();
64
+ this.cachedAnonId = fallbackId;
65
+ return fallbackId;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Clear the stored anonymous ID.
71
+ *
72
+ * After calling this, the next call to getAnonymousId will generate a new ID.
73
+ * Use this for "reset" functionality or testing.
74
+ */
75
+ async clearAnonymousId(): Promise<void> {
76
+ this.cachedAnonId = null;
77
+ this.initPromise = null;
78
+ await this.storage.deleteItem(STORAGE_KEYS.ANON_ID);
79
+ }
80
+
81
+ /**
82
+ * Validate that a string is a valid UUID v4 format.
83
+ */
84
+ private isValidUUID(value: string): boolean {
85
+ const uuidRegex =
86
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
87
+ return uuidRegex.test(value);
88
+ }
89
+ }
@@ -0,0 +1,59 @@
1
+ import type { SecureStorage } from './types';
2
+
3
+ /**
4
+ * Adapter for expo-secure-store.
5
+ *
6
+ * This adapter wraps expo-secure-store to implement the SecureStorage interface.
7
+ * It dynamically imports expo-secure-store to avoid bundling issues when not used.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * import { createSecureStoreAdapter } from '@harkenapp/sdk-react-native';
12
+ * import * as SecureStore from 'expo-secure-store';
13
+ *
14
+ * const storage = createSecureStoreAdapter(SecureStore);
15
+ * ```
16
+ */
17
+ export function createSecureStoreAdapter(secureStore: {
18
+ getItemAsync: (key: string) => Promise<string | null>;
19
+ setItemAsync: (key: string, value: string) => Promise<void>;
20
+ deleteItemAsync: (key: string) => Promise<void>;
21
+ }): SecureStorage {
22
+ return {
23
+ async getItem(key: string): Promise<string | null> {
24
+ return secureStore.getItemAsync(key);
25
+ },
26
+
27
+ async setItem(key: string, value: string): Promise<void> {
28
+ await secureStore.setItemAsync(key, value);
29
+ },
30
+
31
+ async deleteItem(key: string): Promise<void> {
32
+ await secureStore.deleteItemAsync(key);
33
+ },
34
+ };
35
+ }
36
+
37
+ /**
38
+ * In-memory storage for testing or when secure storage is unavailable.
39
+ * Values are not persisted across app restarts.
40
+ *
41
+ * @internal
42
+ */
43
+ export function createMemoryStorage(): SecureStorage {
44
+ const storage = new Map<string, string>();
45
+
46
+ return {
47
+ async getItem(key: string): Promise<string | null> {
48
+ return storage.get(key) ?? null;
49
+ },
50
+
51
+ async setItem(key: string, value: string): Promise<void> {
52
+ storage.set(key, value);
53
+ },
54
+
55
+ async deleteItem(key: string): Promise<void> {
56
+ storage.delete(key);
57
+ },
58
+ };
59
+ }
@@ -0,0 +1,109 @@
1
+ import type { SecureStorage } from './types';
2
+ import { createMemoryStorage } from './SecureStoreAdapter';
3
+
4
+ /**
5
+ * Cached default storage instance.
6
+ * @internal
7
+ */
8
+ let defaultStorageInstance: SecureStorage | null = null;
9
+
10
+ /**
11
+ * Whether we've already attempted to load expo-secure-store.
12
+ * @internal
13
+ */
14
+ let hasAttemptedLoad = false;
15
+
16
+ /**
17
+ * Creates or returns the default storage implementation.
18
+ *
19
+ * Attempts to use expo-secure-store if available, falling back to
20
+ * in-memory storage if not installed or if loading fails.
21
+ *
22
+ * @internal
23
+ */
24
+ export async function getDefaultStorage(): Promise<SecureStorage> {
25
+ if (defaultStorageInstance) {
26
+ return defaultStorageInstance;
27
+ }
28
+
29
+ if (!hasAttemptedLoad) {
30
+ hasAttemptedLoad = true;
31
+
32
+ try {
33
+ // Dynamically import expo-secure-store
34
+ const SecureStore = await import('expo-secure-store');
35
+
36
+ defaultStorageInstance = {
37
+ async getItem(key: string): Promise<string | null> {
38
+ return SecureStore.getItemAsync(key);
39
+ },
40
+ async setItem(key: string, value: string): Promise<void> {
41
+ await SecureStore.setItemAsync(key, value);
42
+ },
43
+ async deleteItem(key: string): Promise<void> {
44
+ await SecureStore.deleteItemAsync(key);
45
+ },
46
+ };
47
+
48
+ return defaultStorageInstance;
49
+ } catch {
50
+ // expo-secure-store not available, fall back to memory storage
51
+ console.warn(
52
+ '[Harken] expo-secure-store not available. Using in-memory storage. ' +
53
+ 'Anonymous IDs will not persist across app restarts. ' +
54
+ 'Install expo-secure-store for persistent storage.'
55
+ );
56
+ }
57
+ }
58
+
59
+ // Fall back to memory storage
60
+ if (!defaultStorageInstance) {
61
+ defaultStorageInstance = createMemoryStorage();
62
+ }
63
+
64
+ return defaultStorageInstance;
65
+ }
66
+
67
+ /**
68
+ * Synchronously creates a storage implementation.
69
+ *
70
+ * Returns a lazy wrapper that will resolve to expo-secure-store if available,
71
+ * or memory storage if not. The actual storage is initialized on first use.
72
+ *
73
+ * @internal
74
+ */
75
+ export function createDefaultStorage(): SecureStorage {
76
+ // Return a lazy wrapper that initializes on first use
77
+ let resolvedStorage: SecureStorage | null = null;
78
+ let initPromise: Promise<SecureStorage> | null = null;
79
+
80
+ const ensureInitialized = async (): Promise<SecureStorage> => {
81
+ if (resolvedStorage) {
82
+ return resolvedStorage;
83
+ }
84
+
85
+ if (!initPromise) {
86
+ initPromise = getDefaultStorage().then((storage) => {
87
+ resolvedStorage = storage;
88
+ return storage;
89
+ });
90
+ }
91
+
92
+ return initPromise;
93
+ };
94
+
95
+ return {
96
+ async getItem(key: string): Promise<string | null> {
97
+ const storage = await ensureInitialized();
98
+ return storage.getItem(key);
99
+ },
100
+ async setItem(key: string, value: string): Promise<void> {
101
+ const storage = await ensureInitialized();
102
+ await storage.setItem(key, value);
103
+ },
104
+ async deleteItem(key: string): Promise<void> {
105
+ const storage = await ensureInitialized();
106
+ await storage.deleteItem(key);
107
+ },
108
+ };
109
+ }
@@ -0,0 +1,5 @@
1
+ export type { SecureStorage } from './types';
2
+ export { STORAGE_KEYS } from './types';
3
+ export { createSecureStoreAdapter, createMemoryStorage } from './SecureStoreAdapter';
4
+ export { createDefaultStorage } from './defaultStorage';
5
+ export { IdentityStore } from './IdentityStore';
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Interface for secure key-value storage.
3
+ * Default implementation uses expo-secure-store.
4
+ * Can be replaced with custom implementations.
5
+ */
6
+ export interface SecureStorage {
7
+ /**
8
+ * Retrieve a value from secure storage.
9
+ * @param key - The key to retrieve
10
+ * @returns The stored value, or null if not found
11
+ */
12
+ getItem(key: string): Promise<string | null>;
13
+
14
+ /**
15
+ * Store a value in secure storage.
16
+ * @param key - The key to store under
17
+ * @param value - The value to store
18
+ */
19
+ setItem(key: string, value: string): Promise<void>;
20
+
21
+ /**
22
+ * Remove a value from secure storage.
23
+ * @param key - The key to remove
24
+ */
25
+ deleteItem(key: string): Promise<void>;
26
+ }
27
+
28
+ /**
29
+ * Storage keys used by the Harken SDK.
30
+ * @internal
31
+ */
32
+ export const STORAGE_KEYS = {
33
+ ANON_ID: 'harken_anon_id',
34
+ } as const;
@@ -0,0 +1,151 @@
1
+ import type {
2
+ HarkenColors,
3
+ HarkenTypography,
4
+ HarkenSpacing,
5
+ HarkenRadii,
6
+ HarkenTheme,
7
+ } from './types';
8
+
9
+ /**
10
+ * Default light mode colors.
11
+ * Neutral, accessible palette with no hard-coded branding.
12
+ */
13
+ export const lightColors: HarkenColors = {
14
+ primary: '#2563EB', // Blue 600
15
+ primaryPressed: '#1D4ED8', // Blue 700
16
+ background: '#FFFFFF',
17
+ backgroundSecondary: '#F9FAFB', // Gray 50
18
+ text: '#111827', // Gray 900
19
+ textSecondary: '#6B7280', // Gray 500
20
+ textPlaceholder: '#9CA3AF', // Gray 400
21
+ textOnPrimary: '#FFFFFF',
22
+ border: '#E5E7EB', // Gray 200
23
+ borderFocused: '#2563EB', // Blue 600
24
+ error: '#DC2626', // Red 600
25
+ success: '#16A34A', // Green 600
26
+ warning: '#D97706', // Amber 600
27
+ info: '#2563EB', // Blue 600
28
+ overlay: 'rgba(0, 0, 0, 0.3)',
29
+ overlayDark: 'rgba(0, 0, 0, 0.6)',
30
+ accent1: '#2563EB', // Blue 600 (camera)
31
+ accent2: '#16A34A', // Green 600 (library)
32
+ accent3: '#D97706', // Amber 600 (files)
33
+ };
34
+
35
+ /**
36
+ * Default dark mode colors.
37
+ * Inverted palette optimized for dark backgrounds.
38
+ */
39
+ export const darkColors: HarkenColors = {
40
+ primary: '#3B82F6', // Blue 500
41
+ primaryPressed: '#2563EB', // Blue 600
42
+ background: '#111827', // Gray 900
43
+ backgroundSecondary: '#1F2937', // Gray 800
44
+ text: '#F9FAFB', // Gray 50
45
+ textSecondary: '#9CA3AF', // Gray 400
46
+ textPlaceholder: '#6B7280', // Gray 500
47
+ textOnPrimary: '#FFFFFF',
48
+ border: '#374151', // Gray 700
49
+ borderFocused: '#3B82F6', // Blue 500
50
+ error: '#EF4444', // Red 500
51
+ success: '#22C55E', // Green 500
52
+ warning: '#F59E0B', // Amber 500
53
+ info: '#3B82F6', // Blue 500
54
+ overlay: 'rgba(0, 0, 0, 0.5)',
55
+ overlayDark: 'rgba(0, 0, 0, 0.8)',
56
+ accent1: '#3B82F6', // Blue 500 (camera)
57
+ accent2: '#22C55E', // Green 500 (library)
58
+ accent3: '#F59E0B', // Amber 500 (files)
59
+ };
60
+
61
+ /**
62
+ * Default typography settings.
63
+ * Uses system font for maximum compatibility.
64
+ */
65
+ export const defaultTypography: HarkenTypography = {
66
+ fontFamily: 'System',
67
+ fontFamilyHeading: undefined, // Falls back to fontFamily
68
+
69
+ titleSize: 20,
70
+ titleLineHeight: 1.3,
71
+ titleWeight: '600',
72
+
73
+ bodySize: 16,
74
+ bodyLineHeight: 1.5,
75
+ bodyWeight: 'normal',
76
+
77
+ labelSize: 14,
78
+ labelWeight: '500',
79
+
80
+ captionSize: 12,
81
+ captionWeight: 'normal',
82
+ };
83
+
84
+ /**
85
+ * Default spacing scale.
86
+ * Based on a 4px grid system.
87
+ */
88
+ export const defaultSpacing: HarkenSpacing = {
89
+ xs: 4,
90
+ sm: 8,
91
+ md: 16,
92
+ lg: 24,
93
+ xl: 32,
94
+ xxl: 48,
95
+ };
96
+
97
+ /**
98
+ * Default border radius values.
99
+ */
100
+ export const defaultRadii: HarkenRadii = {
101
+ none: 0,
102
+ sm: 4,
103
+ md: 8,
104
+ lg: 16,
105
+ xl: 20,
106
+ full: 9999,
107
+ };
108
+
109
+ /**
110
+ * Complete default light theme.
111
+ */
112
+ export const lightTheme: HarkenTheme = {
113
+ colors: lightColors,
114
+ typography: defaultTypography,
115
+ spacing: defaultSpacing,
116
+ radii: defaultRadii,
117
+ };
118
+
119
+ /**
120
+ * Complete default dark theme.
121
+ */
122
+ export const darkTheme: HarkenTheme = {
123
+ colors: darkColors,
124
+ typography: defaultTypography,
125
+ spacing: defaultSpacing,
126
+ radii: defaultRadii,
127
+ };
128
+
129
+ /**
130
+ * Creates a theme by merging overrides with a base theme.
131
+ */
132
+ export function createTheme(
133
+ baseTheme: HarkenTheme,
134
+ overrides?: {
135
+ colors?: Partial<HarkenColors>;
136
+ typography?: Partial<HarkenTypography>;
137
+ spacing?: Partial<HarkenSpacing>;
138
+ radii?: Partial<HarkenRadii>;
139
+ }
140
+ ): HarkenTheme {
141
+ if (!overrides) {
142
+ return baseTheme;
143
+ }
144
+
145
+ return {
146
+ colors: { ...baseTheme.colors, ...overrides.colors },
147
+ typography: { ...baseTheme.typography, ...overrides.typography },
148
+ spacing: { ...baseTheme.spacing, ...overrides.spacing },
149
+ radii: { ...baseTheme.radii, ...overrides.radii },
150
+ };
151
+ }
@@ -0,0 +1,23 @@
1
+ // Theme type exports
2
+ export type {
3
+ HarkenColors,
4
+ HarkenTypography,
5
+ HarkenSpacing,
6
+ HarkenRadii,
7
+ HarkenTheme,
8
+ PartialHarkenTheme,
9
+ TextWeight,
10
+ ThemeMode,
11
+ } from './types';
12
+
13
+ // Default theme exports
14
+ export {
15
+ lightColors,
16
+ darkColors,
17
+ defaultTypography,
18
+ defaultSpacing,
19
+ defaultRadii,
20
+ lightTheme,
21
+ darkTheme,
22
+ createTheme,
23
+ } from './defaults';
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Color tokens for Harken SDK theming.
3
+ * All colors support full override by host apps.
4
+ */
5
+ export interface HarkenColors {
6
+ /** Primary brand color for buttons and accents */
7
+ primary: string;
8
+ /** Darker variant of primary for pressed states */
9
+ primaryPressed: string;
10
+ /** Background color for the feedback form */
11
+ background: string;
12
+ /** Secondary background (cards, inputs) */
13
+ backgroundSecondary: string;
14
+ /** Primary text color */
15
+ text: string;
16
+ /** Secondary/muted text color */
17
+ textSecondary: string;
18
+ /** Placeholder text color */
19
+ textPlaceholder: string;
20
+ /** Text color on primary-colored backgrounds */
21
+ textOnPrimary: string;
22
+ /** Border color for inputs and dividers */
23
+ border: string;
24
+ /** Focused border color */
25
+ borderFocused: string;
26
+ /** Error state color */
27
+ error: string;
28
+ /** Success state color */
29
+ success: string;
30
+ /** Warning state color */
31
+ warning: string;
32
+ /** Informational state color */
33
+ info: string;
34
+ /** Light overlay background (for modals, sheets) */
35
+ overlay: string;
36
+ /** Dark overlay background (for error states, loading) */
37
+ overlayDark: string;
38
+ /** Accent color 1 (e.g., camera option) */
39
+ accent1: string;
40
+ /** Accent color 2 (e.g., photo library option) */
41
+ accent2: string;
42
+ /** Accent color 3 (e.g., files option) */
43
+ accent3: string;
44
+ }
45
+
46
+ /**
47
+ * Typography tokens for Harken SDK theming.
48
+ * Allows complete font customization.
49
+ */
50
+ export interface HarkenTypography {
51
+ /** Font family for body text */
52
+ fontFamily: string;
53
+ /** Font family for headings (defaults to fontFamily if not set) */
54
+ fontFamilyHeading?: string;
55
+
56
+ /** Title text size */
57
+ titleSize: number;
58
+ /** Title line height multiplier */
59
+ titleLineHeight: number;
60
+ /** Title font weight */
61
+ titleWeight: TextWeight;
62
+
63
+ /** Body text size */
64
+ bodySize: number;
65
+ /** Body line height multiplier */
66
+ bodyLineHeight: number;
67
+ /** Body font weight */
68
+ bodyWeight: TextWeight;
69
+
70
+ /** Label text size (for buttons, form labels) */
71
+ labelSize: number;
72
+ /** Label font weight */
73
+ labelWeight: TextWeight;
74
+
75
+ /** Caption/small text size */
76
+ captionSize: number;
77
+ /** Caption font weight */
78
+ captionWeight: TextWeight;
79
+ }
80
+
81
+ /** Font weight values supported across platforms */
82
+ export type TextWeight =
83
+ | 'normal'
84
+ | 'bold'
85
+ | '100'
86
+ | '200'
87
+ | '300'
88
+ | '400'
89
+ | '500'
90
+ | '600'
91
+ | '700'
92
+ | '800'
93
+ | '900';
94
+
95
+ /**
96
+ * Spacing tokens for consistent layout.
97
+ * All values are in logical pixels.
98
+ */
99
+ export interface HarkenSpacing {
100
+ /** Extra small spacing (4px default) */
101
+ xs: number;
102
+ /** Small spacing (8px default) */
103
+ sm: number;
104
+ /** Medium spacing (16px default) */
105
+ md: number;
106
+ /** Large spacing (24px default) */
107
+ lg: number;
108
+ /** Extra large spacing (32px default) */
109
+ xl: number;
110
+ /** 2x extra large spacing (48px default) */
111
+ xxl: number;
112
+ }
113
+
114
+ /**
115
+ * Border radius tokens for rounded corners.
116
+ * All values are in logical pixels.
117
+ */
118
+ export interface HarkenRadii {
119
+ /** No radius */
120
+ none: number;
121
+ /** Small radius for subtle rounding (4px default) */
122
+ sm: number;
123
+ /** Medium radius for inputs and cards (8px default) */
124
+ md: number;
125
+ /** Large radius for modals and sheets (16px default) */
126
+ lg: number;
127
+ /** Extra large radius for bottom sheets (20px default) */
128
+ xl: number;
129
+ /** Full/pill radius */
130
+ full: number;
131
+ }
132
+
133
+ /**
134
+ * Complete theme object combining all token types.
135
+ */
136
+ export interface HarkenTheme {
137
+ colors: HarkenColors;
138
+ typography: HarkenTypography;
139
+ spacing: HarkenSpacing;
140
+ radii: HarkenRadii;
141
+ }
142
+
143
+ /**
144
+ * Partial theme for overriding specific values.
145
+ * Allows deep partial overrides of any theme token.
146
+ */
147
+ export type PartialHarkenTheme = {
148
+ colors?: Partial<HarkenColors>;
149
+ typography?: Partial<HarkenTypography>;
150
+ spacing?: Partial<HarkenSpacing>;
151
+ radii?: Partial<HarkenRadii>;
152
+ };
153
+
154
+ /**
155
+ * Theme mode for automatic light/dark theming.
156
+ */
157
+ export type ThemeMode = 'light' | 'dark' | 'system';