@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,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateUUID = generateUUID;
4
+ /**
5
+ * Generate a RFC4122 version 4 UUID.
6
+ *
7
+ * Uses crypto.getRandomValues when available (React Native),
8
+ * falls back to Math.random for older environments.
9
+ *
10
+ * @returns A random UUID string in the format xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
11
+ */
12
+ function generateUUID() {
13
+ // Use crypto.getRandomValues if available (React Native has this)
14
+ if (typeof crypto !== 'undefined' &&
15
+ typeof crypto.getRandomValues === 'function') {
16
+ return generateUUIDCrypto();
17
+ }
18
+ // Fallback to Math.random-based generation
19
+ return generateUUIDFallback();
20
+ }
21
+ /**
22
+ * Generate UUID using crypto.getRandomValues
23
+ */
24
+ function generateUUIDCrypto() {
25
+ const bytes = new Uint8Array(16);
26
+ crypto.getRandomValues(bytes);
27
+ // Set version (4) and variant (RFC4122)
28
+ bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
29
+ bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant RFC4122
30
+ return formatUUID(bytes);
31
+ }
32
+ /**
33
+ * Generate UUID using Math.random (fallback)
34
+ */
35
+ function generateUUIDFallback() {
36
+ const bytes = new Uint8Array(16);
37
+ for (let i = 0; i < 16; i++) {
38
+ bytes[i] = Math.floor(Math.random() * 256);
39
+ }
40
+ // Set version (4) and variant (RFC4122)
41
+ bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
42
+ bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant RFC4122
43
+ return formatUUID(bytes);
44
+ }
45
+ /**
46
+ * Format bytes as UUID string
47
+ */
48
+ function formatUUID(bytes) {
49
+ const hex = Array.from(bytes)
50
+ .map((b) => b.toString(16).padStart(2, '0'))
51
+ .join('');
52
+ return [
53
+ hex.slice(0, 8),
54
+ hex.slice(8, 12),
55
+ hex.slice(12, 16),
56
+ hex.slice(16, 20),
57
+ hex.slice(20, 32),
58
+ ].join('-');
59
+ }
60
+ //# sourceMappingURL=uuid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uuid.js","sourceRoot":"","sources":["../../src/utils/uuid.ts"],"names":[],"mappings":";;AAkBA,oCAWC;AAnBD;;;;;;;GAOG;AACH,SAAgB,YAAY;IAC1B,kEAAkE;IAClE,IACE,OAAO,MAAM,KAAK,WAAW;QAC7B,OAAO,MAAM,CAAC,eAAe,KAAK,UAAU,EAC5C,CAAC;QACD,OAAO,kBAAkB,EAAE,CAAC;IAC9B,CAAC;IAED,2CAA2C;IAC3C,OAAO,oBAAoB,EAAE,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB;IACzB,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IACjC,MAAO,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAE/B,wCAAwC;IACxC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,YAAY;IAClD,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,kBAAkB;IAExD,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,SAAS,oBAAoB;IAC3B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;IAC7C,CAAC;IAED,wCAAwC;IACxC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,YAAY;IAClD,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,kBAAkB;IAExD,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,SAAS,UAAU,CAAC,KAAiB;IACnC,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;SAC1B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC3C,IAAI,CAAC,EAAE,CAAC,CAAC;IAEZ,OAAO;QACL,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QACf,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;QAChB,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC;QACjB,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC;QACjB,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC;KAClB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC"}
package/package.json ADDED
@@ -0,0 +1,124 @@
1
+ {
2
+ "name": "@harkenapp/sdk-react-native",
3
+ "version": "0.0.1-alpha.1",
4
+ "description": "Harken React Native / Expo SDK for in-app feedback",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "react-native": "./src/index.ts",
9
+ "source": "./src/index.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "react-native": "./src/index.ts",
14
+ "import": "./dist/index.js",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./attachments": {
18
+ "types": "./dist/attachments/index.d.ts",
19
+ "react-native": "./src/attachments/index.ts",
20
+ "import": "./dist/attachments/index.js",
21
+ "default": "./dist/attachments/index.js"
22
+ },
23
+ "./theme": {
24
+ "types": "./dist/theme/index.d.ts",
25
+ "react-native": "./src/theme/index.ts",
26
+ "import": "./dist/theme/index.js",
27
+ "default": "./dist/theme/index.js"
28
+ },
29
+ "./app.plugin.js": "./app.plugin.js",
30
+ "./app.plugin": "./app.plugin.js"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "src",
35
+ "app.plugin.js",
36
+ "app.plugin.cjs"
37
+ ],
38
+ "scripts": {
39
+ "generate": "openapi-typescript openapi.yaml -o src/types/openapi.ts",
40
+ "build": "pnpm run generate && tsc",
41
+ "typecheck": "tsc --noEmit",
42
+ "clean": "rm -rf dist",
43
+ "prepublishOnly": "pnpm run clean && pnpm run build"
44
+ },
45
+ "keywords": [
46
+ "react-native",
47
+ "expo",
48
+ "feedback",
49
+ "sdk",
50
+ "mobile",
51
+ "harken"
52
+ ],
53
+ "license": "Apache-2.0",
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "https://github.com/harken-app/harken.git",
60
+ "directory": "packages/sdk-react-native"
61
+ },
62
+ "bugs": {
63
+ "url": "https://github.com/harken-app/harken/issues"
64
+ },
65
+ "homepage": "https://github.com/harken-app/harken#readme",
66
+ "sideEffects": false,
67
+ "expo": {
68
+ "configPlugin": "./app.plugin.cjs"
69
+ },
70
+ "peerDependencies": {
71
+ "react": ">=18.0.0",
72
+ "react-native": ">=0.72.0",
73
+ "react-native-safe-area-context": ">=4.0.0",
74
+ "expo": ">=54.0.0 <55.0.0",
75
+ "expo-secure-store": ">=13.0.0",
76
+ "expo-file-system": ">=19.0.0",
77
+ "expo-image-picker": ">=15.0.0",
78
+ "expo-document-picker": ">=12.0.0",
79
+ "@react-native-async-storage/async-storage": ">=1.21.0",
80
+ "@react-native-community/netinfo": ">=11.0.0"
81
+ },
82
+ "peerDependenciesMeta": {
83
+ "react-native": {
84
+ "optional": false
85
+ },
86
+ "expo": {
87
+ "optional": true
88
+ },
89
+ "expo-secure-store": {
90
+ "optional": true
91
+ },
92
+ "expo-file-system": {
93
+ "optional": true
94
+ },
95
+ "expo-image-picker": {
96
+ "optional": true
97
+ },
98
+ "expo-document-picker": {
99
+ "optional": true
100
+ },
101
+ "@react-native-async-storage/async-storage": {
102
+ "optional": true
103
+ },
104
+ "@react-native-community/netinfo": {
105
+ "optional": true
106
+ },
107
+ "react-native-safe-area-context": {
108
+ "optional": true
109
+ }
110
+ },
111
+ "devDependencies": {
112
+ "openapi-typescript": "^7.4.4",
113
+ "@react-native-async-storage/async-storage": "^2.1.0",
114
+ "@react-native-community/netinfo": "^11.4.1",
115
+ "@types/react": "^19.1.0",
116
+ "expo-document-picker": "^14.0.0",
117
+ "expo-file-system": "^19.0.0",
118
+ "expo-image-picker": "^17.0.0",
119
+ "react": "^19.1.0",
120
+ "react-native": "^0.81.5",
121
+ "react-native-safe-area-context": "^5.0.0",
122
+ "typescript": "^5.9.0"
123
+ }
124
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Type declarations for expo-file-system/legacy subpath.
3
+ *
4
+ * In expo-file-system v19 (SDK 54), the legacy API containing
5
+ * createUploadTask() was moved to a separate subpath.
6
+ *
7
+ * This declaration enables TypeScript to resolve the import.
8
+ */
9
+ declare module 'expo-file-system/legacy' {
10
+ // Re-export types from the build output
11
+ export * from 'expo-file-system/build/legacy/FileSystem';
12
+ export * from 'expo-file-system/build/legacy/FileSystem.types';
13
+ }
@@ -0,0 +1,250 @@
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
+
6
+ // Re-export types from contracts for convenience
7
+ type FeedbackSubmission = components['schemas']['FeedbackSubmission'];
8
+ type FeedbackSubmissionResponse = components['schemas']['FeedbackSubmissionResponse'];
9
+ type ErrorResponse = components['schemas']['ErrorResponse'];
10
+
11
+ // Attachment types
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
+
17
+ const DEFAULT_API_BASE_URL = 'https://api.harken.app';
18
+
19
+ export interface HarkenClientConfig {
20
+ /** Publishable API key */
21
+ publishableKey: string;
22
+ /** Optional user token for verified identity */
23
+ userToken?: string;
24
+ /** Base URL for API (defaults to production) */
25
+ baseUrl?: string;
26
+ /** Retry configuration */
27
+ retry?: Partial<RetryConfig>;
28
+ /** Request timeout in ms (default: 30000) */
29
+ timeout?: number;
30
+ }
31
+
32
+ /**
33
+ * Low-level API client for Harken.
34
+ */
35
+ export class HarkenClient {
36
+ private readonly config: Required<
37
+ Pick<HarkenClientConfig, 'publishableKey' | 'baseUrl' | 'timeout'>
38
+ > &
39
+ Pick<HarkenClientConfig, 'userToken' | 'retry'>;
40
+
41
+ constructor(config: HarkenClientConfig) {
42
+ this.config = {
43
+ publishableKey: config.publishableKey,
44
+ userToken: config.userToken,
45
+ baseUrl: config.baseUrl ?? DEFAULT_API_BASE_URL,
46
+ retry: config.retry,
47
+ timeout: config.timeout ?? 30000,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Submit feedback to the API.
53
+ */
54
+ async submitFeedback(
55
+ submission: FeedbackSubmission
56
+ ): Promise<FeedbackSubmissionResponse> {
57
+ return withRetry(
58
+ () => this.request<FeedbackSubmissionResponse>('/v1/feedback', {
59
+ method: 'POST',
60
+ body: JSON.stringify(submission),
61
+ }),
62
+ this.config.retry
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Create a presigned URL for attachment upload.
68
+ */
69
+ async createAttachmentUpload(
70
+ request: AttachmentPresignRequest
71
+ ): Promise<AttachmentPresignResponse> {
72
+ return withRetry(
73
+ () =>
74
+ this.request<AttachmentPresignResponse>(
75
+ '/v1/feedback/attachments/presign',
76
+ {
77
+ method: 'POST',
78
+ body: JSON.stringify(request),
79
+ }
80
+ ),
81
+ this.config.retry
82
+ );
83
+ }
84
+
85
+ /**
86
+ * Confirm successful attachment upload.
87
+ */
88
+ async confirmAttachment(
89
+ attachmentId: string,
90
+ request?: AttachmentConfirmRequest
91
+ ): Promise<AttachmentStatusResponse> {
92
+ return withRetry(
93
+ () =>
94
+ this.request<AttachmentStatusResponse>(
95
+ `/v1/feedback/attachments/${attachmentId}/confirm`,
96
+ {
97
+ method: 'POST',
98
+ body: request ? JSON.stringify(request) : undefined,
99
+ }
100
+ ),
101
+ this.config.retry
102
+ );
103
+ }
104
+
105
+ /**
106
+ * Report attachment upload failure.
107
+ * This is a terminal state, so no retry is applied.
108
+ */
109
+ async reportAttachmentFailure(
110
+ attachmentId: string,
111
+ error?: string
112
+ ): Promise<AttachmentStatusResponse> {
113
+ return this.request<AttachmentStatusResponse>(
114
+ `/v1/feedback/attachments/${attachmentId}/fail`,
115
+ {
116
+ method: 'POST',
117
+ body: error ? JSON.stringify({ error }) : undefined,
118
+ }
119
+ );
120
+ }
121
+
122
+ /**
123
+ * Get attachment status and download URLs.
124
+ */
125
+ async getAttachmentStatus(
126
+ attachmentId: string
127
+ ): Promise<AttachmentStatusResponse> {
128
+ return this.request<AttachmentStatusResponse>(
129
+ `/v1/feedback/attachments/${attachmentId}`
130
+ );
131
+ }
132
+
133
+ /**
134
+ * Make an authenticated request to the API.
135
+ */
136
+ private async request<T>(
137
+ path: string,
138
+ options: RequestInit = {}
139
+ ): Promise<T> {
140
+ const url = `${this.config.baseUrl}${path}`;
141
+
142
+ const headers: Record<string, string> = {
143
+ 'Content-Type': 'application/json',
144
+ 'X-Publishable-Key': this.config.publishableKey,
145
+ ...(options.headers as Record<string, string>),
146
+ };
147
+
148
+ if (this.config.userToken) {
149
+ headers['X-User-Token'] = this.config.userToken;
150
+ }
151
+
152
+ // Create abort controller for timeout
153
+ const controller = new AbortController();
154
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
155
+
156
+ try {
157
+ const response = await fetch(url, {
158
+ ...options,
159
+ headers,
160
+ signal: controller.signal,
161
+ });
162
+
163
+ clearTimeout(timeoutId);
164
+
165
+ if (!response.ok) {
166
+ const errorBody = await this.parseErrorResponse(response);
167
+ // Only parse Retry-After for 429 responses
168
+ const retryAfter = response.status === 429
169
+ ? this.parseRetryAfter(response)
170
+ : undefined;
171
+ throw new HarkenApiError(response.status, errorBody, { retryAfter });
172
+ }
173
+
174
+ return (await response.json()) as T;
175
+ } catch (error) {
176
+ clearTimeout(timeoutId);
177
+
178
+ // Re-throw HarkenApiError as-is
179
+ if (error instanceof HarkenApiError) {
180
+ throw error;
181
+ }
182
+
183
+ // Handle abort (timeout)
184
+ if (error instanceof Error && error.name === 'AbortError') {
185
+ throw new HarkenNetworkError('Request timed out', error);
186
+ }
187
+
188
+ // Handle other fetch errors (network issues)
189
+ if (error instanceof TypeError) {
190
+ throw new HarkenNetworkError('Network request failed', error);
191
+ }
192
+
193
+ // Unknown error
194
+ throw new HarkenNetworkError(
195
+ error instanceof Error ? error.message : 'Unknown error',
196
+ error instanceof Error ? error : undefined
197
+ );
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Parse error response body, with fallback for non-JSON responses.
203
+ */
204
+ private async parseErrorResponse(response: Response): Promise<ErrorResponse> {
205
+ try {
206
+ return (await response.json()) as ErrorResponse;
207
+ } catch {
208
+ // Fallback for non-JSON error responses
209
+ return {
210
+ error: {
211
+ code: `http_${response.status}`,
212
+ message: response.statusText || 'Request failed',
213
+ },
214
+ };
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Parse Retry-After header value in seconds.
220
+ * Supports both delta-seconds and HTTP-date formats.
221
+ */
222
+ private parseRetryAfter(response: Response): number | undefined {
223
+ const retryAfter = response.headers.get('Retry-After');
224
+ if (!retryAfter) {
225
+ return undefined;
226
+ }
227
+
228
+ // Try parsing as integer (delta-seconds)
229
+ const seconds = parseInt(retryAfter, 10);
230
+ if (!isNaN(seconds) && seconds >= 0) {
231
+ return seconds;
232
+ }
233
+
234
+ // Try parsing as HTTP-date
235
+ const date = Date.parse(retryAfter);
236
+ if (!isNaN(date)) {
237
+ const delayMs = date - Date.now();
238
+ return delayMs > 0 ? Math.ceil(delayMs / 1000) : 0;
239
+ }
240
+
241
+ return undefined;
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Create a configured Harken client.
247
+ */
248
+ export function createHarkenClient(config: HarkenClientConfig): HarkenClient {
249
+ return new HarkenClient(config);
250
+ }
@@ -0,0 +1,84 @@
1
+ import type { components } from '../types/index.js';
2
+
3
+ type ErrorResponse = components['schemas']['ErrorResponse'];
4
+ type ErrorDetail = components['schemas']['ErrorDetail'];
5
+
6
+ /**
7
+ * Base error class for Harken API errors.
8
+ */
9
+ export class HarkenError extends Error {
10
+ constructor(message: string) {
11
+ super(message);
12
+ this.name = 'HarkenError';
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Error thrown when the API returns an error response.
18
+ */
19
+ export class HarkenApiError extends HarkenError {
20
+ /** HTTP status code */
21
+ readonly status: number;
22
+ /** Machine-readable error code */
23
+ readonly code: string;
24
+ /** Validation error details (if present) */
25
+ readonly details?: ErrorDetail[];
26
+ /** Retry-After value in seconds (from 429 responses) */
27
+ readonly retryAfter?: number;
28
+
29
+ constructor(
30
+ status: number,
31
+ response: ErrorResponse,
32
+ options?: { retryAfter?: number }
33
+ ) {
34
+ super(response.error.message);
35
+ this.name = 'HarkenApiError';
36
+ this.status = status;
37
+ this.code = response.error.code;
38
+ this.details = response.error.details;
39
+ this.retryAfter = options?.retryAfter;
40
+ }
41
+
42
+ /** True if this is a validation error (400) */
43
+ get isValidationError(): boolean {
44
+ return this.status === 400;
45
+ }
46
+
47
+ /** True if this is an auth error (401) */
48
+ get isUnauthorized(): boolean {
49
+ return this.status === 401;
50
+ }
51
+
52
+ /** True if this is a rate limit error (429) */
53
+ get isRateLimited(): boolean {
54
+ return this.status === 429;
55
+ }
56
+
57
+ /** True if this is a server error (5xx) */
58
+ get isServerError(): boolean {
59
+ return this.status >= 500;
60
+ }
61
+
62
+ /** True if this error is retryable */
63
+ get isRetryable(): boolean {
64
+ return this.isRateLimited || this.isServerError;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Error thrown when a network request fails.
70
+ */
71
+ export class HarkenNetworkError extends HarkenError {
72
+ readonly cause?: Error;
73
+
74
+ constructor(message: string, cause?: Error) {
75
+ super(message);
76
+ this.name = 'HarkenNetworkError';
77
+ this.cause = cause;
78
+ }
79
+
80
+ /** Network errors are always retryable */
81
+ get isRetryable(): boolean {
82
+ return true;
83
+ }
84
+ }
@@ -0,0 +1,15 @@
1
+ // Client
2
+ export { HarkenClient, createHarkenClient } from './client';
3
+ export type { HarkenClientConfig } from './client';
4
+
5
+ // Errors
6
+ export {
7
+ HarkenError,
8
+ HarkenApiError,
9
+ HarkenNetworkError,
10
+ } from './errors';
11
+
12
+ // Retry utilities
13
+ export { withRetry, calculateRetryDelay, isRetryableError } from './retry';
14
+ export type { RetryConfig } from './retry';
15
+ export { DEFAULT_RETRY_CONFIG } from './retry';
@@ -0,0 +1,99 @@
1
+ import { HarkenApiError, HarkenNetworkError } from './errors';
2
+
3
+ export interface RetryConfig {
4
+ /** Maximum number of retry attempts (default: 3) */
5
+ maxRetries: number;
6
+ /** Base delay in ms for exponential backoff (default: 1000) */
7
+ baseDelay: number;
8
+ /** Maximum delay in ms (default: 30000) */
9
+ maxDelay: number;
10
+ /** Jitter factor 0-1 to randomize delays (default: 0.1) */
11
+ jitter: number;
12
+ }
13
+
14
+ export const DEFAULT_RETRY_CONFIG: RetryConfig = {
15
+ maxRetries: 3,
16
+ baseDelay: 1000,
17
+ maxDelay: 30000,
18
+ jitter: 0.1,
19
+ };
20
+
21
+ /**
22
+ * Calculate delay for a retry attempt with exponential backoff and jitter.
23
+ */
24
+ export function calculateRetryDelay(
25
+ attempt: number,
26
+ config: RetryConfig,
27
+ retryAfter?: number
28
+ ): number {
29
+ // If server specified Retry-After, respect it
30
+ if (retryAfter !== undefined && retryAfter > 0) {
31
+ return Math.min(retryAfter * 1000, config.maxDelay);
32
+ }
33
+
34
+ // Exponential backoff: baseDelay * 2^attempt
35
+ const exponentialDelay = config.baseDelay * Math.pow(2, attempt);
36
+ const cappedDelay = Math.min(exponentialDelay, config.maxDelay);
37
+
38
+ // Add jitter
39
+ const jitterRange = cappedDelay * config.jitter;
40
+ const jitter = (Math.random() - 0.5) * 2 * jitterRange;
41
+
42
+ return Math.max(0, cappedDelay + jitter);
43
+ }
44
+
45
+ /**
46
+ * Check if an error is retryable.
47
+ */
48
+ export function isRetryableError(
49
+ error: unknown
50
+ ): error is HarkenApiError | HarkenNetworkError {
51
+ if (error instanceof HarkenApiError) {
52
+ return error.isRetryable;
53
+ }
54
+ if (error instanceof HarkenNetworkError) {
55
+ return error.isRetryable;
56
+ }
57
+ return false;
58
+ }
59
+
60
+ /**
61
+ * Sleep for a given number of milliseconds.
62
+ */
63
+ export function sleep(ms: number): Promise<void> {
64
+ return new Promise((resolve) => setTimeout(resolve, ms));
65
+ }
66
+
67
+ /**
68
+ * Execute a function with retry logic.
69
+ */
70
+ export async function withRetry<T>(
71
+ fn: () => Promise<T>,
72
+ config: Partial<RetryConfig> = {}
73
+ ): Promise<T> {
74
+ const fullConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
75
+ let lastError: unknown;
76
+
77
+ for (let attempt = 0; attempt <= fullConfig.maxRetries; attempt++) {
78
+ try {
79
+ return await fn();
80
+ } catch (error) {
81
+ lastError = error;
82
+
83
+ // Don't retry if this is the last attempt or error isn't retryable
84
+ if (attempt >= fullConfig.maxRetries || !isRetryableError(error)) {
85
+ throw error;
86
+ }
87
+
88
+ // Calculate delay, respecting Retry-After header if present
89
+ const retryAfter =
90
+ error instanceof HarkenApiError ? error.retryAfter : undefined;
91
+
92
+ const delay = calculateRetryDelay(attempt, fullConfig, retryAfter);
93
+ await sleep(delay);
94
+ }
95
+ }
96
+
97
+ // Should never reach here, but TypeScript needs this
98
+ throw lastError;
99
+ }