@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.
package/README.md CHANGED
@@ -5,6 +5,8 @@ React Native and Expo SDK for connecting mobile apps directly to supported Mentr
5
5
  The package includes:
6
6
 
7
7
  - A React Native / Expo module API exposed as `BluetoothSdk`.
8
+ - React hooks under `@mentra/bluetooth-sdk/react` for common scan,
9
+ connection, status, and event lifecycles.
8
10
  - Native Android code published as `com.mentra:bluetooth-sdk`.
9
11
  - Native iOS code published as the `MentraBluetoothSDK` CocoaPod.
10
12
  - An Expo config plugin that wires the native dependencies into generated Android and iOS projects.
@@ -141,6 +143,46 @@ if (isReadyGlassesConnectionStatus(glasses.connection)) {
141
143
  removeGlassesListener()
142
144
  ```
143
145
 
146
+ ## React Hooks
147
+
148
+ React Native apps can import optional lifecycle helpers from the `react`
149
+ subpath. The hooks use the same SDK types and still leave commands such as
150
+ `requestPhoto()`, `startStream()`, and `setMicState()` on the root
151
+ `BluetoothSdk` object.
152
+
153
+ ```tsx
154
+ import {Button, Text, View} from 'react-native'
155
+ import {DeviceModels} from '@mentra/bluetooth-sdk'
156
+ import {useBluetoothEvent, useGlassesConnection} from '@mentra/bluetooth-sdk/react'
157
+
158
+ export function DeviceScreen() {
159
+ const glasses = useGlassesConnection({
160
+ scanModel: DeviceModels.MentraLive,
161
+ scanTimeoutMs: 10_000,
162
+ })
163
+
164
+ useBluetoothEvent('button_press', (event) => {
165
+ console.log('Glasses button:', event.buttonId, event.pressType)
166
+ })
167
+
168
+ return (
169
+ <View>
170
+ <Text>{glasses.connected ? 'Connected' : 'Disconnected'}</Text>
171
+ <Button disabled={glasses.busy} title="Scan" onPress={() => glasses.scan.startScan()} />
172
+ {glasses.scan.devices.map((device) => (
173
+ <Button key={device.id} title={device.name} onPress={() => glasses.connect(device)} />
174
+ ))}
175
+ <Button disabled={!glasses.connected} title="Disconnect" onPress={glasses.disconnect} />
176
+ </View>
177
+ )
178
+ }
179
+ ```
180
+
181
+ The hooks do not request Android permissions or choose a persistence package for
182
+ you. Ask for permissions in your app before calling scan/connect actions, and
183
+ pass a `defaultDeviceStorage` adapter to `useGlassesConnection` if you want a
184
+ default device to survive app restarts.
185
+
144
186
  React Native status exposes `GlassesStatus.connection` as a discriminated union:
145
187
 
146
188
  ```ts
package/app.plugin.js CHANGED
@@ -1 +1,3 @@
1
- module.exports = require('./plugin/build');
1
+ const plugin = require('./plugin/build');
2
+
3
+ module.exports = plugin.default ?? plugin;
@@ -0,0 +1,5 @@
1
+ export { useBluetoothEvent, type UseBluetoothEventOptions, } from "./useBluetoothEvent";
2
+ export { useBluetoothScan, type BluetoothScanDedupe, type BluetoothScanHookResult, type UseBluetoothScanOptions, } from "./useBluetoothScan";
3
+ export { useBluetoothStatus, type BluetoothStatusHookResult, type UseBluetoothStatusOptions, } from "./useBluetoothStatus";
4
+ export { useGlassesConnection, type DefaultDeviceStorage, type GlassesConnectionAction, type GlassesConnectionHookResult, type UseGlassesConnectionOptions, } from "./useGlassesConnection";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,KAAK,wBAAwB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,gBAAgB,EAChB,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,GAC7B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,GAC/B,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,oBAAoB,EACpB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,GACjC,MAAM,wBAAwB,CAAA"}
@@ -0,0 +1,5 @@
1
+ export { useBluetoothEvent, } from "./useBluetoothEvent";
2
+ export { useBluetoothScan, } from "./useBluetoothScan";
3
+ export { useBluetoothStatus, } from "./useBluetoothStatus";
4
+ export { useGlassesConnection, } from "./useGlassesConnection";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,GAElB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,gBAAgB,GAIjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,kBAAkB,GAGnB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,oBAAoB,GAKrB,MAAM,wBAAwB,CAAA","sourcesContent":["export {\n useBluetoothEvent,\n type UseBluetoothEventOptions,\n} from \"./useBluetoothEvent\"\nexport {\n useBluetoothScan,\n type BluetoothScanDedupe,\n type BluetoothScanHookResult,\n type UseBluetoothScanOptions,\n} from \"./useBluetoothScan\"\nexport {\n useBluetoothStatus,\n type BluetoothStatusHookResult,\n type UseBluetoothStatusOptions,\n} from \"./useBluetoothStatus\"\nexport {\n useGlassesConnection,\n type DefaultDeviceStorage,\n type GlassesConnectionAction,\n type GlassesConnectionHookResult,\n type UseGlassesConnectionOptions,\n} from \"./useGlassesConnection\"\n"]}
@@ -0,0 +1,6 @@
1
+ import type { BluetoothSdkEventListener, BluetoothSdkEventName } from "../BluetoothSdk.types";
2
+ export type UseBluetoothEventOptions = {
3
+ enabled?: boolean;
4
+ };
5
+ export declare function useBluetoothEvent<EventName extends BluetoothSdkEventName>(eventName: EventName, listener: BluetoothSdkEventListener<EventName>, options?: UseBluetoothEventOptions): void;
6
+ //# sourceMappingURL=useBluetoothEvent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBluetoothEvent.d.ts","sourceRoot":"","sources":["../../src/react/useBluetoothEvent.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,yBAAyB,EACzB,qBAAqB,EACtB,MAAM,uBAAuB,CAAA;AAE9B,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,wBAAgB,iBAAiB,CAAC,SAAS,SAAS,qBAAqB,EACvE,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,yBAAyB,CAAC,SAAS,CAAC,EAC9C,OAAO,GAAE,wBAA6B,GACrC,IAAI,CAqBN"}
@@ -0,0 +1,21 @@
1
+ import { useEffect, useRef } from "react";
2
+ import BluetoothSdk from "../index";
3
+ export function useBluetoothEvent(eventName, listener, options = {}) {
4
+ const enabled = options.enabled ?? true;
5
+ const listenerRef = useRef(listener);
6
+ useEffect(() => {
7
+ listenerRef.current = listener;
8
+ }, [listener]);
9
+ useEffect(() => {
10
+ if (!enabled) {
11
+ return undefined;
12
+ }
13
+ const subscription = BluetoothSdk.addListener(eventName, (event) => {
14
+ listenerRef.current(event);
15
+ });
16
+ return () => {
17
+ subscription.remove();
18
+ };
19
+ }, [enabled, eventName]);
20
+ }
21
+ //# sourceMappingURL=useBluetoothEvent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBluetoothEvent.js","sourceRoot":"","sources":["../../src/react/useBluetoothEvent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,MAAM,EAAC,MAAM,OAAO,CAAA;AAEvC,OAAO,YAAY,MAAM,UAAU,CAAA;AAUnC,MAAM,UAAU,iBAAiB,CAC/B,SAAoB,EACpB,QAA8C,EAC9C,UAAoC,EAAE;IAEtC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,CAAA;IACvC,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;IAEpC,SAAS,CAAC,GAAG,EAAE;QACb,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAA;IAChC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEd,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,SAAS,CAAA;QAClB,CAAC;QAED,MAAM,YAAY,GAAG,YAAY,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;YACjE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAC5B,CAAC,CAAC,CAAA;QAEF,OAAO,GAAG,EAAE;YACV,YAAY,CAAC,MAAM,EAAE,CAAA;QACvB,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAA;AAC1B,CAAC","sourcesContent":["import {useEffect, useRef} from \"react\"\n\nimport BluetoothSdk from \"../index\"\nimport type {\n BluetoothSdkEventListener,\n BluetoothSdkEventName,\n} from \"../BluetoothSdk.types\"\n\nexport type UseBluetoothEventOptions = {\n enabled?: boolean\n}\n\nexport function useBluetoothEvent<EventName extends BluetoothSdkEventName>(\n eventName: EventName,\n listener: BluetoothSdkEventListener<EventName>,\n options: UseBluetoothEventOptions = {},\n): void {\n const enabled = options.enabled ?? true\n const listenerRef = useRef(listener)\n\n useEffect(() => {\n listenerRef.current = listener\n }, [listener])\n\n useEffect(() => {\n if (!enabled) {\n return undefined\n }\n\n const subscription = BluetoothSdk.addListener(eventName, (event) => {\n listenerRef.current(event)\n })\n\n return () => {\n subscription.remove()\n }\n }, [enabled, eventName])\n}\n"]}
@@ -0,0 +1,22 @@
1
+ import type { Device, DeviceModel } from "../BluetoothSdk.types";
2
+ export type BluetoothScanDedupe = "id" | "name" | ((device: Device) => string);
3
+ export type UseBluetoothScanOptions = {
4
+ dedupe?: BluetoothScanDedupe;
5
+ model?: DeviceModel;
6
+ onError?: (error: unknown) => void;
7
+ timeoutMs?: number;
8
+ };
9
+ export type BluetoothScanHookResult = {
10
+ clearResults: () => void;
11
+ devices: Device[];
12
+ error: unknown | null;
13
+ model: DeviceModel;
14
+ scanning: boolean;
15
+ selectedDevice: Device | null;
16
+ selectDevice: (device: Device | null) => void;
17
+ setModel: (model: DeviceModel) => void;
18
+ startScan: (model?: DeviceModel) => Promise<Device[]>;
19
+ stopScan: () => Promise<void>;
20
+ };
21
+ export declare function useBluetoothScan(options?: UseBluetoothScanOptions): BluetoothScanHookResult;
22
+ //# sourceMappingURL=useBluetoothScan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBluetoothScan.d.ts","sourceRoot":"","sources":["../../src/react/useBluetoothScan.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,MAAM,EAAE,WAAW,EAAC,MAAM,uBAAuB,CAAA;AAE9D,MAAM,MAAM,mBAAmB,GAAG,IAAI,GAAG,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC,CAAA;AAE9E,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,CAAC,EAAE,mBAAmB,CAAA;IAC5B,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAClC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;IACrB,KAAK,EAAE,WAAW,CAAA;IAClB,QAAQ,EAAE,OAAO,CAAA;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;IACtC,SAAS,EAAE,CAAC,KAAK,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACrD,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9B,CAAA;AAoCD,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,uBAAuB,CA+G/F"}
@@ -0,0 +1,135 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import BluetoothSdk, { DeviceModels } from "../index";
3
+ function dedupeKey(device, dedupe) {
4
+ if (typeof dedupe === "function") {
5
+ return dedupe(device);
6
+ }
7
+ if (dedupe === "name") {
8
+ return `${device.model}:${device.name}`;
9
+ }
10
+ return device.id;
11
+ }
12
+ function dedupeDevices(devices, dedupe) {
13
+ const seen = new Set();
14
+ const nextDevices = [];
15
+ devices.forEach((device) => {
16
+ const key = dedupeKey(device, dedupe);
17
+ if (seen.has(key)) {
18
+ return;
19
+ }
20
+ seen.add(key);
21
+ nextDevices.push(device);
22
+ });
23
+ return nextDevices;
24
+ }
25
+ function hasDevice(devices, selectedDevice, dedupe) {
26
+ if (!selectedDevice) {
27
+ return true;
28
+ }
29
+ const selectedKey = dedupeKey(selectedDevice, dedupe);
30
+ return devices.some((device) => dedupeKey(device, dedupe) === selectedKey);
31
+ }
32
+ export function useBluetoothScan(options = {}) {
33
+ const [devices, setDevices] = useState([]);
34
+ const [error, setError] = useState(null);
35
+ const [model, setModelState] = useState(options.model ?? DeviceModels.MentraLive);
36
+ const [scanning, setScanning] = useState(false);
37
+ const [selectedDevice, selectDevice] = useState(null);
38
+ const activeScanRef = useRef(0);
39
+ const dedupeRef = useRef(options.dedupe ?? "id");
40
+ const onErrorRef = useRef(options.onError);
41
+ const scanningRef = useRef(false);
42
+ const timeoutMsRef = useRef(options.timeoutMs);
43
+ useEffect(() => {
44
+ dedupeRef.current = options.dedupe ?? "id";
45
+ onErrorRef.current = options.onError;
46
+ timeoutMsRef.current = options.timeoutMs;
47
+ }, [options.dedupe, options.onError, options.timeoutMs]);
48
+ useEffect(() => {
49
+ scanningRef.current = scanning;
50
+ }, [scanning]);
51
+ useEffect(() => {
52
+ if (!hasDevice(devices, selectedDevice, dedupeRef.current)) {
53
+ selectDevice(null);
54
+ }
55
+ }, [devices, selectedDevice]);
56
+ useEffect(() => {
57
+ return () => {
58
+ if (!scanningRef.current) {
59
+ return;
60
+ }
61
+ activeScanRef.current += 1;
62
+ void BluetoothSdk.stopScan().catch(() => undefined);
63
+ };
64
+ }, []);
65
+ function clearResults() {
66
+ setDevices([]);
67
+ selectDevice(null);
68
+ }
69
+ function setModel(nextModel) {
70
+ setModelState(nextModel);
71
+ clearResults();
72
+ }
73
+ async function startScan(nextModel) {
74
+ const scanId = activeScanRef.current + 1;
75
+ activeScanRef.current = scanId;
76
+ const scanModel = nextModel ?? model;
77
+ if (nextModel && nextModel !== model) {
78
+ setModelState(nextModel);
79
+ }
80
+ setError(null);
81
+ setScanning(true);
82
+ scanningRef.current = true;
83
+ setDevices([]);
84
+ selectDevice(null);
85
+ try {
86
+ const nextDevices = await BluetoothSdk.scan(scanModel, {
87
+ ...(timeoutMsRef.current == null ? {} : { timeoutMs: timeoutMsRef.current }),
88
+ onResults: (results) => {
89
+ if (activeScanRef.current !== scanId) {
90
+ return;
91
+ }
92
+ setDevices(dedupeDevices(results, dedupeRef.current));
93
+ },
94
+ });
95
+ const finalDevices = dedupeDevices(nextDevices, dedupeRef.current);
96
+ if (activeScanRef.current === scanId) {
97
+ setDevices(finalDevices);
98
+ setError(null);
99
+ }
100
+ return finalDevices;
101
+ }
102
+ catch (nextError) {
103
+ if (activeScanRef.current === scanId) {
104
+ setError(nextError);
105
+ onErrorRef.current?.(nextError);
106
+ }
107
+ throw nextError;
108
+ }
109
+ finally {
110
+ if (activeScanRef.current === scanId) {
111
+ setScanning(false);
112
+ scanningRef.current = false;
113
+ }
114
+ }
115
+ }
116
+ async function stopScan() {
117
+ activeScanRef.current += 1;
118
+ setScanning(false);
119
+ scanningRef.current = false;
120
+ await BluetoothSdk.stopScan();
121
+ }
122
+ return {
123
+ clearResults,
124
+ devices,
125
+ error,
126
+ model,
127
+ scanning,
128
+ selectedDevice,
129
+ selectDevice,
130
+ setModel,
131
+ startScan,
132
+ stopScan,
133
+ };
134
+ }
135
+ //# sourceMappingURL=useBluetoothScan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBluetoothScan.js","sourceRoot":"","sources":["../../src/react/useBluetoothScan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AAEjD,OAAO,YAAY,EAAE,EAAC,YAAY,EAAC,MAAM,UAAU,CAAA;AAyBnD,SAAS,SAAS,CAAC,MAAc,EAAE,MAA2B;IAC5D,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;QACjC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAA;IACvB,CAAC;IACD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE,CAAA;IACzC,CAAC;IACD,OAAO,MAAM,CAAC,EAAE,CAAA;AAClB,CAAC;AAED,SAAS,aAAa,CAAC,OAAiB,EAAE,MAA2B;IACnE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,WAAW,GAAa,EAAE,CAAA;IAEhC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;QACzB,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACrC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAClB,OAAM;QACR,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACb,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC1B,CAAC,CAAC,CAAA;IAEF,OAAO,WAAW,CAAA;AACpB,CAAC;AAED,SAAS,SAAS,CAAC,OAAiB,EAAE,cAA6B,EAAE,MAA2B;IAC9F,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO,IAAI,CAAA;IACb,CAAC;IACD,MAAM,WAAW,GAAG,SAAS,CAAC,cAAc,EAAE,MAAM,CAAC,CAAA;IACrD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,WAAW,CAAC,CAAA;AAC5E,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,UAAmC,EAAE;IACpE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAW,EAAE,CAAC,CAAA;IACpD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAiB,IAAI,CAAC,CAAA;IACxD,MAAM,CAAC,KAAK,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAc,OAAO,CAAC,KAAK,IAAI,YAAY,CAAC,UAAU,CAAC,CAAA;IAC9F,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC/C,MAAM,CAAC,cAAc,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAA;IACpE,MAAM,aAAa,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;IAC/B,MAAM,SAAS,GAAG,MAAM,CAAsB,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,CAAA;IACrE,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1C,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IACjC,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAE9C,SAAS,CAAC,GAAG,EAAE;QACb,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,CAAA;QAC1C,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAA;QACpC,YAAY,CAAC,OAAO,GAAG,OAAO,CAAC,SAAS,CAAA;IAC1C,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAA;IAExD,SAAS,CAAC,GAAG,EAAE;QACb,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAA;IAChC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAEd,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3D,YAAY,CAAC,IAAI,CAAC,CAAA;QACpB,CAAC;IACH,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAA;IAE7B,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;gBACzB,OAAM;YACR,CAAC;YACD,aAAa,CAAC,OAAO,IAAI,CAAC,CAAA;YAC1B,KAAK,YAAY,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QACrD,CAAC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,SAAS,YAAY;QACnB,UAAU,CAAC,EAAE,CAAC,CAAA;QACd,YAAY,CAAC,IAAI,CAAC,CAAA;IACpB,CAAC;IAED,SAAS,QAAQ,CAAC,SAAsB;QACtC,aAAa,CAAC,SAAS,CAAC,CAAA;QACxB,YAAY,EAAE,CAAA;IAChB,CAAC;IAED,KAAK,UAAU,SAAS,CAAC,SAAuB;QAC9C,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,GAAG,CAAC,CAAA;QACxC,aAAa,CAAC,OAAO,GAAG,MAAM,CAAA;QAC9B,MAAM,SAAS,GAAG,SAAS,IAAI,KAAK,CAAA;QACpC,IAAI,SAAS,IAAI,SAAS,KAAK,KAAK,EAAE,CAAC;YACrC,aAAa,CAAC,SAAS,CAAC,CAAA;QAC1B,CAAC;QAED,QAAQ,CAAC,IAAI,CAAC,CAAA;QACd,WAAW,CAAC,IAAI,CAAC,CAAA;QACjB,WAAW,CAAC,OAAO,GAAG,IAAI,CAAA;QAC1B,UAAU,CAAC,EAAE,CAAC,CAAA;QACd,YAAY,CAAC,IAAI,CAAC,CAAA;QAElB,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE;gBACrD,GAAG,CAAC,YAAY,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAC,SAAS,EAAE,YAAY,CAAC,OAAO,EAAC,CAAC;gBAC1E,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE;oBACrB,IAAI,aAAa,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;wBACrC,OAAM;oBACR,CAAC;oBACD,UAAU,CAAC,aAAa,CAAC,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;gBACvD,CAAC;aACF,CAAC,CAAA;YACF,MAAM,YAAY,GAAG,aAAa,CAAC,WAAW,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;YAClE,IAAI,aAAa,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;gBACrC,UAAU,CAAC,YAAY,CAAC,CAAA;gBACxB,QAAQ,CAAC,IAAI,CAAC,CAAA;YAChB,CAAC;YACD,OAAO,YAAY,CAAA;QACrB,CAAC;QAAC,OAAO,SAAS,EAAE,CAAC;YACnB,IAAI,aAAa,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;gBACrC,QAAQ,CAAC,SAAS,CAAC,CAAA;gBACnB,UAAU,CAAC,OAAO,EAAE,CAAC,SAAS,CAAC,CAAA;YACjC,CAAC;YACD,MAAM,SAAS,CAAA;QACjB,CAAC;gBAAS,CAAC;YACT,IAAI,aAAa,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;gBACrC,WAAW,CAAC,KAAK,CAAC,CAAA;gBAClB,WAAW,CAAC,OAAO,GAAG,KAAK,CAAA;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,UAAU,QAAQ;QACrB,aAAa,CAAC,OAAO,IAAI,CAAC,CAAA;QAC1B,WAAW,CAAC,KAAK,CAAC,CAAA;QAClB,WAAW,CAAC,OAAO,GAAG,KAAK,CAAA;QAC3B,MAAM,YAAY,CAAC,QAAQ,EAAE,CAAA;IAC/B,CAAC;IAED,OAAO;QACL,YAAY;QACZ,OAAO;QACP,KAAK;QACL,KAAK;QACL,QAAQ;QACR,cAAc;QACd,YAAY;QACZ,QAAQ;QACR,SAAS;QACT,QAAQ;KACT,CAAA;AACH,CAAC","sourcesContent":["import {useEffect, useRef, useState} from \"react\"\n\nimport BluetoothSdk, {DeviceModels} from \"../index\"\nimport type {Device, DeviceModel} from \"../BluetoothSdk.types\"\n\nexport type BluetoothScanDedupe = \"id\" | \"name\" | ((device: Device) => string)\n\nexport type UseBluetoothScanOptions = {\n dedupe?: BluetoothScanDedupe\n model?: DeviceModel\n onError?: (error: unknown) => void\n timeoutMs?: number\n}\n\nexport type BluetoothScanHookResult = {\n clearResults: () => void\n devices: Device[]\n error: unknown | null\n model: DeviceModel\n scanning: boolean\n selectedDevice: Device | null\n selectDevice: (device: Device | null) => void\n setModel: (model: DeviceModel) => void\n startScan: (model?: DeviceModel) => Promise<Device[]>\n stopScan: () => Promise<void>\n}\n\nfunction dedupeKey(device: Device, dedupe: BluetoothScanDedupe): string {\n if (typeof dedupe === \"function\") {\n return dedupe(device)\n }\n if (dedupe === \"name\") {\n return `${device.model}:${device.name}`\n }\n return device.id\n}\n\nfunction dedupeDevices(devices: Device[], dedupe: BluetoothScanDedupe): Device[] {\n const seen = new Set<string>()\n const nextDevices: Device[] = []\n\n devices.forEach((device) => {\n const key = dedupeKey(device, dedupe)\n if (seen.has(key)) {\n return\n }\n seen.add(key)\n nextDevices.push(device)\n })\n\n return nextDevices\n}\n\nfunction hasDevice(devices: Device[], selectedDevice: Device | null, dedupe: BluetoothScanDedupe): boolean {\n if (!selectedDevice) {\n return true\n }\n const selectedKey = dedupeKey(selectedDevice, dedupe)\n return devices.some((device) => dedupeKey(device, dedupe) === selectedKey)\n}\n\nexport function useBluetoothScan(options: UseBluetoothScanOptions = {}): BluetoothScanHookResult {\n const [devices, setDevices] = useState<Device[]>([])\n const [error, setError] = useState<unknown | null>(null)\n const [model, setModelState] = useState<DeviceModel>(options.model ?? DeviceModels.MentraLive)\n const [scanning, setScanning] = useState(false)\n const [selectedDevice, selectDevice] = useState<Device | null>(null)\n const activeScanRef = useRef(0)\n const dedupeRef = useRef<BluetoothScanDedupe>(options.dedupe ?? \"id\")\n const onErrorRef = useRef(options.onError)\n const scanningRef = useRef(false)\n const timeoutMsRef = useRef(options.timeoutMs)\n\n useEffect(() => {\n dedupeRef.current = options.dedupe ?? \"id\"\n onErrorRef.current = options.onError\n timeoutMsRef.current = options.timeoutMs\n }, [options.dedupe, options.onError, options.timeoutMs])\n\n useEffect(() => {\n scanningRef.current = scanning\n }, [scanning])\n\n useEffect(() => {\n if (!hasDevice(devices, selectedDevice, dedupeRef.current)) {\n selectDevice(null)\n }\n }, [devices, selectedDevice])\n\n useEffect(() => {\n return () => {\n if (!scanningRef.current) {\n return\n }\n activeScanRef.current += 1\n void BluetoothSdk.stopScan().catch(() => undefined)\n }\n }, [])\n\n function clearResults() {\n setDevices([])\n selectDevice(null)\n }\n\n function setModel(nextModel: DeviceModel) {\n setModelState(nextModel)\n clearResults()\n }\n\n async function startScan(nextModel?: DeviceModel): Promise<Device[]> {\n const scanId = activeScanRef.current + 1\n activeScanRef.current = scanId\n const scanModel = nextModel ?? model\n if (nextModel && nextModel !== model) {\n setModelState(nextModel)\n }\n\n setError(null)\n setScanning(true)\n scanningRef.current = true\n setDevices([])\n selectDevice(null)\n\n try {\n const nextDevices = await BluetoothSdk.scan(scanModel, {\n ...(timeoutMsRef.current == null ? {} : {timeoutMs: timeoutMsRef.current}),\n onResults: (results) => {\n if (activeScanRef.current !== scanId) {\n return\n }\n setDevices(dedupeDevices(results, dedupeRef.current))\n },\n })\n const finalDevices = dedupeDevices(nextDevices, dedupeRef.current)\n if (activeScanRef.current === scanId) {\n setDevices(finalDevices)\n setError(null)\n }\n return finalDevices\n } catch (nextError) {\n if (activeScanRef.current === scanId) {\n setError(nextError)\n onErrorRef.current?.(nextError)\n }\n throw nextError\n } finally {\n if (activeScanRef.current === scanId) {\n setScanning(false)\n scanningRef.current = false\n }\n }\n }\n\n async function stopScan() {\n activeScanRef.current += 1\n setScanning(false)\n scanningRef.current = false\n await BluetoothSdk.stopScan()\n }\n\n return {\n clearResults,\n devices,\n error,\n model,\n scanning,\n selectedDevice,\n selectDevice,\n setModel,\n startScan,\n stopScan,\n }\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { PublicBluetoothStatus, PublicGlassesStatus } from "../BluetoothSdk.types";
2
+ export type UseBluetoothStatusOptions = {
3
+ enabled?: boolean;
4
+ onError?: (error: unknown) => void;
5
+ };
6
+ export type BluetoothStatusHookResult = {
7
+ bluetoothStatus: Partial<PublicBluetoothStatus>;
8
+ connected: boolean;
9
+ error: unknown | null;
10
+ glassesStatus: Partial<PublicGlassesStatus>;
11
+ loading: boolean;
12
+ ready: boolean;
13
+ refresh: () => Promise<void>;
14
+ };
15
+ export declare function useBluetoothStatus(options?: UseBluetoothStatusOptions): BluetoothStatusHookResult;
16
+ //# sourceMappingURL=useBluetoothStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBluetoothStatus.d.ts","sourceRoot":"","sources":["../../src/react/useBluetoothStatus.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAIV,qBAAqB,EACrB,mBAAmB,EAGpB,MAAM,uBAAuB,CAAA;AAE9B,MAAM,MAAM,yBAAyB,GAAG;IACtC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG;IACtC,eAAe,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAA;IAC/C,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;IACrB,aAAa,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAA;IAC3C,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B,CAAA;AAiCD,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,yBAA8B,GAAG,yBAAyB,CAmHrG"}
@@ -0,0 +1,137 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import BluetoothSdk from "../index";
3
+ import { createDisconnectedGlassesStatus, isConnectedGlassesConnectionStatus, isReadyGlassesConnectionStatus, } from "../BluetoothSdk.types";
4
+ function wifiStatusFromEvent(event) {
5
+ switch (event.state) {
6
+ case "connected":
7
+ return { state: "connected", ssid: event.ssid, localIp: event.localIp };
8
+ case "disconnected":
9
+ return { state: "disconnected" };
10
+ }
11
+ }
12
+ function hotspotStatusFromEvent(event) {
13
+ if (event.state === "enabled") {
14
+ return {
15
+ state: "enabled",
16
+ ssid: event.ssid,
17
+ password: event.password,
18
+ localIp: event.localIp,
19
+ };
20
+ }
21
+ return { state: event.state };
22
+ }
23
+ function mergeGlassesStatus(current, changed) {
24
+ if (changed.connection?.state === "disconnected") {
25
+ return { ...createDisconnectedGlassesStatus(), ...changed };
26
+ }
27
+ return { ...current, ...changed };
28
+ }
29
+ export function useBluetoothStatus(options = {}) {
30
+ const enabled = options.enabled ?? true;
31
+ const onErrorRef = useRef(options.onError);
32
+ const [bluetoothStatus, setBluetoothStatus] = useState({});
33
+ const [error, setError] = useState(null);
34
+ const [glassesStatus, setGlassesStatus] = useState(() => createDisconnectedGlassesStatus());
35
+ const [loading, setLoading] = useState(enabled);
36
+ useEffect(() => {
37
+ onErrorRef.current = options.onError;
38
+ }, [options.onError]);
39
+ async function refresh() {
40
+ setLoading(true);
41
+ try {
42
+ const [nextGlassesStatus, nextBluetoothStatus] = await Promise.all([
43
+ BluetoothSdk.getGlassesStatus(),
44
+ BluetoothSdk.getBluetoothStatus(),
45
+ ]);
46
+ setGlassesStatus(nextGlassesStatus);
47
+ setBluetoothStatus(nextBluetoothStatus);
48
+ setError(null);
49
+ }
50
+ catch (nextError) {
51
+ setError(nextError);
52
+ onErrorRef.current?.(nextError);
53
+ }
54
+ finally {
55
+ setLoading(false);
56
+ }
57
+ }
58
+ useEffect(() => {
59
+ if (!enabled) {
60
+ setLoading(false);
61
+ return undefined;
62
+ }
63
+ let mounted = true;
64
+ const loadInitialStatus = async () => {
65
+ setLoading(true);
66
+ try {
67
+ const [nextGlassesStatus, nextBluetoothStatus] = await Promise.all([
68
+ BluetoothSdk.getGlassesStatus(),
69
+ BluetoothSdk.getBluetoothStatus(),
70
+ ]);
71
+ if (!mounted) {
72
+ return;
73
+ }
74
+ setGlassesStatus(nextGlassesStatus);
75
+ setBluetoothStatus(nextBluetoothStatus);
76
+ setError(null);
77
+ }
78
+ catch (nextError) {
79
+ if (!mounted) {
80
+ return;
81
+ }
82
+ setError(nextError);
83
+ onErrorRef.current?.(nextError);
84
+ }
85
+ finally {
86
+ if (mounted) {
87
+ setLoading(false);
88
+ }
89
+ }
90
+ };
91
+ void loadInitialStatus();
92
+ const removeGlasses = BluetoothSdk.onGlassesStatus((changed) => {
93
+ setGlassesStatus((current) => mergeGlassesStatus(current, changed));
94
+ });
95
+ const removeBluetooth = BluetoothSdk.onBluetoothStatus((changed) => {
96
+ setBluetoothStatus((current) => ({ ...current, ...changed }));
97
+ });
98
+ const batterySubscription = BluetoothSdk.addListener("battery_status", (event) => {
99
+ setGlassesStatus((current) => ({
100
+ ...current,
101
+ batteryLevel: event.level,
102
+ charging: event.charging,
103
+ }));
104
+ });
105
+ const wifiSubscription = BluetoothSdk.addListener("wifi_status_change", (event) => {
106
+ setGlassesStatus((current) => ({ ...current, wifi: wifiStatusFromEvent(event) }));
107
+ });
108
+ const hotspotSubscription = BluetoothSdk.addListener("hotspot_status_change", (event) => {
109
+ setGlassesStatus((current) => ({ ...current, hotspot: hotspotStatusFromEvent(event) }));
110
+ });
111
+ const hotspotErrorSubscription = BluetoothSdk.addListener("hotspot_error", () => {
112
+ setGlassesStatus((current) => ({ ...current, hotspot: { state: "disabled" } }));
113
+ });
114
+ return () => {
115
+ mounted = false;
116
+ removeGlasses();
117
+ removeBluetooth();
118
+ batterySubscription.remove();
119
+ wifiSubscription.remove();
120
+ hotspotSubscription.remove();
121
+ hotspotErrorSubscription.remove();
122
+ };
123
+ }, [enabled]);
124
+ const connection = glassesStatus.connection;
125
+ const connected = connection ? isConnectedGlassesConnectionStatus(connection) : false;
126
+ const ready = connection ? isReadyGlassesConnectionStatus(connection) : false;
127
+ return {
128
+ bluetoothStatus,
129
+ connected,
130
+ error,
131
+ glassesStatus,
132
+ loading,
133
+ ready,
134
+ refresh,
135
+ };
136
+ }
137
+ //# sourceMappingURL=useBluetoothStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBluetoothStatus.js","sourceRoot":"","sources":["../../src/react/useBluetoothStatus.ts"],"names":[],"mappings":"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","sourcesContent":["import {useEffect, useRef, useState} from \"react\"\n\nimport BluetoothSdk from \"../index\"\nimport {\n createDisconnectedGlassesStatus,\n isConnectedGlassesConnectionStatus,\n isReadyGlassesConnectionStatus,\n} from \"../BluetoothSdk.types\"\nimport type {\n BatteryStatusEvent,\n HotspotStatus,\n HotspotStatusChangeEvent,\n PublicBluetoothStatus,\n PublicGlassesStatus,\n WifiStatus,\n WifiStatusChangeEvent,\n} from \"../BluetoothSdk.types\"\n\nexport type UseBluetoothStatusOptions = {\n enabled?: boolean\n onError?: (error: unknown) => void\n}\n\nexport type BluetoothStatusHookResult = {\n bluetoothStatus: Partial<PublicBluetoothStatus>\n connected: boolean\n error: unknown | null\n glassesStatus: Partial<PublicGlassesStatus>\n loading: boolean\n ready: boolean\n refresh: () => Promise<void>\n}\n\nfunction wifiStatusFromEvent(event: WifiStatusChangeEvent): WifiStatus {\n switch (event.state) {\n case \"connected\":\n return {state: \"connected\", ssid: event.ssid, localIp: event.localIp}\n case \"disconnected\":\n return {state: \"disconnected\"}\n }\n}\n\nfunction hotspotStatusFromEvent(event: HotspotStatusChangeEvent): HotspotStatus {\n if (event.state === \"enabled\") {\n return {\n state: \"enabled\",\n ssid: event.ssid,\n password: event.password,\n localIp: event.localIp,\n }\n }\n return {state: event.state}\n}\n\nfunction mergeGlassesStatus(\n current: Partial<PublicGlassesStatus>,\n changed: Partial<PublicGlassesStatus>,\n): Partial<PublicGlassesStatus> {\n if (changed.connection?.state === \"disconnected\") {\n return {...createDisconnectedGlassesStatus(), ...changed}\n }\n return {...current, ...changed}\n}\n\nexport function useBluetoothStatus(options: UseBluetoothStatusOptions = {}): BluetoothStatusHookResult {\n const enabled = options.enabled ?? true\n const onErrorRef = useRef(options.onError)\n const [bluetoothStatus, setBluetoothStatus] = useState<Partial<PublicBluetoothStatus>>({})\n const [error, setError] = useState<unknown | null>(null)\n const [glassesStatus, setGlassesStatus] = useState<Partial<PublicGlassesStatus>>(() =>\n createDisconnectedGlassesStatus(),\n )\n const [loading, setLoading] = useState(enabled)\n\n useEffect(() => {\n onErrorRef.current = options.onError\n }, [options.onError])\n\n async function refresh() {\n setLoading(true)\n try {\n const [nextGlassesStatus, nextBluetoothStatus] = await Promise.all([\n BluetoothSdk.getGlassesStatus(),\n BluetoothSdk.getBluetoothStatus(),\n ])\n setGlassesStatus(nextGlassesStatus)\n setBluetoothStatus(nextBluetoothStatus)\n setError(null)\n } catch (nextError) {\n setError(nextError)\n onErrorRef.current?.(nextError)\n } finally {\n setLoading(false)\n }\n }\n\n useEffect(() => {\n if (!enabled) {\n setLoading(false)\n return undefined\n }\n\n let mounted = true\n\n const loadInitialStatus = async () => {\n setLoading(true)\n try {\n const [nextGlassesStatus, nextBluetoothStatus] = await Promise.all([\n BluetoothSdk.getGlassesStatus(),\n BluetoothSdk.getBluetoothStatus(),\n ])\n if (!mounted) {\n return\n }\n setGlassesStatus(nextGlassesStatus)\n setBluetoothStatus(nextBluetoothStatus)\n setError(null)\n } catch (nextError) {\n if (!mounted) {\n return\n }\n setError(nextError)\n onErrorRef.current?.(nextError)\n } finally {\n if (mounted) {\n setLoading(false)\n }\n }\n }\n\n void loadInitialStatus()\n\n const removeGlasses = BluetoothSdk.onGlassesStatus((changed) => {\n setGlassesStatus((current) => mergeGlassesStatus(current, changed))\n })\n const removeBluetooth = BluetoothSdk.onBluetoothStatus((changed) => {\n setBluetoothStatus((current) => ({...current, ...changed}))\n })\n const batterySubscription = BluetoothSdk.addListener(\"battery_status\", (event: BatteryStatusEvent) => {\n setGlassesStatus((current) => ({\n ...current,\n batteryLevel: event.level,\n charging: event.charging,\n }))\n })\n const wifiSubscription = BluetoothSdk.addListener(\"wifi_status_change\", (event) => {\n setGlassesStatus((current) => ({...current, wifi: wifiStatusFromEvent(event)}))\n })\n const hotspotSubscription = BluetoothSdk.addListener(\"hotspot_status_change\", (event) => {\n setGlassesStatus((current) => ({...current, hotspot: hotspotStatusFromEvent(event)}))\n })\n const hotspotErrorSubscription = BluetoothSdk.addListener(\"hotspot_error\", () => {\n setGlassesStatus((current) => ({...current, hotspot: {state: \"disabled\"}}))\n })\n\n return () => {\n mounted = false\n removeGlasses()\n removeBluetooth()\n batterySubscription.remove()\n wifiSubscription.remove()\n hotspotSubscription.remove()\n hotspotErrorSubscription.remove()\n }\n }, [enabled])\n\n const connection = glassesStatus.connection\n const connected = connection ? isConnectedGlassesConnectionStatus(connection) : false\n const ready = connection ? isReadyGlassesConnectionStatus(connection) : false\n\n return {\n bluetoothStatus,\n connected,\n error,\n glassesStatus,\n loading,\n ready,\n refresh,\n }\n}\n"]}
@@ -0,0 +1,29 @@
1
+ import type { ConnectOptions, Device, DeviceModel } from "../BluetoothSdk.types";
2
+ import { type BluetoothScanHookResult } from "./useBluetoothScan";
3
+ import { type BluetoothStatusHookResult } from "./useBluetoothStatus";
4
+ export type DefaultDeviceStorage = {
5
+ load: () => Promise<Device | null>;
6
+ save: (device: Device | null) => Promise<void>;
7
+ };
8
+ export type GlassesConnectionAction = "idle" | "scanning" | "connecting" | "disconnecting" | "forgetting";
9
+ export type UseGlassesConnectionOptions = {
10
+ autoConnectDefault?: boolean;
11
+ defaultDeviceStorage?: DefaultDeviceStorage;
12
+ onError?: (error: unknown) => void;
13
+ scanModel?: DeviceModel;
14
+ scanTimeoutMs?: number;
15
+ };
16
+ export type GlassesConnectionHookResult = BluetoothStatusHookResult & {
17
+ action: GlassesConnectionAction;
18
+ busy: boolean;
19
+ clearDefaultDevice: () => Promise<void>;
20
+ connect: (device?: Device, options?: ConnectOptions) => Promise<void>;
21
+ connectDefault: (options?: ConnectOptions) => Promise<void>;
22
+ defaultDevice: Device | null;
23
+ disconnect: () => Promise<void>;
24
+ forget: () => Promise<void>;
25
+ scan: BluetoothScanHookResult;
26
+ setDefaultDevice: (device: Device | null) => Promise<void>;
27
+ };
28
+ export declare function useGlassesConnection(options?: UseGlassesConnectionOptions): GlassesConnectionHookResult;
29
+ //# sourceMappingURL=useGlassesConnection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGlassesConnection.d.ts","sourceRoot":"","sources":["../../src/react/useGlassesConnection.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAC,MAAM,uBAAuB,CAAA;AAE9E,OAAO,EAAmB,KAAK,uBAAuB,EAAC,MAAM,oBAAoB,CAAA;AACjF,OAAO,EAAqB,KAAK,yBAAyB,EAAC,MAAM,sBAAsB,CAAA;AAEvF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAClC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC/C,CAAA;AAED,MAAM,MAAM,uBAAuB,GAC/B,MAAM,GACN,UAAU,GACV,YAAY,GACZ,eAAe,GACf,YAAY,CAAA;AAEhB,MAAM,MAAM,2BAA2B,GAAG;IACxC,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,oBAAoB,CAAC,EAAE,oBAAoB,CAAA;IAC3C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAClC,SAAS,CAAC,EAAE,WAAW,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG,yBAAyB,GAAG;IACpE,MAAM,EAAE,uBAAuB,CAAA;IAC/B,IAAI,EAAE,OAAO,CAAA;IACb,kBAAkB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACrE,cAAc,EAAE,CAAC,OAAO,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,IAAI,EAAE,uBAAuB,CAAA;IAC7B,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3D,CAAA;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,2BAAgC,GACxC,2BAA2B,CAsJ7B"}