@harkenapp/sdk-react-native 0.0.1-alpha.1 → 0.0.1-alpha.2
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 +44 -7
- package/app.plugin.cjs +12 -17
- package/dist/__mocks__/async-storage.d.ts +16 -0
- package/dist/__mocks__/async-storage.d.ts.map +1 -0
- package/dist/__mocks__/async-storage.js +39 -0
- package/dist/__mocks__/async-storage.js.map +1 -0
- package/dist/__mocks__/expo-document-picker.d.ts +26 -0
- package/dist/__mocks__/expo-document-picker.d.ts.map +1 -0
- package/dist/__mocks__/expo-document-picker.js +25 -0
- package/dist/__mocks__/expo-document-picker.js.map +1 -0
- package/dist/__mocks__/expo-file-system.d.ts +42 -0
- package/dist/__mocks__/expo-file-system.d.ts.map +1 -0
- package/dist/__mocks__/expo-file-system.js +37 -0
- package/dist/__mocks__/expo-file-system.js.map +1 -0
- package/dist/__mocks__/expo-image-picker.d.ts +30 -0
- package/dist/__mocks__/expo-image-picker.d.ts.map +1 -0
- package/dist/__mocks__/expo-image-picker.js +30 -0
- package/dist/__mocks__/expo-image-picker.js.map +1 -0
- package/dist/__mocks__/expo-secure-store.d.ts +15 -0
- package/dist/__mocks__/expo-secure-store.d.ts.map +1 -0
- package/dist/__mocks__/expo-secure-store.js +30 -0
- package/dist/__mocks__/expo-secure-store.js.map +1 -0
- package/dist/__mocks__/react-native.d.ts +73 -0
- package/dist/__mocks__/react-native.d.ts.map +1 -0
- package/dist/__mocks__/react-native.js +45 -0
- package/dist/__mocks__/react-native.js.map +1 -0
- package/dist/api/client.d.ts +8 -8
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +17 -19
- package/dist/api/client.js.map +1 -1
- package/dist/api/client.test.d.ts +2 -0
- package/dist/api/client.test.d.ts.map +1 -0
- package/dist/api/client.test.js +417 -0
- package/dist/api/client.test.js.map +1 -0
- package/dist/api/errors.d.ts +3 -3
- package/dist/api/errors.d.ts.map +1 -1
- package/dist/api/errors.js +3 -3
- package/dist/api/errors.js.map +1 -1
- package/dist/api/errors.test.d.ts +2 -0
- package/dist/api/errors.test.d.ts.map +1 -0
- package/dist/api/errors.test.js +155 -0
- package/dist/api/errors.test.js.map +1 -0
- package/dist/api/index.d.ts +6 -6
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/retry.d.ts +1 -1
- package/dist/api/retry.d.ts.map +1 -1
- package/dist/api/retry.js.map +1 -1
- package/dist/api/retry.test.d.ts +2 -0
- package/dist/api/retry.test.d.ts.map +1 -0
- package/dist/api/retry.test.js +193 -0
- package/dist/api/retry.test.js.map +1 -0
- package/dist/attachments/FeedbackSheet.d.ts +36 -13
- package/dist/attachments/FeedbackSheet.d.ts.map +1 -1
- package/dist/attachments/FeedbackSheet.js +50 -30
- package/dist/attachments/FeedbackSheet.js.map +1 -1
- package/dist/attachments/index.d.ts +2 -2
- package/dist/components/AttachmentGrid.d.ts +12 -4
- package/dist/components/AttachmentGrid.d.ts.map +1 -1
- package/dist/components/AttachmentGrid.js +44 -34
- package/dist/components/AttachmentGrid.js.map +1 -1
- package/dist/components/AttachmentPicker.d.ts +3 -3
- package/dist/components/AttachmentPicker.d.ts.map +1 -1
- package/dist/components/AttachmentPicker.js +34 -36
- package/dist/components/AttachmentPicker.js.map +1 -1
- package/dist/components/AttachmentPreview.d.ts +10 -4
- package/dist/components/AttachmentPreview.d.ts.map +1 -1
- package/dist/components/AttachmentPreview.js +48 -34
- package/dist/components/AttachmentPreview.js.map +1 -1
- package/dist/components/CategorySelector.d.ts +3 -3
- package/dist/components/CategorySelector.d.ts.map +1 -1
- package/dist/components/CategorySelector.js +21 -27
- package/dist/components/CategorySelector.js.map +1 -1
- package/dist/components/FeedbackForm.d.ts +3 -3
- package/dist/components/FeedbackForm.d.ts.map +1 -1
- package/dist/components/FeedbackForm.js +7 -8
- package/dist/components/FeedbackForm.js.map +1 -1
- package/dist/components/FeedbackSheet.d.ts +34 -11
- package/dist/components/FeedbackSheet.d.ts.map +1 -1
- package/dist/components/FeedbackSheet.js +46 -28
- package/dist/components/FeedbackSheet.js.map +1 -1
- package/dist/components/ThemedButton.d.ts +16 -5
- package/dist/components/ThemedButton.d.ts.map +1 -1
- package/dist/components/ThemedButton.js +38 -29
- package/dist/components/ThemedButton.js.map +1 -1
- package/dist/components/ThemedText.d.ts +3 -3
- package/dist/components/ThemedText.d.ts.map +1 -1
- package/dist/components/ThemedText.js +1 -1
- package/dist/components/ThemedText.js.map +1 -1
- package/dist/components/ThemedTextInput.d.ts +11 -2
- package/dist/components/ThemedTextInput.d.ts.map +1 -1
- package/dist/components/ThemedTextInput.js +19 -9
- package/dist/components/ThemedTextInput.js.map +1 -1
- package/dist/components/UploadStatusOverlay.d.ts +11 -3
- package/dist/components/UploadStatusOverlay.d.ts.map +1 -1
- package/dist/components/UploadStatusOverlay.js +59 -76
- package/dist/components/UploadStatusOverlay.js.map +1 -1
- package/dist/components/index.d.ts +18 -18
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/context/HarkenContext.d.ts +20 -15
- package/dist/context/HarkenContext.d.ts.map +1 -1
- package/dist/context/HarkenContext.js +20 -17
- package/dist/context/HarkenContext.js.map +1 -1
- package/dist/context/index.d.ts +2 -2
- package/dist/domain/index.d.ts +2 -2
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js.map +1 -1
- package/dist/hooks/index.d.ts +5 -5
- package/dist/hooks/useAnonymousId.js +1 -1
- package/dist/hooks/useAnonymousId.test.d.ts +2 -0
- package/dist/hooks/useAnonymousId.test.d.ts.map +1 -0
- package/dist/hooks/useAnonymousId.test.js +154 -0
- package/dist/hooks/useAnonymousId.test.js.map +1 -0
- package/dist/hooks/useAttachmentPicker.d.ts +3 -3
- package/dist/hooks/useAttachmentPicker.js +7 -7
- package/dist/hooks/useAttachmentStatus.d.ts +1 -1
- package/dist/hooks/useAttachmentStatus.d.ts.map +1 -1
- package/dist/hooks/useAttachmentStatus.js.map +1 -1
- package/dist/hooks/useAttachmentUpload.d.ts +2 -2
- package/dist/hooks/useAttachmentUpload.d.ts.map +1 -1
- package/dist/hooks/useAttachmentUpload.js +5 -5
- package/dist/hooks/useAttachmentUpload.js.map +1 -1
- package/dist/hooks/useAttachmentUpload.test.d.ts +2 -0
- package/dist/hooks/useAttachmentUpload.test.d.ts.map +1 -0
- package/dist/hooks/useAttachmentUpload.test.js +542 -0
- package/dist/hooks/useAttachmentUpload.test.js.map +1 -0
- package/dist/hooks/useFeedback.d.ts +4 -4
- package/dist/hooks/useFeedback.d.ts.map +1 -1
- package/dist/hooks/useFeedback.js +3 -5
- package/dist/hooks/useFeedback.js.map +1 -1
- package/dist/hooks/useFeedback.test.d.ts +2 -0
- package/dist/hooks/useFeedback.test.d.ts.map +1 -0
- package/dist/hooks/useFeedback.test.js +299 -0
- package/dist/hooks/useFeedback.test.js.map +1 -0
- package/dist/hooks/useHarkenContext.d.ts +1 -1
- package/dist/hooks/useHarkenContext.js +1 -1
- package/dist/hooks/useHarkenTheme.d.ts +27 -3
- package/dist/hooks/useHarkenTheme.d.ts.map +1 -1
- package/dist/hooks/useHarkenTheme.js +26 -2
- package/dist/hooks/useHarkenTheme.js.map +1 -1
- package/dist/index.d.ts +28 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/index.d.ts +3 -3
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/uploadQueueService.d.ts +2 -2
- package/dist/services/uploadQueueService.d.ts.map +1 -1
- package/dist/services/uploadQueueService.js +16 -17
- package/dist/services/uploadQueueService.js.map +1 -1
- package/dist/services/uploadQueueService.test.d.ts +2 -0
- package/dist/services/uploadQueueService.test.d.ts.map +1 -0
- package/dist/services/uploadQueueService.test.js +426 -0
- package/dist/services/uploadQueueService.test.js.map +1 -0
- package/dist/services/uploadQueueStorage.d.ts +1 -1
- package/dist/services/uploadQueueStorage.d.ts.map +1 -1
- package/dist/services/uploadQueueStorage.js +4 -4
- package/dist/services/uploadQueueStorage.js.map +1 -1
- package/dist/services/uploadQueueStorage.test.d.ts +2 -0
- package/dist/services/uploadQueueStorage.test.d.ts.map +1 -0
- package/dist/services/uploadQueueStorage.test.js +200 -0
- package/dist/services/uploadQueueStorage.test.js.map +1 -0
- package/dist/storage/IdentityStore.d.ts +1 -1
- package/dist/storage/IdentityStore.d.ts.map +1 -1
- package/dist/storage/IdentityStore.js.map +1 -1
- package/dist/storage/IdentityStore.test.d.ts +2 -0
- package/dist/storage/IdentityStore.test.d.ts.map +1 -0
- package/dist/storage/IdentityStore.test.js +176 -0
- package/dist/storage/IdentityStore.test.js.map +1 -0
- package/dist/storage/SecureStoreAdapter.d.ts +1 -1
- package/dist/storage/SecureStoreAdapter.test.d.ts +2 -0
- package/dist/storage/SecureStoreAdapter.test.d.ts.map +1 -0
- package/dist/storage/SecureStoreAdapter.test.js +114 -0
- package/dist/storage/SecureStoreAdapter.test.js.map +1 -0
- package/dist/storage/defaultStorage.d.ts +1 -1
- package/dist/storage/defaultStorage.js +4 -4
- package/dist/storage/defaultStorage.test.d.ts +2 -0
- package/dist/storage/defaultStorage.test.d.ts.map +1 -0
- package/dist/storage/defaultStorage.test.js +159 -0
- package/dist/storage/defaultStorage.test.js.map +1 -0
- package/dist/storage/index.d.ts +5 -5
- package/dist/storage/types.js +1 -1
- package/dist/theme/defaults.d.ts +14 -3
- package/dist/theme/defaults.d.ts.map +1 -1
- package/dist/theme/defaults.js +58 -43
- package/dist/theme/defaults.js.map +1 -1
- package/dist/theme/index.d.ts +3 -2
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +4 -1
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/resolver.d.ts +16 -0
- package/dist/theme/resolver.d.ts.map +1 -0
- package/dist/theme/resolver.js +375 -0
- package/dist/theme/resolver.js.map +1 -0
- package/dist/theme/resolver.test.d.ts +2 -0
- package/dist/theme/resolver.test.d.ts.map +1 -0
- package/dist/theme/resolver.test.js +344 -0
- package/dist/theme/resolver.test.js.map +1 -0
- package/dist/theme/types.d.ts +378 -5
- package/dist/theme/types.d.ts.map +1 -1
- package/dist/types/config.d.ts +4 -4
- package/dist/types/index.d.ts +2 -2
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/uuid.d.ts.map +1 -1
- package/dist/utils/uuid.js +4 -5
- package/dist/utils/uuid.js.map +1 -1
- package/dist/utils/uuid.test.d.ts +2 -0
- package/dist/utils/uuid.test.d.ts.map +1 -0
- package/dist/utils/uuid.test.js +78 -0
- package/dist/utils/uuid.test.js.map +1 -0
- package/package.json +21 -13
- package/src/@types/expo-file-system-legacy.d.ts +3 -3
- package/src/__mocks__/async-storage.ts +46 -0
- package/src/__mocks__/expo-document-picker.ts +41 -0
- package/src/__mocks__/expo-file-system.ts +62 -0
- package/src/__mocks__/expo-image-picker.ts +48 -0
- package/src/__mocks__/expo-secure-store.ts +29 -0
- package/src/__mocks__/react-native.ts +46 -0
- package/src/api/client.test.ts +515 -0
- package/src/api/client.ts +45 -64
- package/src/api/errors.test.ts +193 -0
- package/src/api/errors.ts +7 -11
- package/src/api/index.ts +6 -10
- package/src/api/retry.test.ts +251 -0
- package/src/api/retry.ts +3 -6
- package/src/attachments/FeedbackSheet.tsx +100 -80
- package/src/attachments/index.ts +2 -2
- package/src/components/AttachmentGrid.tsx +54 -45
- package/src/components/AttachmentPicker.tsx +43 -54
- package/src/components/AttachmentPreview.tsx +51 -47
- package/src/components/CategorySelector.tsx +29 -35
- package/src/components/FeedbackForm.tsx +23 -35
- package/src/components/FeedbackSheet.tsx +89 -68
- package/src/components/ThemedButton.tsx +49 -47
- package/src/components/ThemedText.tsx +7 -10
- package/src/components/ThemedTextInput.tsx +23 -13
- package/src/components/UploadStatusOverlay.tsx +66 -89
- package/src/components/index.ts +18 -21
- package/src/context/HarkenContext.tsx +29 -28
- package/src/context/index.ts +2 -2
- package/src/domain/index.ts +2 -5
- package/src/domain/upload-queue.ts +5 -5
- package/src/hooks/index.ts +5 -5
- package/src/hooks/useAnonymousId.test.ts +189 -0
- package/src/hooks/useAnonymousId.ts +3 -3
- package/src/hooks/useAttachmentPicker.ts +12 -12
- package/src/hooks/useAttachmentStatus.ts +12 -16
- package/src/hooks/useAttachmentUpload.test.ts +632 -0
- package/src/hooks/useAttachmentUpload.ts +45 -54
- package/src/hooks/useFeedback.test.ts +376 -0
- package/src/hooks/useFeedback.ts +12 -14
- package/src/hooks/useHarkenContext.ts +4 -4
- package/src/hooks/useHarkenTheme.ts +30 -6
- package/src/index.ts +28 -52
- package/src/services/index.ts +3 -9
- package/src/services/uploadQueueService.test.ts +489 -0
- package/src/services/uploadQueueService.ts +40 -56
- package/src/services/uploadQueueStorage.test.ts +243 -0
- package/src/services/uploadQueueStorage.ts +7 -9
- package/src/storage/IdentityStore.test.ts +173 -0
- package/src/storage/IdentityStore.ts +4 -5
- package/src/storage/SecureStoreAdapter.test.ts +147 -0
- package/src/storage/SecureStoreAdapter.ts +1 -1
- package/src/storage/defaultStorage.test.ts +159 -0
- package/src/storage/defaultStorage.ts +6 -6
- package/src/storage/index.ts +5 -5
- package/src/storage/types.ts +1 -1
- package/src/theme/defaults.ts +75 -46
- package/src/theme/index.ts +15 -2
- package/src/theme/resolver.test.ts +411 -0
- package/src/theme/resolver.ts +446 -0
- package/src/theme/types.ts +453 -15
- package/src/types/config.ts +4 -4
- package/src/types/index.ts +2 -2
- package/src/utils/index.ts +1 -1
- package/src/utils/uuid.test.ts +85 -0
- package/src/utils/uuid.ts +4 -7
package/src/api/client.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import type { components } from
|
|
2
|
-
import { HarkenApiError, HarkenNetworkError } from
|
|
3
|
-
import { withRetry } from
|
|
4
|
-
import type { RetryConfig } from
|
|
1
|
+
import type { components } from "../types/index.js";
|
|
2
|
+
import { HarkenApiError, HarkenNetworkError } from "./errors";
|
|
3
|
+
import { withRetry } from "./retry";
|
|
4
|
+
import type { RetryConfig } from "./retry";
|
|
5
5
|
|
|
6
6
|
// Re-export types from contracts for convenience
|
|
7
|
-
type FeedbackSubmission = components[
|
|
8
|
-
type FeedbackSubmissionResponse = components[
|
|
9
|
-
type ErrorResponse = components[
|
|
7
|
+
type FeedbackSubmission = components["schemas"]["FeedbackSubmission"];
|
|
8
|
+
type FeedbackSubmissionResponse = components["schemas"]["FeedbackSubmissionResponse"];
|
|
9
|
+
type ErrorResponse = components["schemas"]["ErrorResponse"];
|
|
10
10
|
|
|
11
11
|
// Attachment types
|
|
12
|
-
type AttachmentPresignRequest = components[
|
|
13
|
-
type AttachmentPresignResponse = components[
|
|
14
|
-
type AttachmentConfirmRequest = components[
|
|
15
|
-
type AttachmentStatusResponse = components[
|
|
12
|
+
type AttachmentPresignRequest = components["schemas"]["AttachmentPresignRequest"];
|
|
13
|
+
type AttachmentPresignResponse = components["schemas"]["AttachmentPresignResponse"];
|
|
14
|
+
type AttachmentConfirmRequest = components["schemas"]["AttachmentConfirmRequest"];
|
|
15
|
+
type AttachmentStatusResponse = components["schemas"]["AttachmentStatusResponse"];
|
|
16
16
|
|
|
17
|
-
const DEFAULT_API_BASE_URL =
|
|
17
|
+
const DEFAULT_API_BASE_URL = "https://api.harken.app";
|
|
18
18
|
|
|
19
19
|
export interface HarkenClientConfig {
|
|
20
20
|
/** Publishable API key */
|
|
@@ -34,9 +34,9 @@ export interface HarkenClientConfig {
|
|
|
34
34
|
*/
|
|
35
35
|
export class HarkenClient {
|
|
36
36
|
private readonly config: Required<
|
|
37
|
-
Pick<HarkenClientConfig,
|
|
37
|
+
Pick<HarkenClientConfig, "publishableKey" | "baseUrl" | "timeout">
|
|
38
38
|
> &
|
|
39
|
-
Pick<HarkenClientConfig,
|
|
39
|
+
Pick<HarkenClientConfig, "userToken" | "retry">;
|
|
40
40
|
|
|
41
41
|
constructor(config: HarkenClientConfig) {
|
|
42
42
|
this.config = {
|
|
@@ -51,14 +51,13 @@ export class HarkenClient {
|
|
|
51
51
|
/**
|
|
52
52
|
* Submit feedback to the API.
|
|
53
53
|
*/
|
|
54
|
-
async submitFeedback(
|
|
55
|
-
submission: FeedbackSubmission
|
|
56
|
-
): Promise<FeedbackSubmissionResponse> {
|
|
54
|
+
async submitFeedback(submission: FeedbackSubmission): Promise<FeedbackSubmissionResponse> {
|
|
57
55
|
return withRetry(
|
|
58
|
-
() =>
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
() =>
|
|
57
|
+
this.request<FeedbackSubmissionResponse>("/v1/feedback", {
|
|
58
|
+
method: "POST",
|
|
59
|
+
body: JSON.stringify(submission),
|
|
60
|
+
}),
|
|
62
61
|
this.config.retry
|
|
63
62
|
);
|
|
64
63
|
}
|
|
@@ -71,13 +70,10 @@ export class HarkenClient {
|
|
|
71
70
|
): Promise<AttachmentPresignResponse> {
|
|
72
71
|
return withRetry(
|
|
73
72
|
() =>
|
|
74
|
-
this.request<AttachmentPresignResponse>(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
body: JSON.stringify(request),
|
|
79
|
-
}
|
|
80
|
-
),
|
|
73
|
+
this.request<AttachmentPresignResponse>("/v1/feedback/attachments/presign", {
|
|
74
|
+
method: "POST",
|
|
75
|
+
body: JSON.stringify(request),
|
|
76
|
+
}),
|
|
81
77
|
this.config.retry
|
|
82
78
|
);
|
|
83
79
|
}
|
|
@@ -91,13 +87,10 @@ export class HarkenClient {
|
|
|
91
87
|
): Promise<AttachmentStatusResponse> {
|
|
92
88
|
return withRetry(
|
|
93
89
|
() =>
|
|
94
|
-
this.request<AttachmentStatusResponse>(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
body: request ? JSON.stringify(request) : undefined,
|
|
99
|
-
}
|
|
100
|
-
),
|
|
90
|
+
this.request<AttachmentStatusResponse>(`/v1/feedback/attachments/${attachmentId}/confirm`, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
body: request ? JSON.stringify(request) : undefined,
|
|
93
|
+
}),
|
|
101
94
|
this.config.retry
|
|
102
95
|
);
|
|
103
96
|
}
|
|
@@ -110,43 +103,33 @@ export class HarkenClient {
|
|
|
110
103
|
attachmentId: string,
|
|
111
104
|
error?: string
|
|
112
105
|
): Promise<AttachmentStatusResponse> {
|
|
113
|
-
return this.request<AttachmentStatusResponse>(
|
|
114
|
-
|
|
115
|
-
{
|
|
116
|
-
|
|
117
|
-
body: error ? JSON.stringify({ error }) : undefined,
|
|
118
|
-
}
|
|
119
|
-
);
|
|
106
|
+
return this.request<AttachmentStatusResponse>(`/v1/feedback/attachments/${attachmentId}/fail`, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
body: error ? JSON.stringify({ error }) : undefined,
|
|
109
|
+
});
|
|
120
110
|
}
|
|
121
111
|
|
|
122
112
|
/**
|
|
123
113
|
* Get attachment status and download URLs.
|
|
124
114
|
*/
|
|
125
|
-
async getAttachmentStatus(
|
|
126
|
-
attachmentId
|
|
127
|
-
): Promise<AttachmentStatusResponse> {
|
|
128
|
-
return this.request<AttachmentStatusResponse>(
|
|
129
|
-
`/v1/feedback/attachments/${attachmentId}`
|
|
130
|
-
);
|
|
115
|
+
async getAttachmentStatus(attachmentId: string): Promise<AttachmentStatusResponse> {
|
|
116
|
+
return this.request<AttachmentStatusResponse>(`/v1/feedback/attachments/${attachmentId}`);
|
|
131
117
|
}
|
|
132
118
|
|
|
133
119
|
/**
|
|
134
120
|
* Make an authenticated request to the API.
|
|
135
121
|
*/
|
|
136
|
-
private async request<T>(
|
|
137
|
-
path: string,
|
|
138
|
-
options: RequestInit = {}
|
|
139
|
-
): Promise<T> {
|
|
122
|
+
private async request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
|
140
123
|
const url = `${this.config.baseUrl}${path}`;
|
|
141
124
|
|
|
142
125
|
const headers: Record<string, string> = {
|
|
143
|
-
|
|
144
|
-
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
"X-Publishable-Key": this.config.publishableKey,
|
|
145
128
|
...(options.headers as Record<string, string>),
|
|
146
129
|
};
|
|
147
130
|
|
|
148
131
|
if (this.config.userToken) {
|
|
149
|
-
headers[
|
|
132
|
+
headers["X-User-Token"] = this.config.userToken;
|
|
150
133
|
}
|
|
151
134
|
|
|
152
135
|
// Create abort controller for timeout
|
|
@@ -165,9 +148,7 @@ export class HarkenClient {
|
|
|
165
148
|
if (!response.ok) {
|
|
166
149
|
const errorBody = await this.parseErrorResponse(response);
|
|
167
150
|
// Only parse Retry-After for 429 responses
|
|
168
|
-
const retryAfter = response.status === 429
|
|
169
|
-
? this.parseRetryAfter(response)
|
|
170
|
-
: undefined;
|
|
151
|
+
const retryAfter = response.status === 429 ? this.parseRetryAfter(response) : undefined;
|
|
171
152
|
throw new HarkenApiError(response.status, errorBody, { retryAfter });
|
|
172
153
|
}
|
|
173
154
|
|
|
@@ -181,18 +162,18 @@ export class HarkenClient {
|
|
|
181
162
|
}
|
|
182
163
|
|
|
183
164
|
// Handle abort (timeout)
|
|
184
|
-
if (error instanceof Error && error.name ===
|
|
185
|
-
throw new HarkenNetworkError(
|
|
165
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
166
|
+
throw new HarkenNetworkError("Request timed out", error);
|
|
186
167
|
}
|
|
187
168
|
|
|
188
169
|
// Handle other fetch errors (network issues)
|
|
189
170
|
if (error instanceof TypeError) {
|
|
190
|
-
throw new HarkenNetworkError(
|
|
171
|
+
throw new HarkenNetworkError("Network request failed", error);
|
|
191
172
|
}
|
|
192
173
|
|
|
193
174
|
// Unknown error
|
|
194
175
|
throw new HarkenNetworkError(
|
|
195
|
-
error instanceof Error ? error.message :
|
|
176
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
196
177
|
error instanceof Error ? error : undefined
|
|
197
178
|
);
|
|
198
179
|
}
|
|
@@ -209,7 +190,7 @@ export class HarkenClient {
|
|
|
209
190
|
return {
|
|
210
191
|
error: {
|
|
211
192
|
code: `http_${response.status}`,
|
|
212
|
-
message: response.statusText ||
|
|
193
|
+
message: response.statusText || "Request failed",
|
|
213
194
|
},
|
|
214
195
|
};
|
|
215
196
|
}
|
|
@@ -220,7 +201,7 @@ export class HarkenClient {
|
|
|
220
201
|
* Supports both delta-seconds and HTTP-date formats.
|
|
221
202
|
*/
|
|
222
203
|
private parseRetryAfter(response: Response): number | undefined {
|
|
223
|
-
const retryAfter = response.headers.get(
|
|
204
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
224
205
|
if (!retryAfter) {
|
|
225
206
|
return undefined;
|
|
226
207
|
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { HarkenError, HarkenApiError, HarkenNetworkError } from "./errors";
|
|
3
|
+
|
|
4
|
+
describe("HarkenError", () => {
|
|
5
|
+
it("creates error with message", () => {
|
|
6
|
+
const error = new HarkenError("Something went wrong");
|
|
7
|
+
expect(error.message).toBe("Something went wrong");
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe("HarkenApiError", () => {
|
|
12
|
+
it("creates error with status, code, and message", () => {
|
|
13
|
+
const error = new HarkenApiError(400, {
|
|
14
|
+
error: {
|
|
15
|
+
code: "validation_error",
|
|
16
|
+
message: "Invalid input",
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
expect(error.status).toBe(400);
|
|
21
|
+
expect(error.code).toBe("validation_error");
|
|
22
|
+
expect(error.message).toBe("Invalid input");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("includes error details when provided", () => {
|
|
26
|
+
const error = new HarkenApiError(400, {
|
|
27
|
+
error: {
|
|
28
|
+
code: "validation_error",
|
|
29
|
+
message: "Invalid input",
|
|
30
|
+
details: [
|
|
31
|
+
{ field: "message", message: "Required field" },
|
|
32
|
+
{ field: "category", message: "Invalid value" },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(error.details).toHaveLength(2);
|
|
38
|
+
expect(error.details?.[0]).toEqual({
|
|
39
|
+
field: "message",
|
|
40
|
+
message: "Required field",
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("stores retryAfter when provided", () => {
|
|
45
|
+
const error = new HarkenApiError(
|
|
46
|
+
429,
|
|
47
|
+
{ error: { code: "rate_limited", message: "Too many requests" } },
|
|
48
|
+
{ retryAfter: 60 }
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(error.retryAfter).toBe(60);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("isValidationError", () => {
|
|
55
|
+
it("returns true for 400 status", () => {
|
|
56
|
+
const error = new HarkenApiError(400, {
|
|
57
|
+
error: { code: "validation_error", message: "Invalid" },
|
|
58
|
+
});
|
|
59
|
+
expect(error.isValidationError).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("returns false for other statuses", () => {
|
|
63
|
+
const error = new HarkenApiError(401, {
|
|
64
|
+
error: { code: "unauthorized", message: "Unauthorized" },
|
|
65
|
+
});
|
|
66
|
+
expect(error.isValidationError).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("isUnauthorized", () => {
|
|
71
|
+
it("returns true for 401 status", () => {
|
|
72
|
+
const error = new HarkenApiError(401, {
|
|
73
|
+
error: { code: "unauthorized", message: "Unauthorized" },
|
|
74
|
+
});
|
|
75
|
+
expect(error.isUnauthorized).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("returns false for other statuses", () => {
|
|
79
|
+
const error = new HarkenApiError(403, {
|
|
80
|
+
error: { code: "forbidden", message: "Forbidden" },
|
|
81
|
+
});
|
|
82
|
+
expect(error.isUnauthorized).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("isRateLimited", () => {
|
|
87
|
+
it("returns true for 429 status", () => {
|
|
88
|
+
const error = new HarkenApiError(429, {
|
|
89
|
+
error: { code: "rate_limited", message: "Too many requests" },
|
|
90
|
+
});
|
|
91
|
+
expect(error.isRateLimited).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns false for other statuses", () => {
|
|
95
|
+
const error = new HarkenApiError(400, {
|
|
96
|
+
error: { code: "validation_error", message: "Invalid" },
|
|
97
|
+
});
|
|
98
|
+
expect(error.isRateLimited).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("isServerError", () => {
|
|
103
|
+
it("returns true for 5xx statuses", () => {
|
|
104
|
+
expect(
|
|
105
|
+
new HarkenApiError(500, {
|
|
106
|
+
error: { code: "internal_error", message: "Server error" },
|
|
107
|
+
}).isServerError
|
|
108
|
+
).toBe(true);
|
|
109
|
+
|
|
110
|
+
expect(
|
|
111
|
+
new HarkenApiError(502, {
|
|
112
|
+
error: { code: "bad_gateway", message: "Bad gateway" },
|
|
113
|
+
}).isServerError
|
|
114
|
+
).toBe(true);
|
|
115
|
+
|
|
116
|
+
expect(
|
|
117
|
+
new HarkenApiError(503, {
|
|
118
|
+
error: { code: "service_unavailable", message: "Service unavailable" },
|
|
119
|
+
}).isServerError
|
|
120
|
+
).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("returns false for 4xx statuses", () => {
|
|
124
|
+
expect(
|
|
125
|
+
new HarkenApiError(400, {
|
|
126
|
+
error: { code: "validation_error", message: "Invalid" },
|
|
127
|
+
}).isServerError
|
|
128
|
+
).toBe(false);
|
|
129
|
+
|
|
130
|
+
expect(
|
|
131
|
+
new HarkenApiError(429, {
|
|
132
|
+
error: { code: "rate_limited", message: "Rate limited" },
|
|
133
|
+
}).isServerError
|
|
134
|
+
).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("isRetryable", () => {
|
|
139
|
+
it("returns true for 429 (rate limited)", () => {
|
|
140
|
+
const error = new HarkenApiError(429, {
|
|
141
|
+
error: { code: "rate_limited", message: "Too many requests" },
|
|
142
|
+
});
|
|
143
|
+
expect(error.isRetryable).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("returns true for 5xx (server errors)", () => {
|
|
147
|
+
const error500 = new HarkenApiError(500, {
|
|
148
|
+
error: { code: "internal_error", message: "Server error" },
|
|
149
|
+
});
|
|
150
|
+
const error503 = new HarkenApiError(503, {
|
|
151
|
+
error: { code: "service_unavailable", message: "Unavailable" },
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
expect(error500.isRetryable).toBe(true);
|
|
155
|
+
expect(error503.isRetryable).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("returns false for 4xx (client errors except 429)", () => {
|
|
159
|
+
const error400 = new HarkenApiError(400, {
|
|
160
|
+
error: { code: "validation_error", message: "Invalid" },
|
|
161
|
+
});
|
|
162
|
+
const error401 = new HarkenApiError(401, {
|
|
163
|
+
error: { code: "unauthorized", message: "Unauthorized" },
|
|
164
|
+
});
|
|
165
|
+
const error404 = new HarkenApiError(404, {
|
|
166
|
+
error: { code: "not_found", message: "Not found" },
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(error400.isRetryable).toBe(false);
|
|
170
|
+
expect(error401.isRetryable).toBe(false);
|
|
171
|
+
expect(error404.isRetryable).toBe(false);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe("HarkenNetworkError", () => {
|
|
177
|
+
it("creates error with message", () => {
|
|
178
|
+
const error = new HarkenNetworkError("Network request failed");
|
|
179
|
+
expect(error.message).toBe("Network request failed");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("stores cause when provided", () => {
|
|
183
|
+
const cause = new TypeError("Failed to fetch");
|
|
184
|
+
const error = new HarkenNetworkError("Network request failed", cause);
|
|
185
|
+
|
|
186
|
+
expect(error.cause).toBe(cause);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("isRetryable always returns true", () => {
|
|
190
|
+
const error = new HarkenNetworkError("Network failed");
|
|
191
|
+
expect(error.isRetryable).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
});
|
package/src/api/errors.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { components } from
|
|
1
|
+
import type { components } from "../types/index.js";
|
|
2
2
|
|
|
3
|
-
type ErrorResponse = components[
|
|
4
|
-
type ErrorDetail = components[
|
|
3
|
+
type ErrorResponse = components["schemas"]["ErrorResponse"];
|
|
4
|
+
type ErrorDetail = components["schemas"]["ErrorDetail"];
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Base error class for Harken API errors.
|
|
@@ -9,7 +9,7 @@ type ErrorDetail = components['schemas']['ErrorDetail'];
|
|
|
9
9
|
export class HarkenError extends Error {
|
|
10
10
|
constructor(message: string) {
|
|
11
11
|
super(message);
|
|
12
|
-
this.name =
|
|
12
|
+
this.name = "HarkenError";
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -26,13 +26,9 @@ export class HarkenApiError extends HarkenError {
|
|
|
26
26
|
/** Retry-After value in seconds (from 429 responses) */
|
|
27
27
|
readonly retryAfter?: number;
|
|
28
28
|
|
|
29
|
-
constructor(
|
|
30
|
-
status: number,
|
|
31
|
-
response: ErrorResponse,
|
|
32
|
-
options?: { retryAfter?: number }
|
|
33
|
-
) {
|
|
29
|
+
constructor(status: number, response: ErrorResponse, options?: { retryAfter?: number }) {
|
|
34
30
|
super(response.error.message);
|
|
35
|
-
this.name =
|
|
31
|
+
this.name = "HarkenApiError";
|
|
36
32
|
this.status = status;
|
|
37
33
|
this.code = response.error.code;
|
|
38
34
|
this.details = response.error.details;
|
|
@@ -73,7 +69,7 @@ export class HarkenNetworkError extends HarkenError {
|
|
|
73
69
|
|
|
74
70
|
constructor(message: string, cause?: Error) {
|
|
75
71
|
super(message);
|
|
76
|
-
this.name =
|
|
72
|
+
this.name = "HarkenNetworkError";
|
|
77
73
|
this.cause = cause;
|
|
78
74
|
}
|
|
79
75
|
|
package/src/api/index.ts
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
// Client
|
|
2
|
-
export { HarkenClient, createHarkenClient } from
|
|
3
|
-
export type { HarkenClientConfig } from
|
|
2
|
+
export { HarkenClient, createHarkenClient } from "./client";
|
|
3
|
+
export type { HarkenClientConfig } from "./client";
|
|
4
4
|
|
|
5
5
|
// Errors
|
|
6
|
-
export {
|
|
7
|
-
HarkenError,
|
|
8
|
-
HarkenApiError,
|
|
9
|
-
HarkenNetworkError,
|
|
10
|
-
} from './errors';
|
|
6
|
+
export { HarkenError, HarkenApiError, HarkenNetworkError } from "./errors";
|
|
11
7
|
|
|
12
8
|
// Retry utilities
|
|
13
|
-
export { withRetry, calculateRetryDelay, isRetryableError } from
|
|
14
|
-
export type { RetryConfig } from
|
|
15
|
-
export { DEFAULT_RETRY_CONFIG } from
|
|
9
|
+
export { withRetry, calculateRetryDelay, isRetryableError } from "./retry";
|
|
10
|
+
export type { RetryConfig } from "./retry";
|
|
11
|
+
export { DEFAULT_RETRY_CONFIG } from "./retry";
|