@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.
- package/README.md +67 -0
- package/app.plugin.cjs +135 -0
- package/app.plugin.js +1 -0
- package/dist/api/client.d.ts +67 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +163 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/errors.d.ts +46 -0
- package/dist/api/errors.d.ts.map +1 -0
- package/dist/api/errors.js +72 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +20 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/retry.d.ts +29 -0
- package/dist/api/retry.d.ts.map +1 -0
- package/dist/api/retry.js +74 -0
- package/dist/api/retry.js.map +1 -0
- package/dist/attachments/FeedbackSheet.d.ts +88 -0
- package/dist/attachments/FeedbackSheet.d.ts.map +1 -0
- package/dist/attachments/FeedbackSheet.js +250 -0
- package/dist/attachments/FeedbackSheet.js.map +1 -0
- package/dist/attachments/index.d.ts +20 -0
- package/dist/attachments/index.d.ts.map +1 -0
- package/dist/attachments/index.js +40 -0
- package/dist/attachments/index.js.map +1 -0
- package/dist/components/AttachmentGrid.d.ts +94 -0
- package/dist/components/AttachmentGrid.d.ts.map +1 -0
- package/dist/components/AttachmentGrid.js +132 -0
- package/dist/components/AttachmentGrid.js.map +1 -0
- package/dist/components/AttachmentPicker.d.ts +98 -0
- package/dist/components/AttachmentPicker.d.ts.map +1 -0
- package/dist/components/AttachmentPicker.js +297 -0
- package/dist/components/AttachmentPicker.js.map +1 -0
- package/dist/components/AttachmentPreview.d.ts +78 -0
- package/dist/components/AttachmentPreview.d.ts.map +1 -0
- package/dist/components/AttachmentPreview.js +133 -0
- package/dist/components/AttachmentPreview.js.map +1 -0
- package/dist/components/CategorySelector.d.ts +77 -0
- package/dist/components/CategorySelector.d.ts.map +1 -0
- package/dist/components/CategorySelector.js +117 -0
- package/dist/components/CategorySelector.js.map +1 -0
- package/dist/components/FeedbackForm.d.ts +50 -0
- package/dist/components/FeedbackForm.d.ts.map +1 -0
- package/dist/components/FeedbackForm.js +141 -0
- package/dist/components/FeedbackForm.js.map +1 -0
- package/dist/components/FeedbackSheet.d.ts +75 -0
- package/dist/components/FeedbackSheet.d.ts.map +1 -0
- package/dist/components/FeedbackSheet.js +215 -0
- package/dist/components/FeedbackSheet.js.map +1 -0
- package/dist/components/ThemedButton.d.ts +23 -0
- package/dist/components/ThemedButton.d.ts.map +1 -0
- package/dist/components/ThemedButton.js +77 -0
- package/dist/components/ThemedButton.js.map +1 -0
- package/dist/components/ThemedText.d.ts +16 -0
- package/dist/components/ThemedText.d.ts.map +1 -0
- package/dist/components/ThemedText.js +44 -0
- package/dist/components/ThemedText.js.map +1 -0
- package/dist/components/ThemedTextInput.d.ts +13 -0
- package/dist/components/ThemedTextInput.d.ts.map +1 -0
- package/dist/components/ThemedTextInput.js +76 -0
- package/dist/components/ThemedTextInput.js.map +1 -0
- package/dist/components/UploadStatusOverlay.d.ts +82 -0
- package/dist/components/UploadStatusOverlay.d.ts.map +1 -0
- package/dist/components/UploadStatusOverlay.js +319 -0
- package/dist/components/UploadStatusOverlay.js.map +1 -0
- package/dist/components/index.d.ts +19 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +28 -0
- package/dist/components/index.js.map +1 -0
- package/dist/context/HarkenContext.d.ts +62 -0
- package/dist/context/HarkenContext.d.ts.map +1 -0
- package/dist/context/HarkenContext.js +128 -0
- package/dist/context/HarkenContext.js.map +1 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +7 -0
- package/dist/context/index.js.map +1 -0
- package/dist/domain/index.d.ts +3 -0
- package/dist/domain/index.d.ts.map +1 -0
- package/dist/domain/index.js +7 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/upload-queue.d.ts +116 -0
- package/dist/domain/upload-queue.d.ts.map +1 -0
- package/dist/domain/upload-queue.js +34 -0
- package/dist/domain/upload-queue.js.map +1 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +16 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useAnonymousId.d.ts +28 -0
- package/dist/hooks/useAnonymousId.d.ts.map +1 -0
- package/dist/hooks/useAnonymousId.js +59 -0
- package/dist/hooks/useAnonymousId.js.map +1 -0
- package/dist/hooks/useAttachmentPicker.d.ts +84 -0
- package/dist/hooks/useAttachmentPicker.d.ts.map +1 -0
- package/dist/hooks/useAttachmentPicker.js +181 -0
- package/dist/hooks/useAttachmentPicker.js.map +1 -0
- package/dist/hooks/useAttachmentStatus.d.ts +51 -0
- package/dist/hooks/useAttachmentStatus.d.ts.map +1 -0
- package/dist/hooks/useAttachmentStatus.js +69 -0
- package/dist/hooks/useAttachmentStatus.js.map +1 -0
- package/dist/hooks/useAttachmentUpload.d.ts +101 -0
- package/dist/hooks/useAttachmentUpload.d.ts.map +1 -0
- package/dist/hooks/useAttachmentUpload.js +293 -0
- package/dist/hooks/useAttachmentUpload.js.map +1 -0
- package/dist/hooks/useFeedback.d.ts +55 -0
- package/dist/hooks/useFeedback.d.ts.map +1 -0
- package/dist/hooks/useFeedback.js +96 -0
- package/dist/hooks/useFeedback.js.map +1 -0
- package/dist/hooks/useHarkenContext.d.ts +25 -0
- package/dist/hooks/useHarkenContext.d.ts.map +1 -0
- package/dist/hooks/useHarkenContext.js +35 -0
- package/dist/hooks/useHarkenContext.js.map +1 -0
- package/dist/hooks/useHarkenTheme.d.ts +26 -0
- package/dist/hooks/useHarkenTheme.d.ts.map +1 -0
- package/dist/hooks/useHarkenTheme.js +36 -0
- package/dist/hooks/useHarkenTheme.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +9 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/uploadQueueService.d.ts +193 -0
- package/dist/services/uploadQueueService.d.ts.map +1 -0
- package/dist/services/uploadQueueService.js +623 -0
- package/dist/services/uploadQueueService.js.map +1 -0
- package/dist/services/uploadQueueStorage.d.ts +30 -0
- package/dist/services/uploadQueueStorage.d.ts.map +1 -0
- package/dist/services/uploadQueueStorage.js +77 -0
- package/dist/services/uploadQueueStorage.js.map +1 -0
- package/dist/storage/IdentityStore.d.ts +38 -0
- package/dist/storage/IdentityStore.d.ts.map +1 -0
- package/dist/storage/IdentityStore.js +83 -0
- package/dist/storage/IdentityStore.js.map +1 -0
- package/dist/storage/SecureStoreAdapter.d.ts +28 -0
- package/dist/storage/SecureStoreAdapter.d.ts.map +1 -0
- package/dist/storage/SecureStoreAdapter.js +52 -0
- package/dist/storage/SecureStoreAdapter.js.map +1 -0
- package/dist/storage/defaultStorage.d.ts +20 -0
- package/dist/storage/defaultStorage.d.ts.map +1 -0
- package/dist/storage/defaultStorage.js +131 -0
- package/dist/storage/defaultStorage.js.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +13 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/types.d.ts +32 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +11 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/theme/defaults.d.ts +43 -0
- package/dist/theme/defaults.d.ts.map +1 -0
- package/dist/theme/defaults.js +128 -0
- package/dist/theme/defaults.js.map +1 -0
- package/dist/theme/index.d.ts +3 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +14 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/types.d.ts +136 -0
- package/dist/theme/types.d.ts.map +1 -0
- package/dist/theme/types.js +3 -0
- package/dist/theme/types.js.map +1 -0
- package/dist/types/config.d.ts +100 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +3 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/openapi.d.ts +601 -0
- package/dist/types/openapi.d.ts.map +1 -0
- package/dist/types/openapi.js +7 -0
- package/dist/types/openapi.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/uuid.d.ts +10 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/dist/utils/uuid.js +60 -0
- package/dist/utils/uuid.js.map +1 -0
- package/package.json +124 -0
- package/src/@types/expo-file-system-legacy.d.ts +13 -0
- package/src/api/client.ts +250 -0
- package/src/api/errors.ts +84 -0
- package/src/api/index.ts +15 -0
- package/src/api/retry.ts +99 -0
- package/src/attachments/FeedbackSheet.tsx +400 -0
- package/src/attachments/index.ts +70 -0
- package/src/components/AttachmentGrid.tsx +247 -0
- package/src/components/AttachmentPicker.tsx +391 -0
- package/src/components/AttachmentPreview.tsx +210 -0
- package/src/components/CategorySelector.tsx +174 -0
- package/src/components/FeedbackForm.tsx +216 -0
- package/src/components/FeedbackSheet.tsx +321 -0
- package/src/components/ThemedButton.tsx +127 -0
- package/src/components/ThemedText.tsx +65 -0
- package/src/components/ThemedTextInput.tsx +65 -0
- package/src/components/UploadStatusOverlay.tsx +440 -0
- package/src/components/index.ts +39 -0
- package/src/context/HarkenContext.tsx +129 -0
- package/src/context/index.ts +2 -0
- package/src/domain/index.ts +12 -0
- package/src/domain/upload-queue.ts +131 -0
- package/src/hooks/index.ts +10 -0
- package/src/hooks/useAnonymousId.ts +68 -0
- package/src/hooks/useAttachmentPicker.ts +243 -0
- package/src/hooks/useAttachmentStatus.ts +86 -0
- package/src/hooks/useAttachmentUpload.ts +370 -0
- package/src/hooks/useFeedback.ts +139 -0
- package/src/hooks/useHarkenContext.ts +35 -0
- package/src/hooks/useHarkenTheme.ts +36 -0
- package/src/index.ts +168 -0
- package/src/services/index.ts +11 -0
- package/src/services/uploadQueueService.ts +727 -0
- package/src/services/uploadQueueStorage.ts +78 -0
- package/src/storage/IdentityStore.ts +89 -0
- package/src/storage/SecureStoreAdapter.ts +59 -0
- package/src/storage/defaultStorage.ts +109 -0
- package/src/storage/index.ts +5 -0
- package/src/storage/types.ts +34 -0
- package/src/theme/defaults.ts +151 -0
- package/src/theme/index.ts +23 -0
- package/src/theme/types.ts +157 -0
- package/src/types/config.ts +112 -0
- package/src/types/index.ts +10 -0
- package/src/types/openapi.ts +601 -0
- package/src/utils/index.ts +1 -0
- 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';
|