@blario/react-native 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +377 -2
  3. package/dist/BlarProvider.d.ts +74 -0
  4. package/dist/actions/batched.d.ts +1 -0
  5. package/dist/actions/click.d.ts +4 -0
  6. package/dist/actions/commit-watch.d.ts +15 -0
  7. package/dist/actions/events.d.ts +35 -0
  8. package/dist/actions/executor.d.ts +29 -0
  9. package/dist/actions/highlight.d.ts +4 -0
  10. package/dist/actions/interaction-guards.d.ts +9 -0
  11. package/dist/actions/landing.d.ts +11 -0
  12. package/dist/actions/navigation-driver.d.ts +22 -0
  13. package/dist/actions/parse.d.ts +4 -0
  14. package/dist/actions/result.d.ts +4 -0
  15. package/dist/actions/scrolling.d.ts +12 -0
  16. package/dist/actions/services.d.ts +20 -0
  17. package/dist/actions/settle.d.ts +13 -0
  18. package/dist/actions/switch.d.ts +3 -0
  19. package/dist/actions/target.d.ts +25 -0
  20. package/dist/actions/text-input.d.ts +9 -0
  21. package/dist/actions/types.d.ts +59 -0
  22. package/dist/agent/batch-commands.d.ts +17 -0
  23. package/dist/agent/compose.d.ts +23 -0
  24. package/dist/agent/controller.d.ts +28 -0
  25. package/dist/agent/frame-runner.d.ts +9 -0
  26. package/dist/agent/frames.d.ts +180 -0
  27. package/dist/agent/handlers/agent-command.d.ts +5 -0
  28. package/dist/agent/handlers/batch.d.ts +5 -0
  29. package/dist/agent/handlers/consent-screenshot.d.ts +4 -0
  30. package/dist/agent/handlers/context.d.ts +16 -0
  31. package/dist/agent/handlers/dom-state.d.ts +4 -0
  32. package/dist/agent/handlers/element-context.d.ts +2 -0
  33. package/dist/agent/handlers/screenshot.d.ts +3 -0
  34. package/dist/agent/handlers/text-dom.d.ts +5 -0
  35. package/dist/agent/stage.d.ts +13 -0
  36. package/dist/agent-mode/action-description.d.ts +3 -0
  37. package/dist/agent-mode/bubbles.d.ts +10 -0
  38. package/dist/agent-mode/colors.d.ts +12 -0
  39. package/dist/agent-mode/controller.d.ts +92 -0
  40. package/dist/agent-mode/cursor-follow.d.ts +12 -0
  41. package/dist/agent-mode/cursor.d.ts +37 -0
  42. package/dist/agent-mode/frames.d.ts +36 -0
  43. package/dist/agent-mode/haptics.d.ts +6 -0
  44. package/dist/agent-mode/keep-awake.d.ts +19 -0
  45. package/dist/agent-mode/layer-stacking.d.ts +7 -0
  46. package/dist/agent-mode/markdown.d.ts +19 -0
  47. package/dist/agent-mode/panel-geometry.d.ts +6 -0
  48. package/dist/agent-mode/resume.d.ts +13 -0
  49. package/dist/agent-mode/run-memory.d.ts +19 -0
  50. package/dist/agent-mode/spotlight-controller.d.ts +68 -0
  51. package/dist/agent-mode/spotlight.d.ts +6 -0
  52. package/dist/agent-mode/state.d.ts +9 -0
  53. package/dist/agent-mode/status.d.ts +11 -0
  54. package/dist/agent-mode/timers.d.ts +6 -0
  55. package/dist/agent-mode/ui/AgentDock.d.ts +37 -0
  56. package/dist/agent-mode/ui/AgentLayer.d.ts +19 -0
  57. package/dist/agent-mode/ui/AgentLayerHost.d.ts +8 -0
  58. package/dist/agent-mode/ui/AgentPanel.d.ts +19 -0
  59. package/dist/agent-mode/ui/AgentTap.d.ts +10 -0
  60. package/dist/agent-mode/ui/MarkdownText.d.ts +8 -0
  61. package/dist/agent-mode/ui/MessageBubbles.d.ts +9 -0
  62. package/dist/agent-mode/ui/PendingOverlay.d.ts +5 -0
  63. package/dist/agent-mode/ui/SpotlightLayer.d.ts +9 -0
  64. package/dist/agent-mode/ui/SpotlightOverlay.d.ts +7 -0
  65. package/dist/agent-mode/ui/StatusCard.d.ts +14 -0
  66. package/dist/agent-mode/ui/TargetPulse.d.ts +9 -0
  67. package/dist/agent-mode/ui/TouchShield.d.ts +5 -0
  68. package/dist/agent-mode/ui/WarningIcon.d.ts +6 -0
  69. package/dist/agent-mode/ui/full-window-overlay.d.ts +5 -0
  70. package/dist/agent-mode/ui/layer-host.d.ts +2 -0
  71. package/dist/backend/api-client.d.ts +38 -0
  72. package/dist/backend/api-types.d.ts +25 -0
  73. package/dist/backend/app-info.d.ts +14 -0
  74. package/dist/backend/backend-transport.d.ts +70 -0
  75. package/dist/backend/backoff.d.ts +9 -0
  76. package/dist/backend/base64.d.ts +1 -0
  77. package/dist/backend/errors.d.ts +17 -0
  78. package/dist/backend/foreground.d.ts +1 -0
  79. package/dist/backend/health.d.ts +17 -0
  80. package/dist/backend/identity-proof.d.ts +23 -0
  81. package/dist/backend/identity.d.ts +27 -0
  82. package/dist/backend/message-metadata.d.ts +19 -0
  83. package/dist/backend/presence.d.ts +20 -0
  84. package/dist/backend/response-cache.d.ts +9 -0
  85. package/dist/backend/session-change.d.ts +2 -0
  86. package/dist/backend/session-socket.d.ts +49 -0
  87. package/dist/backend/session.d.ts +39 -0
  88. package/dist/backend/socket-url.d.ts +9 -0
  89. package/dist/backend/storage.d.ts +13 -0
  90. package/dist/backend/tab-id.d.ts +2 -0
  91. package/dist/blar.d.ts +37 -0
  92. package/dist/chat/ChatLayer.d.ts +29 -0
  93. package/dist/chat/ChatSheet.d.ts +11 -0
  94. package/dist/chat/ChatView.d.ts +19 -0
  95. package/dist/chat/Launcher.d.ts +14 -0
  96. package/dist/chat/bridge.d.ts +22 -0
  97. package/dist/chat/chat-coordinator.d.ts +57 -0
  98. package/dist/chat/chat-state.d.ts +20 -0
  99. package/dist/chat/navigation.d.ts +5 -0
  100. package/dist/chat/options.d.ts +22 -0
  101. package/dist/chat/webview.d.ts +10 -0
  102. package/dist/chat/widget-bundle.generated.d.ts +2 -0
  103. package/dist/chat/widget-shell.d.ts +24 -0
  104. package/dist/confirmation/port.d.ts +12 -0
  105. package/dist/fiber/locate.d.ts +7 -0
  106. package/dist/fiber/narrow.d.ts +5 -0
  107. package/dist/fiber/reader.d.ts +10 -0
  108. package/dist/fiber/text.d.ts +4 -0
  109. package/dist/fiber/types.d.ts +21 -0
  110. package/dist/host/border-radius.d.ts +1 -0
  111. package/dist/host/instance.d.ts +9 -0
  112. package/dist/host/measure.d.ts +3 -0
  113. package/dist/host/rect.d.ts +22 -0
  114. package/dist/host/visible-area.d.ts +2 -0
  115. package/dist/index.d.ts +10 -0
  116. package/dist/nudge/NudgeBubble.d.ts +15 -0
  117. package/dist/nudge/nudge.d.ts +23 -0
  118. package/dist/optional/expo-haptics.d.ts +1 -0
  119. package/dist/optional/expo-keep-awake.d.ts +1 -0
  120. package/dist/optional/react-native-haptic-feedback.d.ts +1 -0
  121. package/dist/optional/react-native-keep-awake.d.ts +1 -0
  122. package/dist/overlay/AgentOverlay.d.ts +5 -0
  123. package/dist/screenshot/capture-backend.d.ts +6 -0
  124. package/dist/screenshot/screenshotter.d.ts +18 -0
  125. package/dist/screenshot/skia.d.ts +35 -0
  126. package/dist/screenshot/view-shot.d.ts +2 -0
  127. package/dist/snapshot/capture.d.ts +36 -0
  128. package/dist/snapshot/classify.d.ts +11 -0
  129. package/dist/snapshot/collect.d.ts +27 -0
  130. package/dist/snapshot/element-context.d.ts +9 -0
  131. package/dist/snapshot/icon.d.ts +2 -0
  132. package/dist/snapshot/kinds.d.ts +3 -0
  133. package/dist/snapshot/modals.d.ts +2 -0
  134. package/dist/snapshot/navigation.d.ts +14 -0
  135. package/dist/snapshot/ordering.d.ts +2 -0
  136. package/dist/snapshot/properties.d.ts +14 -0
  137. package/dist/snapshot/registry.d.ts +16 -0
  138. package/dist/snapshot/render.d.ts +10 -0
  139. package/dist/snapshot/route-summary.d.ts +3 -0
  140. package/dist/snapshot/scroll.d.ts +15 -0
  141. package/dist/snapshot/text-layout.d.ts +8 -0
  142. package/dist/snapshot/view.d.ts +30 -0
  143. package/dist/snapshot/visibility.d.ts +17 -0
  144. package/dist/snapshot/wrappers.d.ts +11 -0
  145. package/dist/telemetry/SdkErrorBoundary.d.ts +15 -0
  146. package/dist/telemetry/build-info.generated.d.ts +2 -0
  147. package/dist/telemetry/envelope.d.ts +53 -0
  148. package/dist/telemetry/reporter.d.ts +33 -0
  149. package/dist/telemetry/scrub.d.ts +1 -0
  150. package/dist/telemetry/sdk-reporter.d.ts +5 -0
  151. package/dist/transport/connection-options.d.ts +18 -0
  152. package/dist/transport/relay-transport.d.ts +17 -0
  153. package/dist/transport/transport.d.ts +7 -0
  154. package/dist/ui/insets.d.ts +8 -0
  155. package/dist/ui/strings.d.ts +33 -0
  156. package/dist/ui/useInsets.d.ts +2 -0
  157. package/dist/version.d.ts +1 -0
  158. package/package.json +85 -4
  159. package/src/BlarProvider.tsx +462 -0
  160. package/src/actions/batched.ts +16 -0
  161. package/src/actions/click.ts +54 -0
  162. package/src/actions/commit-watch.ts +86 -0
  163. package/src/actions/events.ts +58 -0
  164. package/src/actions/executor.ts +147 -0
  165. package/src/actions/highlight.ts +13 -0
  166. package/src/actions/interaction-guards.ts +44 -0
  167. package/src/actions/landing.ts +26 -0
  168. package/src/actions/navigation-driver.ts +131 -0
  169. package/src/actions/parse.ts +82 -0
  170. package/src/actions/result.ts +14 -0
  171. package/src/actions/scrolling.ts +112 -0
  172. package/src/actions/services.ts +24 -0
  173. package/src/actions/settle.ts +40 -0
  174. package/src/actions/switch.ts +31 -0
  175. package/src/actions/target.ts +77 -0
  176. package/src/actions/text-input.ts +82 -0
  177. package/src/actions/types.ts +52 -0
  178. package/src/agent/batch-commands.ts +33 -0
  179. package/src/agent/compose.ts +67 -0
  180. package/src/agent/controller.ts +106 -0
  181. package/src/agent/frame-runner.ts +27 -0
  182. package/src/agent/frames.ts +193 -0
  183. package/src/agent/handlers/agent-command.ts +74 -0
  184. package/src/agent/handlers/batch.ts +72 -0
  185. package/src/agent/handlers/consent-screenshot.ts +44 -0
  186. package/src/agent/handlers/context.ts +17 -0
  187. package/src/agent/handlers/dom-state.ts +19 -0
  188. package/src/agent/handlers/element-context.ts +9 -0
  189. package/src/agent/handlers/screenshot.ts +24 -0
  190. package/src/agent/handlers/text-dom.ts +17 -0
  191. package/src/agent/stage.ts +22 -0
  192. package/src/agent-mode/action-description.ts +25 -0
  193. package/src/agent-mode/bubbles.ts +31 -0
  194. package/src/agent-mode/colors.ts +40 -0
  195. package/src/agent-mode/controller.ts +459 -0
  196. package/src/agent-mode/cursor-follow.ts +43 -0
  197. package/src/agent-mode/cursor.ts +118 -0
  198. package/src/agent-mode/frames.ts +70 -0
  199. package/src/agent-mode/haptics.ts +49 -0
  200. package/src/agent-mode/keep-awake.ts +107 -0
  201. package/src/agent-mode/layer-stacking.ts +16 -0
  202. package/src/agent-mode/markdown.ts +84 -0
  203. package/src/agent-mode/panel-geometry.ts +27 -0
  204. package/src/agent-mode/resume.ts +16 -0
  205. package/src/agent-mode/run-memory.ts +65 -0
  206. package/src/agent-mode/spotlight-controller.ts +255 -0
  207. package/src/agent-mode/spotlight.ts +14 -0
  208. package/src/agent-mode/state.ts +36 -0
  209. package/src/agent-mode/status.ts +13 -0
  210. package/src/agent-mode/timers.ts +11 -0
  211. package/src/agent-mode/ui/AgentDock.tsx +285 -0
  212. package/src/agent-mode/ui/AgentLayer.tsx +123 -0
  213. package/src/agent-mode/ui/AgentLayerHost.tsx +37 -0
  214. package/src/agent-mode/ui/AgentPanel.tsx +187 -0
  215. package/src/agent-mode/ui/AgentTap.tsx +190 -0
  216. package/src/agent-mode/ui/MarkdownText.tsx +55 -0
  217. package/src/agent-mode/ui/MessageBubbles.tsx +58 -0
  218. package/src/agent-mode/ui/PendingOverlay.tsx +32 -0
  219. package/src/agent-mode/ui/SpotlightLayer.tsx +60 -0
  220. package/src/agent-mode/ui/SpotlightOverlay.tsx +35 -0
  221. package/src/agent-mode/ui/StatusCard.tsx +137 -0
  222. package/src/agent-mode/ui/TargetPulse.tsx +74 -0
  223. package/src/agent-mode/ui/TouchShield.tsx +22 -0
  224. package/src/agent-mode/ui/WarningIcon.tsx +32 -0
  225. package/src/agent-mode/ui/full-window-overlay.ts +16 -0
  226. package/src/agent-mode/ui/layer-host.ts +11 -0
  227. package/src/backend/api-client.ts +210 -0
  228. package/src/backend/api-types.ts +56 -0
  229. package/src/backend/app-info.ts +40 -0
  230. package/src/backend/backend-transport.ts +338 -0
  231. package/src/backend/backoff.ts +16 -0
  232. package/src/backend/base64.ts +25 -0
  233. package/src/backend/errors.ts +32 -0
  234. package/src/backend/foreground.ts +10 -0
  235. package/src/backend/health.ts +64 -0
  236. package/src/backend/identity-proof.ts +151 -0
  237. package/src/backend/identity.ts +31 -0
  238. package/src/backend/message-metadata.ts +28 -0
  239. package/src/backend/presence.ts +95 -0
  240. package/src/backend/response-cache.ts +45 -0
  241. package/src/backend/session-change.ts +14 -0
  242. package/src/backend/session-socket.ts +292 -0
  243. package/src/backend/session.ts +188 -0
  244. package/src/backend/socket-url.ts +25 -0
  245. package/src/backend/storage.ts +49 -0
  246. package/src/backend/tab-id.ts +24 -0
  247. package/src/blar.ts +100 -0
  248. package/src/chat/ChatLayer.tsx +109 -0
  249. package/src/chat/ChatSheet.tsx +82 -0
  250. package/src/chat/ChatView.tsx +127 -0
  251. package/src/chat/Launcher.tsx +72 -0
  252. package/src/chat/bridge.ts +56 -0
  253. package/src/chat/chat-coordinator.ts +189 -0
  254. package/src/chat/chat-state.ts +35 -0
  255. package/src/chat/navigation.ts +15 -0
  256. package/src/chat/options.ts +39 -0
  257. package/src/chat/webview.ts +23 -0
  258. package/src/chat/widget-bundle.generated.ts +2 -0
  259. package/src/chat/widget-shell.ts +170 -0
  260. package/src/confirmation/port.ts +15 -0
  261. package/src/fiber/locate.ts +41 -0
  262. package/src/fiber/narrow.ts +15 -0
  263. package/src/fiber/reader.ts +98 -0
  264. package/src/fiber/text.ts +52 -0
  265. package/src/fiber/types.ts +24 -0
  266. package/src/host/border-radius.ts +11 -0
  267. package/src/host/instance.ts +30 -0
  268. package/src/host/measure.ts +25 -0
  269. package/src/host/rect.ts +45 -0
  270. package/src/host/visible-area.ts +5 -0
  271. package/src/index.ts +10 -0
  272. package/src/nudge/NudgeBubble.tsx +117 -0
  273. package/src/nudge/nudge.ts +100 -0
  274. package/src/optional/expo-haptics.ts +11 -0
  275. package/src/optional/expo-keep-awake.ts +11 -0
  276. package/src/optional/react-native-haptic-feedback.ts +11 -0
  277. package/src/optional/react-native-keep-awake.ts +11 -0
  278. package/src/overlay/AgentOverlay.tsx +16 -0
  279. package/src/screenshot/capture-backend.ts +7 -0
  280. package/src/screenshot/screenshotter.ts +39 -0
  281. package/src/screenshot/skia.ts +107 -0
  282. package/src/screenshot/view-shot.ts +41 -0
  283. package/src/snapshot/capture.ts +163 -0
  284. package/src/snapshot/classify.ts +40 -0
  285. package/src/snapshot/collect.ts +136 -0
  286. package/src/snapshot/element-context.ts +66 -0
  287. package/src/snapshot/icon.ts +21 -0
  288. package/src/snapshot/kinds.ts +10 -0
  289. package/src/snapshot/modals.ts +21 -0
  290. package/src/snapshot/navigation.ts +47 -0
  291. package/src/snapshot/ordering.ts +26 -0
  292. package/src/snapshot/properties.ts +71 -0
  293. package/src/snapshot/registry.ts +33 -0
  294. package/src/snapshot/render.ts +140 -0
  295. package/src/snapshot/route-summary.ts +15 -0
  296. package/src/snapshot/scroll.ts +90 -0
  297. package/src/snapshot/text-layout.ts +59 -0
  298. package/src/snapshot/view.ts +87 -0
  299. package/src/snapshot/visibility.ts +63 -0
  300. package/src/snapshot/wrappers.ts +69 -0
  301. package/src/telemetry/SdkErrorBoundary.tsx +28 -0
  302. package/src/telemetry/build-info.generated.ts +2 -0
  303. package/src/telemetry/envelope.ts +83 -0
  304. package/src/telemetry/reporter.ts +170 -0
  305. package/src/telemetry/scrub.ts +10 -0
  306. package/src/telemetry/sdk-reporter.ts +37 -0
  307. package/src/transport/connection-options.ts +65 -0
  308. package/src/transport/relay-transport.ts +82 -0
  309. package/src/transport/transport.ts +8 -0
  310. package/src/ui/insets.ts +31 -0
  311. package/src/ui/strings.ts +102 -0
  312. package/src/ui/useInsets.ts +10 -0
  313. package/src/version.ts +1 -0
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { StyleSheet, Text, View } from 'react-native';
3
+
4
+ export interface WarningIconProps {
5
+ color: string;
6
+ background: string;
7
+ }
8
+
9
+ export function WarningIcon({ color, background }: WarningIconProps): React.JSX.Element {
10
+ return (
11
+ <View style={styles.box}>
12
+ <View style={[styles.triangle, { borderBottomColor: color }]} />
13
+ <Text style={[styles.mark, { color: background }]}>!</Text>
14
+ </View>
15
+ );
16
+ }
17
+
18
+ const styles = StyleSheet.create({
19
+ box: { width: 18, height: 18, alignItems: 'center', justifyContent: 'flex-end' },
20
+ triangle: {
21
+ position: 'absolute',
22
+ bottom: 1,
23
+ width: 0,
24
+ height: 0,
25
+ borderLeftWidth: 9,
26
+ borderRightWidth: 9,
27
+ borderBottomWidth: 16,
28
+ borderLeftColor: 'transparent',
29
+ borderRightColor: 'transparent',
30
+ },
31
+ mark: { fontSize: 11, lineHeight: 13, fontWeight: '700', marginBottom: 1 },
32
+ });
@@ -0,0 +1,16 @@
1
+ import type { ComponentType, ReactNode } from 'react';
2
+
3
+ import { isFunction, isRecord } from '../../fiber/narrow.ts';
4
+
5
+ export type OverlayComponent = ComponentType<{ children?: ReactNode }>;
6
+
7
+ declare const require: (moduleName: string) => unknown;
8
+
9
+ export function loadFullWindowOverlay(): OverlayComponent | null {
10
+ try {
11
+ const screens = require('react-native-screens');
12
+ return isRecord(screens) && isFunction(screens.FullWindowOverlay) ? (screens.FullWindowOverlay as OverlayComponent) : null;
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
@@ -0,0 +1,11 @@
1
+ export type LayerHostKind = 'modal' | 'full-window-overlay' | 'root';
2
+
3
+ export function chooseLayerHost(platform: string, hasFullWindowOverlay: boolean): LayerHostKind {
4
+ if (platform === 'android') {
5
+ return 'modal';
6
+ }
7
+ if (platform === 'ios' && hasFullWindowOverlay) {
8
+ return 'full-window-overlay';
9
+ }
10
+ return 'root';
11
+ }
@@ -0,0 +1,210 @@
1
+ import { isRecord } from '../fiber/narrow.ts';
2
+ import { type BootstrapResponse, type HeartbeatResponse, type IdentifyRequest, parseBootstrap, parseHeartbeat, parseSessionId } from './api-types.ts';
3
+ import { ApiRequestError, isIdentityRejection } from './errors.ts';
4
+ import type { BlarIdentity, IdentityProof, IdentityRefusalReason } from './identity.ts';
5
+ import { IdentityProofStore } from './identity-proof.ts';
6
+
7
+ const LOAD_ENDPOINT = '/api/tickets/contacts/load/';
8
+ const IDENTIFY_ENDPOINT = '/api/tickets/contacts/identify/';
9
+ const HEARTBEAT_ENDPOINT = '/api/tickets/contacts/heartbeat/';
10
+ const CREATE_SESSION_ENDPOINT = '/api/ai/contact-sessions/widget/create/';
11
+ const NUDGE_OPEN_ENDPOINT = '/api/activation/nudges/open/';
12
+ const NUDGE_DISMISS_ENDPOINT = '/api/activation/nudges/dismiss/';
13
+ const NUDGE_SNOOZE_ENDPOINT = '/api/activation/nudges/snooze/';
14
+ const REQUEST_TIMEOUT_MS = 10_000;
15
+
16
+ export interface ApiClientHooks {
17
+ restoreSource?: () => Promise<string | null>;
18
+ onIdentityRefused?: (reason: IdentityRefusalReason) => void;
19
+ }
20
+
21
+ export interface ApiClientOptions {
22
+ apiBaseUrl: string;
23
+ publishableKey: string;
24
+ }
25
+
26
+ interface RetryBudget {
27
+ refresh: boolean;
28
+ restore: boolean;
29
+ }
30
+
31
+ interface RequestSpec {
32
+ endpoint: string;
33
+ method: 'GET' | 'POST';
34
+ body?: unknown;
35
+ }
36
+
37
+ const FULL_BUDGET: RetryBudget = { refresh: true, restore: true };
38
+
39
+ async function readErrorCode(response: Response): Promise<string | null> {
40
+ try {
41
+ const body: unknown = await response.clone().json();
42
+ return isRecord(body) && typeof body.code === 'string' ? body.code : null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ async function readJson(response: Response, endpoint: string): Promise<unknown> {
49
+ const contentType = response.headers.get('content-type') ?? '';
50
+ if (!contentType.includes('application/json')) {
51
+ return {};
52
+ }
53
+ try {
54
+ return await response.json();
55
+ } catch {
56
+ throw ApiRequestError.transport(endpoint, 'network');
57
+ }
58
+ }
59
+
60
+ export class ApiClient {
61
+ private sourceId: string | null = null;
62
+
63
+ private hooks: ApiClientHooks = {};
64
+
65
+ private readonly proofs = new IdentityProofStore();
66
+
67
+ constructor(private readonly options: ApiClientOptions) {}
68
+
69
+ setHooks(hooks: ApiClientHooks): void {
70
+ this.hooks = hooks;
71
+ }
72
+
73
+ setSourceId(sourceId: string | null): void {
74
+ this.sourceId = sourceId;
75
+ }
76
+
77
+ getSourceId(): string | null {
78
+ return this.sourceId;
79
+ }
80
+
81
+ getApiBaseUrl(): string {
82
+ return this.options.apiBaseUrl;
83
+ }
84
+
85
+ getPublishableKey(): string {
86
+ return this.options.publishableKey;
87
+ }
88
+
89
+ setIdentity(identity: BlarIdentity | null): void {
90
+ this.proofs.setIdentity(identity);
91
+ }
92
+
93
+ getIdentity(): BlarIdentity | null {
94
+ return this.proofs.getIdentity();
95
+ }
96
+
97
+ getIdentityProof(): Promise<IdentityProof | null> | null {
98
+ return this.proofs.hasIdentity() ? this.proofs.get() : null;
99
+ }
100
+
101
+ refreshIdentityProof(): Promise<IdentityProof | null> {
102
+ return this.proofs.get({ refresh: true });
103
+ }
104
+
105
+ async loadSession(sourceId: string | null): Promise<BootstrapResponse> {
106
+ const raw = await this.send({ endpoint: LOAD_ENDPOINT, method: 'POST', body: sourceId === null ? {} : { source_id: sourceId } }, {
107
+ refresh: true,
108
+ restore: false,
109
+ });
110
+ return this.requireBootstrap(raw, LOAD_ENDPOINT);
111
+ }
112
+
113
+ async identifyUser(request: IdentifyRequest): Promise<BootstrapResponse> {
114
+ const raw = await this.send({ endpoint: IDENTIFY_ENDPOINT, method: 'POST', body: request }, FULL_BUDGET);
115
+ return this.requireBootstrap(raw, IDENTIFY_ENDPOINT);
116
+ }
117
+
118
+ async heartbeat(): Promise<HeartbeatResponse> {
119
+ return parseHeartbeat(await this.send({ endpoint: HEARTBEAT_ENDPOINT, method: 'POST', body: {} }, FULL_BUDGET));
120
+ }
121
+
122
+ async createSession(): Promise<number | null> {
123
+ const raw = await this.send({ endpoint: CREATE_SESSION_ENDPOINT, method: 'POST', body: { force_agent_mode: false } }, FULL_BUDGET);
124
+ return parseSessionId(raw);
125
+ }
126
+
127
+ async openNudge(tabId: string): Promise<number | null> {
128
+ return parseSessionId(await this.send({ endpoint: NUDGE_OPEN_ENDPOINT, method: 'POST', body: { tab_id: tabId } }, FULL_BUDGET));
129
+ }
130
+
131
+ async dismissNudge(milestoneMessageId: number): Promise<void> {
132
+ await this.send({ endpoint: NUDGE_DISMISS_ENDPOINT, method: 'POST', body: { milestone_message_id: milestoneMessageId } }, FULL_BUDGET);
133
+ }
134
+
135
+ async snoozeNudge(milestoneMessageId: number): Promise<void> {
136
+ await this.send({ endpoint: NUDGE_SNOOZE_ENDPOINT, method: 'POST', body: { milestone_message_id: milestoneMessageId } }, FULL_BUDGET);
137
+ }
138
+
139
+ private requireBootstrap(raw: unknown, endpoint: string): BootstrapResponse {
140
+ const bootstrap = parseBootstrap(raw);
141
+ if (bootstrap === null) {
142
+ throw new ApiRequestError({ kind: 'http', endpoint, message: `${endpoint} answered without a source_id` });
143
+ }
144
+ return bootstrap;
145
+ }
146
+
147
+ private async buildHeaders(): Promise<Record<string, string>> {
148
+ const headers: Record<string, string> = {
149
+ 'Content-Type': 'application/json',
150
+ 'X-Publishable-Key': this.options.publishableKey,
151
+ };
152
+ if (this.sourceId !== null) {
153
+ headers['X-Source-Id'] = this.sourceId;
154
+ }
155
+ const proof = await this.proofs.get();
156
+ if (proof !== null) {
157
+ headers[proof.header] = proof.value;
158
+ }
159
+ return headers;
160
+ }
161
+
162
+ private async fetchWithTimeout(spec: RequestSpec, headers: Record<string, string>): Promise<Response> {
163
+ const controller = new AbortController();
164
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
165
+ try {
166
+ return await fetch(`${this.options.apiBaseUrl}${spec.endpoint}`, {
167
+ method: spec.method,
168
+ headers,
169
+ body: spec.body === undefined ? undefined : JSON.stringify(spec.body),
170
+ signal: controller.signal,
171
+ });
172
+ } catch (error) {
173
+ const timedOut = error instanceof Error && error.name === 'AbortError';
174
+ throw ApiRequestError.transport(spec.endpoint, timedOut ? 'timeout' : 'network');
175
+ } finally {
176
+ clearTimeout(timer);
177
+ }
178
+ }
179
+
180
+ private async send(spec: RequestSpec, budget: RetryBudget): Promise<unknown> {
181
+ const headers = await this.buildHeaders();
182
+ const response = await this.fetchWithTimeout(spec, headers);
183
+ if (response.ok) {
184
+ return readJson(response, spec.endpoint);
185
+ }
186
+ const error = ApiRequestError.http(spec.endpoint, response.status, response.statusText, await readErrorCode(response));
187
+ return this.recover(spec, budget, headers, error);
188
+ }
189
+
190
+ private async recover(spec: RequestSpec, budget: RetryBudget, headers: Record<string, string>, error: ApiRequestError): Promise<unknown> {
191
+ if (error.status === 401 && headers['X-Source-Id'] === undefined) {
192
+ if (budget.restore && this.hooks.restoreSource !== undefined && (await this.hooks.restoreSource()) !== null) {
193
+ return this.send(spec, { ...budget, restore: false });
194
+ }
195
+ throw error;
196
+ }
197
+ if (budget.refresh && isIdentityRejection(error) && this.proofs.canRefresh()) {
198
+ await this.proofs.get({ refresh: true });
199
+ return this.send(spec, { ...budget, refresh: false });
200
+ }
201
+ const refused = error.code === 'identity_verification_required' && this.proofs.hasIdentity() && (!budget.refresh || !this.proofs.canRefresh());
202
+ if (refused && this.hooks.onIdentityRefused !== undefined) {
203
+ this.hooks.onIdentityRefused(this.proofs.refusalReason());
204
+ if (spec.endpoint !== IDENTIFY_ENDPOINT) {
205
+ return this.send(spec, { ...budget, refresh: false });
206
+ }
207
+ }
208
+ throw error;
209
+ }
210
+ }
@@ -0,0 +1,56 @@
1
+ import { isRecord, nonEmptyString } from '../fiber/narrow.ts';
2
+ import type { CustomFields } from './identity.ts';
3
+
4
+ export interface BootstrapResponse {
5
+ sourceId: string;
6
+ conversationId: number | null;
7
+ sessionId: number | null;
8
+ aiActive: boolean;
9
+ isIdentityVerified: boolean;
10
+ }
11
+
12
+ export interface IdentifyRequest {
13
+ external_id: string;
14
+ email?: string;
15
+ name?: string;
16
+ phone?: string;
17
+ contact_company?: string;
18
+ contact_company_external_id?: string;
19
+ country?: string;
20
+ custom_fields?: CustomFields;
21
+ }
22
+
23
+ export interface HeartbeatResponse {
24
+ hasPendingNudge: boolean;
25
+ conversationId: number | null;
26
+ }
27
+
28
+ function numberOrNull(value: unknown): number | null {
29
+ return typeof value === 'number' ? value : null;
30
+ }
31
+
32
+ export function parseBootstrap(raw: unknown): BootstrapResponse | null {
33
+ if (!isRecord(raw)) {
34
+ return null;
35
+ }
36
+ const sourceId = nonEmptyString(raw.source_id);
37
+ if (sourceId === null) {
38
+ return null;
39
+ }
40
+ return {
41
+ sourceId,
42
+ conversationId: numberOrNull(raw.conversation_id),
43
+ sessionId: numberOrNull(raw.session_id),
44
+ aiActive: raw.ai_active !== false,
45
+ isIdentityVerified: raw.is_identity_verified === true,
46
+ };
47
+ }
48
+
49
+ export function parseHeartbeat(raw: unknown): HeartbeatResponse {
50
+ const record = isRecord(raw) ? raw : {};
51
+ return { hasPendingNudge: record.has_pending_nudge === true, conversationId: numberOrNull(record.conversation_id) };
52
+ }
53
+
54
+ export function parseSessionId(raw: unknown): number | null {
55
+ return isRecord(raw) ? numberOrNull(raw.session_id) : null;
56
+ }
@@ -0,0 +1,40 @@
1
+ import { Platform } from 'react-native';
2
+
3
+ import { isRecord } from '../fiber/narrow.ts';
4
+
5
+ export interface AppInfo {
6
+ platform: 'react-native';
7
+ os: string;
8
+ os_version: string;
9
+ rn_version: string;
10
+ new_arch: boolean;
11
+ }
12
+
13
+ interface GlobalMarkers {
14
+ nativeFabricUIManager?: unknown;
15
+ RN$Bridgeless?: unknown;
16
+ }
17
+
18
+ function readReactNativeVersion(): string {
19
+ const constants: unknown = Platform.constants;
20
+ const version = isRecord(constants) ? constants.reactNativeVersion : null;
21
+ if (!isRecord(version)) {
22
+ return 'unknown';
23
+ }
24
+ const { major, minor, patch } = version;
25
+ return `${String(major)}.${String(minor)}.${String(patch)}`;
26
+ }
27
+
28
+ export function detectNewArchitecture(markers: GlobalMarkers): boolean {
29
+ return markers.nativeFabricUIManager !== undefined || markers.RN$Bridgeless === true;
30
+ }
31
+
32
+ export function readAppInfo(): AppInfo {
33
+ return {
34
+ platform: 'react-native',
35
+ os: Platform.OS,
36
+ os_version: String(Platform.Version),
37
+ rn_version: readReactNativeVersion(),
38
+ new_arch: detectNewArchitecture(globalThis as GlobalMarkers),
39
+ };
40
+ }