@mentra/bluetooth-sdk 0.1.3 → 0.1.4

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.
@@ -0,0 +1,194 @@
1
+ import {useEffect, useRef, useState} from "react"
2
+
3
+ import BluetoothSdk from "../index"
4
+ import type {ConnectOptions, Device, DeviceModel} from "../BluetoothSdk.types"
5
+
6
+ import {useBluetoothScan, type BluetoothScanHookResult} from "./useBluetoothScan"
7
+ import {useBluetoothStatus, type BluetoothStatusHookResult} from "./useBluetoothStatus"
8
+
9
+ export type DefaultDeviceStorage = {
10
+ load: () => Promise<Device | null>
11
+ save: (device: Device | null) => Promise<void>
12
+ }
13
+
14
+ export type GlassesConnectionAction =
15
+ | "idle"
16
+ | "scanning"
17
+ | "connecting"
18
+ | "disconnecting"
19
+ | "forgetting"
20
+
21
+ export type UseGlassesConnectionOptions = {
22
+ autoConnectDefault?: boolean
23
+ defaultDeviceStorage?: DefaultDeviceStorage
24
+ onError?: (error: unknown) => void
25
+ scanModel?: DeviceModel
26
+ scanTimeoutMs?: number
27
+ }
28
+
29
+ export type GlassesConnectionHookResult = BluetoothStatusHookResult & {
30
+ action: GlassesConnectionAction
31
+ busy: boolean
32
+ clearDefaultDevice: () => Promise<void>
33
+ connect: (device?: Device, options?: ConnectOptions) => Promise<void>
34
+ connectDefault: (options?: ConnectOptions) => Promise<void>
35
+ defaultDevice: Device | null
36
+ disconnect: () => Promise<void>
37
+ forget: () => Promise<void>
38
+ scan: BluetoothScanHookResult
39
+ setDefaultDevice: (device: Device | null) => Promise<void>
40
+ }
41
+
42
+ export function useGlassesConnection(
43
+ options: UseGlassesConnectionOptions = {},
44
+ ): GlassesConnectionHookResult {
45
+ const status = useBluetoothStatus({onError: options.onError})
46
+ const scan = useBluetoothScan({
47
+ model: options.scanModel,
48
+ onError: options.onError,
49
+ timeoutMs: options.scanTimeoutMs,
50
+ })
51
+ const [action, setAction] = useState<GlassesConnectionAction>("idle")
52
+ const [defaultDevice, setDefaultDeviceState] = useState<Device | null>(null)
53
+ const [operationError, setOperationError] = useState<unknown | null>(null)
54
+ const autoConnectAttemptedRef = useRef(false)
55
+ const defaultDeviceStorageRef = useRef(options.defaultDeviceStorage)
56
+ const onErrorRef = useRef(options.onError)
57
+
58
+ useEffect(() => {
59
+ defaultDeviceStorageRef.current = options.defaultDeviceStorage
60
+ onErrorRef.current = options.onError
61
+ }, [options.defaultDeviceStorage, options.onError])
62
+
63
+ useEffect(() => {
64
+ let mounted = true
65
+
66
+ const loadDefaultDevice = async () => {
67
+ try {
68
+ const [nativeDefaultDevice, storedDefaultDevice] = await Promise.all([
69
+ BluetoothSdk.getDefaultDevice(),
70
+ defaultDeviceStorageRef.current?.load() ?? Promise.resolve(null),
71
+ ])
72
+ if (!mounted) {
73
+ return
74
+ }
75
+ const nextDefaultDevice = storedDefaultDevice ?? nativeDefaultDevice
76
+ if (nextDefaultDevice) {
77
+ await BluetoothSdk.setDefaultDevice(nextDefaultDevice)
78
+ }
79
+ if (mounted) {
80
+ setDefaultDeviceState(nextDefaultDevice)
81
+ setOperationError(null)
82
+ }
83
+ } catch (nextError) {
84
+ if (!mounted) {
85
+ return
86
+ }
87
+ setOperationError(nextError)
88
+ onErrorRef.current?.(nextError)
89
+ }
90
+ }
91
+
92
+ void loadDefaultDevice()
93
+
94
+ return () => {
95
+ mounted = false
96
+ }
97
+ }, [])
98
+
99
+ useEffect(() => {
100
+ if (!options.autoConnectDefault || autoConnectAttemptedRef.current || status.connected || !defaultDevice) {
101
+ return
102
+ }
103
+
104
+ autoConnectAttemptedRef.current = true
105
+ void connectDefault().catch(() => undefined)
106
+ }, [defaultDevice, options.autoConnectDefault, status.connected])
107
+
108
+ async function runConnectionAction<T>(
109
+ nextAction: Exclude<GlassesConnectionAction, "idle" | "scanning">,
110
+ operation: () => Promise<T>,
111
+ ): Promise<T> {
112
+ setAction(nextAction)
113
+ setOperationError(null)
114
+ try {
115
+ const result = await operation()
116
+ setOperationError(null)
117
+ return result
118
+ } catch (nextError) {
119
+ setOperationError(nextError)
120
+ onErrorRef.current?.(nextError)
121
+ throw nextError
122
+ } finally {
123
+ setAction("idle")
124
+ }
125
+ }
126
+
127
+ async function setDefaultDevice(device: Device | null) {
128
+ await runConnectionAction("connecting", async () => {
129
+ await BluetoothSdk.setDefaultDevice(device)
130
+ await defaultDeviceStorageRef.current?.save(device)
131
+ setDefaultDeviceState(device)
132
+ })
133
+ }
134
+
135
+ async function clearDefaultDevice() {
136
+ await runConnectionAction("forgetting", async () => {
137
+ await BluetoothSdk.clearDefaultDevice()
138
+ await defaultDeviceStorageRef.current?.save(null)
139
+ setDefaultDeviceState(null)
140
+ scan.selectDevice(null)
141
+ })
142
+ }
143
+
144
+ async function connect(device?: Device, connectOptions?: ConnectOptions) {
145
+ await runConnectionAction("connecting", async () => {
146
+ const targetDevice = device ?? scan.selectedDevice
147
+ if (targetDevice) {
148
+ await BluetoothSdk.connect(targetDevice, connectOptions)
149
+ if (connectOptions?.saveAsDefault !== false) {
150
+ await defaultDeviceStorageRef.current?.save(targetDevice)
151
+ setDefaultDeviceState(targetDevice)
152
+ }
153
+ return
154
+ }
155
+ await BluetoothSdk.connectDefault(connectOptions)
156
+ })
157
+ }
158
+
159
+ async function connectDefault(connectOptions?: ConnectOptions) {
160
+ await runConnectionAction("connecting", async () => {
161
+ await BluetoothSdk.connectDefault(connectOptions)
162
+ })
163
+ }
164
+
165
+ async function disconnect() {
166
+ await runConnectionAction("disconnecting", async () => {
167
+ await BluetoothSdk.disconnect()
168
+ })
169
+ }
170
+
171
+ async function forget() {
172
+ await runConnectionAction("forgetting", async () => {
173
+ await BluetoothSdk.forget()
174
+ scan.clearResults()
175
+ })
176
+ }
177
+
178
+ const busy = action !== "idle" || scan.scanning || status.loading
179
+
180
+ return {
181
+ ...status,
182
+ action: scan.scanning ? "scanning" : action,
183
+ busy,
184
+ clearDefaultDevice,
185
+ connect,
186
+ connectDefault,
187
+ defaultDevice,
188
+ disconnect,
189
+ error: operationError ?? scan.error ?? status.error,
190
+ forget,
191
+ scan,
192
+ setDefaultDevice,
193
+ }
194
+ }