walletpair-sdk 1.0.2 → 1.0.5
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 +13 -0
- package/dist/ble/framing.d.ts.map +1 -1
- package/dist/ble/framing.js +2 -2
- package/dist/ble/framing.js.map +1 -1
- package/dist/ble/index.d.ts +2 -2
- package/dist/ble/index.d.ts.map +1 -1
- package/dist/ble/index.js +2 -2
- package/dist/ble/index.js.map +1 -1
- package/dist/ble/web-ble-transport.d.ts +1 -1
- package/dist/ble/web-ble-transport.d.ts.map +1 -1
- package/dist/ble/web-ble-transport.js +23 -12
- package/dist/ble/web-ble-transport.js.map +1 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +29 -12
- package/dist/crypto.js.map +1 -1
- package/dist/dapp-session.d.ts.map +1 -1
- package/dist/dapp-session.js +15 -5
- package/dist/dapp-session.js.map +1 -1
- package/dist/emitter.d.ts +1 -3
- package/dist/emitter.d.ts.map +1 -1
- package/dist/emitter.js +4 -2
- package/dist/emitter.js.map +1 -1
- package/dist/evm/eip1193.d.ts +2 -2
- package/dist/evm/eip1193.d.ts.map +1 -1
- package/dist/evm/eip1193.js +32 -18
- package/dist/evm/eip1193.js.map +1 -1
- package/dist/evm/index.d.ts +2 -2
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js.map +1 -1
- package/dist/wallet-session.d.ts.map +1 -1
- package/dist/wallet-session.js +4 -3
- package/dist/wallet-session.js.map +1 -1
- package/dist/ws-transport.d.ts +3 -2
- package/dist/ws-transport.d.ts.map +1 -1
- package/dist/ws-transport.js +13 -4
- package/dist/ws-transport.js.map +1 -1
- package/package.json +20 -1
- package/src/__tests__/adversarial/crypto-attacks.test.ts +240 -233
- package/src/__tests__/adversarial/malicious-dapp.test.ts +228 -194
- package/src/__tests__/adversarial/malicious-relay.test.ts +292 -220
- package/src/__tests__/adversarial/malicious-wallet.test.ts +246 -180
- package/src/__tests__/spec-compliance/canonical-json.test.ts +105 -105
- package/src/__tests__/spec-compliance/crypto-vectors.test.ts +149 -154
- package/src/__tests__/spec-compliance/message-format.test.ts +180 -151
- package/src/__tests__/spec-compliance/sequence-numbers.test.ts +142 -149
- package/src/__tests__/spec-compliance/state-machine.test.ts +203 -180
- package/src/ble/framing.test.ts +122 -114
- package/src/ble/framing.ts +48 -51
- package/src/ble/index.ts +7 -7
- package/src/ble/web-ble-transport.test.ts +93 -84
- package/src/ble/web-ble-transport.ts +70 -57
- package/src/ble/web-bluetooth.d.ts +19 -19
- package/src/canonical-json.test.ts +301 -285
- package/src/crypto-directional.test.ts +155 -129
- package/src/crypto-hardening.test.ts +292 -283
- package/src/crypto.test.ts +364 -346
- package/src/crypto.ts +185 -175
- package/src/dapp-session.test.ts +522 -385
- package/src/dapp-session.ts +17 -11
- package/src/emitter.test.ts +122 -122
- package/src/emitter.ts +20 -18
- package/src/evm/eip1193.test.ts +283 -205
- package/src/evm/eip1193.ts +162 -138
- package/src/evm/index.ts +5 -5
- package/src/evm/wagmi.test.ts +1 -1
- package/src/integration.test.ts +329 -201
- package/src/security.test.ts +331 -238
- package/src/sequence-validation.test.ts +6 -9
- package/src/test-helpers.ts +102 -78
- package/src/types.test.ts +45 -50
- package/src/wallet-session.test.ts +611 -383
- package/src/wallet-session.ts +7 -9
- package/src/ws-transport.test.ts +141 -139
- package/src/ws-transport.ts +52 -41
|
@@ -1,42 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import type { ProtocolMessage } from '../types.js'
|
|
3
|
+
import { isWebBleSupported, WebBleCentralTransport } from './web-ble-transport.js'
|
|
3
4
|
|
|
4
5
|
// ── Mock Web Bluetooth API ──────────────────────────────────────────
|
|
5
6
|
|
|
6
7
|
function createMockCharacteristic() {
|
|
7
|
-
const listeners = new Map<string,
|
|
8
|
+
const listeners = new Map<string, (...args: unknown[]) => unknown>()
|
|
8
9
|
return {
|
|
9
10
|
writeValueWithoutResponse: vi.fn().mockResolvedValue(undefined),
|
|
10
11
|
startNotifications: vi.fn().mockResolvedValue(undefined),
|
|
11
|
-
addEventListener: vi.fn((evt: string, fn:
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
addEventListener: vi.fn((evt: string, fn: (...args: unknown[]) => unknown) => {
|
|
13
|
+
listeners.set(evt, fn)
|
|
14
|
+
}),
|
|
15
|
+
removeEventListener: vi.fn((evt: string) => {
|
|
16
|
+
listeners.delete(evt)
|
|
17
|
+
}),
|
|
18
|
+
_fire: (evt: string, data: unknown) => listeners.get(evt)?.(data),
|
|
14
19
|
value: null as DataView | null,
|
|
15
|
-
}
|
|
20
|
+
}
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
function createMockBleDevice() {
|
|
19
|
-
const listeners = new Map<string,
|
|
20
|
-
const writeChar = createMockCharacteristic()
|
|
21
|
-
const notifyChar = createMockCharacteristic()
|
|
24
|
+
const listeners = new Map<string, (...args: unknown[]) => unknown>()
|
|
25
|
+
const writeChar = createMockCharacteristic()
|
|
26
|
+
const notifyChar = createMockCharacteristic()
|
|
22
27
|
|
|
23
28
|
const service = {
|
|
24
29
|
getCharacteristic: vi.fn((uuid: string) => {
|
|
25
30
|
// BLE_WRITE_CHAR_UUID = 'e3a10002-...'
|
|
26
|
-
if (uuid.includes('10002')) return Promise.resolve(writeChar)
|
|
27
|
-
if (uuid.includes('10003')) return Promise.resolve(notifyChar)
|
|
28
|
-
return Promise.reject(new Error('unknown characteristic'))
|
|
31
|
+
if (uuid.includes('10002')) return Promise.resolve(writeChar)
|
|
32
|
+
if (uuid.includes('10003')) return Promise.resolve(notifyChar)
|
|
33
|
+
return Promise.reject(new Error('unknown characteristic'))
|
|
29
34
|
}),
|
|
30
|
-
}
|
|
35
|
+
}
|
|
31
36
|
|
|
32
37
|
const server = {
|
|
33
38
|
connect: vi.fn().mockResolvedValue(undefined),
|
|
34
39
|
getPrimaryService: vi.fn().mockResolvedValue(service),
|
|
35
40
|
connected: true,
|
|
36
|
-
}
|
|
41
|
+
}
|
|
37
42
|
|
|
38
43
|
// Make connect() return the server with getPrimaryService
|
|
39
|
-
server.connect.mockResolvedValue(server)
|
|
44
|
+
server.connect.mockResolvedValue(server)
|
|
40
45
|
|
|
41
46
|
const device = {
|
|
42
47
|
gatt: {
|
|
@@ -44,15 +49,19 @@ function createMockBleDevice() {
|
|
|
44
49
|
connected: true,
|
|
45
50
|
disconnect: vi.fn(),
|
|
46
51
|
},
|
|
47
|
-
addEventListener: vi.fn((evt: string, fn:
|
|
48
|
-
|
|
52
|
+
addEventListener: vi.fn((evt: string, fn: (...args: unknown[]) => unknown) => {
|
|
53
|
+
listeners.set(evt, fn)
|
|
54
|
+
}),
|
|
55
|
+
removeEventListener: vi.fn((evt: string) => {
|
|
56
|
+
listeners.delete(evt)
|
|
57
|
+
}),
|
|
49
58
|
_fireDisconnect: () => listeners.get('gattserverdisconnected')?.({} as Event),
|
|
50
|
-
}
|
|
59
|
+
}
|
|
51
60
|
|
|
52
|
-
return { device, writeChar, notifyChar, server }
|
|
61
|
+
return { device, writeChar, notifyChar, server }
|
|
53
62
|
}
|
|
54
63
|
|
|
55
|
-
function mockNavigatorBluetooth(device:
|
|
64
|
+
function mockNavigatorBluetooth(device: unknown) {
|
|
56
65
|
Object.defineProperty(globalThis, 'navigator', {
|
|
57
66
|
value: {
|
|
58
67
|
bluetooth: {
|
|
@@ -61,7 +70,7 @@ function mockNavigatorBluetooth(device: any) {
|
|
|
61
70
|
},
|
|
62
71
|
writable: true,
|
|
63
72
|
configurable: true,
|
|
64
|
-
})
|
|
73
|
+
})
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
function clearNavigatorBluetooth() {
|
|
@@ -69,124 +78,124 @@ function clearNavigatorBluetooth() {
|
|
|
69
78
|
value: undefined,
|
|
70
79
|
writable: true,
|
|
71
80
|
configurable: true,
|
|
72
|
-
})
|
|
81
|
+
})
|
|
73
82
|
}
|
|
74
83
|
|
|
75
84
|
// ── isWebBleSupported ───────────────────────────────────────────────
|
|
76
85
|
|
|
77
86
|
describe('isWebBleSupported', () => {
|
|
78
87
|
it('returns false when navigator is undefined', () => {
|
|
79
|
-
clearNavigatorBluetooth()
|
|
80
|
-
expect(isWebBleSupported()).toBe(false)
|
|
81
|
-
})
|
|
88
|
+
clearNavigatorBluetooth()
|
|
89
|
+
expect(isWebBleSupported()).toBe(false)
|
|
90
|
+
})
|
|
82
91
|
|
|
83
92
|
it('returns false when navigator.bluetooth is missing', () => {
|
|
84
93
|
Object.defineProperty(globalThis, 'navigator', {
|
|
85
94
|
value: {},
|
|
86
95
|
writable: true,
|
|
87
96
|
configurable: true,
|
|
88
|
-
})
|
|
89
|
-
expect(isWebBleSupported()).toBe(false)
|
|
90
|
-
})
|
|
97
|
+
})
|
|
98
|
+
expect(isWebBleSupported()).toBe(false)
|
|
99
|
+
})
|
|
91
100
|
|
|
92
101
|
it('returns true when navigator.bluetooth exists', () => {
|
|
93
102
|
Object.defineProperty(globalThis, 'navigator', {
|
|
94
103
|
value: { bluetooth: {} },
|
|
95
104
|
writable: true,
|
|
96
105
|
configurable: true,
|
|
97
|
-
})
|
|
98
|
-
expect(isWebBleSupported()).toBe(true)
|
|
99
|
-
})
|
|
100
|
-
})
|
|
106
|
+
})
|
|
107
|
+
expect(isWebBleSupported()).toBe(true)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
101
110
|
|
|
102
111
|
// ── WebBleCentralTransport ──────────────────────────────────────────
|
|
103
112
|
|
|
104
113
|
describe('WebBleCentralTransport', () => {
|
|
105
|
-
let transport: WebBleCentralTransport
|
|
114
|
+
let transport: WebBleCentralTransport
|
|
106
115
|
|
|
107
116
|
beforeEach(() => {
|
|
108
|
-
transport = new WebBleCentralTransport()
|
|
109
|
-
clearNavigatorBluetooth()
|
|
110
|
-
})
|
|
117
|
+
transport = new WebBleCentralTransport()
|
|
118
|
+
clearNavigatorBluetooth()
|
|
119
|
+
})
|
|
111
120
|
|
|
112
121
|
it('starts in disconnected state', () => {
|
|
113
|
-
expect(transport.state).toBe('disconnected')
|
|
114
|
-
})
|
|
122
|
+
expect(transport.state).toBe('disconnected')
|
|
123
|
+
})
|
|
115
124
|
|
|
116
125
|
it('connect() throws when Web Bluetooth is not supported', async () => {
|
|
117
|
-
await expect(transport.connect()).rejects.toThrow('Web Bluetooth is not supported')
|
|
118
|
-
})
|
|
126
|
+
await expect(transport.connect()).rejects.toThrow('Web Bluetooth is not supported')
|
|
127
|
+
})
|
|
119
128
|
|
|
120
129
|
it('connect() transitions through connecting to connected', async () => {
|
|
121
|
-
const { device } = createMockBleDevice()
|
|
122
|
-
mockNavigatorBluetooth(device)
|
|
130
|
+
const { device } = createMockBleDevice()
|
|
131
|
+
mockNavigatorBluetooth(device)
|
|
123
132
|
|
|
124
|
-
const openHandler = vi.fn()
|
|
125
|
-
transport.onOpen(openHandler)
|
|
133
|
+
const openHandler = vi.fn()
|
|
134
|
+
transport.onOpen(openHandler)
|
|
126
135
|
|
|
127
|
-
await transport.connect()
|
|
136
|
+
await transport.connect()
|
|
128
137
|
|
|
129
|
-
expect(transport.state).toBe('connected')
|
|
130
|
-
expect(openHandler).toHaveBeenCalledTimes(1)
|
|
131
|
-
})
|
|
138
|
+
expect(transport.state).toBe('connected')
|
|
139
|
+
expect(openHandler).toHaveBeenCalledTimes(1)
|
|
140
|
+
})
|
|
132
141
|
|
|
133
142
|
it('connect() sets up GATT characteristics', async () => {
|
|
134
|
-
const { device, notifyChar } = createMockBleDevice()
|
|
135
|
-
mockNavigatorBluetooth(device)
|
|
143
|
+
const { device, notifyChar } = createMockBleDevice()
|
|
144
|
+
mockNavigatorBluetooth(device)
|
|
136
145
|
|
|
137
|
-
await transport.connect()
|
|
146
|
+
await transport.connect()
|
|
138
147
|
|
|
139
|
-
expect(notifyChar.startNotifications).toHaveBeenCalled()
|
|
148
|
+
expect(notifyChar.startNotifications).toHaveBeenCalled()
|
|
140
149
|
expect(notifyChar.addEventListener).toHaveBeenCalledWith(
|
|
141
150
|
'characteristicvaluechanged',
|
|
142
151
|
expect.any(Function),
|
|
143
|
-
)
|
|
144
|
-
})
|
|
152
|
+
)
|
|
153
|
+
})
|
|
145
154
|
|
|
146
155
|
it('disconnect() calls gatt.disconnect and cleans up', async () => {
|
|
147
|
-
const { device } = createMockBleDevice()
|
|
148
|
-
mockNavigatorBluetooth(device)
|
|
156
|
+
const { device } = createMockBleDevice()
|
|
157
|
+
mockNavigatorBluetooth(device)
|
|
149
158
|
|
|
150
|
-
await transport.connect()
|
|
151
|
-
transport.disconnect()
|
|
159
|
+
await transport.connect()
|
|
160
|
+
transport.disconnect()
|
|
152
161
|
|
|
153
|
-
expect(device.gatt.disconnect).toHaveBeenCalled()
|
|
154
|
-
expect(transport.state).toBe('disconnected')
|
|
155
|
-
})
|
|
162
|
+
expect(device.gatt.disconnect).toHaveBeenCalled()
|
|
163
|
+
expect(transport.state).toBe('disconnected')
|
|
164
|
+
})
|
|
156
165
|
|
|
157
166
|
it('disconnect() is safe to call when not connected', () => {
|
|
158
|
-
expect(() => transport.disconnect()).not.toThrow()
|
|
159
|
-
expect(transport.state).toBe('disconnected')
|
|
160
|
-
})
|
|
167
|
+
expect(() => transport.disconnect()).not.toThrow()
|
|
168
|
+
expect(transport.state).toBe('disconnected')
|
|
169
|
+
})
|
|
161
170
|
|
|
162
171
|
it('send() does nothing when not connected', () => {
|
|
163
|
-
transport.send({ v: 1, t: 'ping', ch: 'abc' } as
|
|
172
|
+
transport.send({ v: 1, t: 'ping', ch: 'abc' } as unknown as ProtocolMessage)
|
|
164
173
|
// Should not throw
|
|
165
|
-
})
|
|
174
|
+
})
|
|
166
175
|
|
|
167
176
|
it('onClose handler fires on GATT disconnect', async () => {
|
|
168
|
-
const { device } = createMockBleDevice()
|
|
169
|
-
mockNavigatorBluetooth(device)
|
|
177
|
+
const { device } = createMockBleDevice()
|
|
178
|
+
mockNavigatorBluetooth(device)
|
|
170
179
|
|
|
171
|
-
const closeHandler = vi.fn()
|
|
172
|
-
transport.onClose(closeHandler)
|
|
180
|
+
const closeHandler = vi.fn()
|
|
181
|
+
transport.onClose(closeHandler)
|
|
173
182
|
|
|
174
|
-
await transport.connect()
|
|
175
|
-
device._fireDisconnect()
|
|
183
|
+
await transport.connect()
|
|
184
|
+
device._fireDisconnect()
|
|
176
185
|
|
|
177
|
-
expect(closeHandler).toHaveBeenCalledTimes(1)
|
|
178
|
-
expect(transport.state).toBe('disconnected')
|
|
179
|
-
})
|
|
186
|
+
expect(closeHandler).toHaveBeenCalledTimes(1)
|
|
187
|
+
expect(transport.state).toBe('disconnected')
|
|
188
|
+
})
|
|
180
189
|
|
|
181
190
|
it('handler registration methods work', () => {
|
|
182
|
-
const msgHandler = vi.fn()
|
|
183
|
-
const closeHandler = vi.fn()
|
|
184
|
-
const openHandler = vi.fn()
|
|
191
|
+
const msgHandler = vi.fn()
|
|
192
|
+
const closeHandler = vi.fn()
|
|
193
|
+
const openHandler = vi.fn()
|
|
185
194
|
|
|
186
|
-
transport.onMessage(msgHandler)
|
|
187
|
-
transport.onClose(closeHandler)
|
|
188
|
-
transport.onOpen(openHandler)
|
|
195
|
+
transport.onMessage(msgHandler)
|
|
196
|
+
transport.onClose(closeHandler)
|
|
197
|
+
transport.onOpen(openHandler)
|
|
189
198
|
|
|
190
199
|
// No throws — handlers registered
|
|
191
|
-
})
|
|
192
|
-
})
|
|
200
|
+
})
|
|
201
|
+
})
|
|
@@ -7,110 +7,123 @@
|
|
|
7
7
|
|
|
8
8
|
/// <reference path="./web-bluetooth.d.ts" />
|
|
9
9
|
|
|
10
|
-
import type { Transport, TransportState
|
|
10
|
+
import type { ProtocolMessage, Transport, TransportState } from '../types.js'
|
|
11
11
|
import {
|
|
12
|
+
BLE_NOTIFY_CHAR_UUID,
|
|
12
13
|
BLE_SERVICE_UUID,
|
|
13
14
|
BLE_WRITE_CHAR_UUID,
|
|
14
|
-
BLE_NOTIFY_CHAR_UUID,
|
|
15
|
-
frameMessage,
|
|
16
15
|
Defragmenter,
|
|
17
|
-
|
|
16
|
+
frameMessage,
|
|
17
|
+
} from './framing.js'
|
|
18
18
|
|
|
19
19
|
export function isWebBleSupported(): boolean {
|
|
20
|
-
return typeof navigator !== 'undefined' && !!navigator.bluetooth
|
|
20
|
+
return typeof navigator !== 'undefined' && !!navigator.bluetooth
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export class WebBleCentralTransport implements Transport {
|
|
24
|
-
state: TransportState = 'disconnected'
|
|
24
|
+
state: TransportState = 'disconnected'
|
|
25
25
|
|
|
26
|
-
private device: BluetoothDevice | null = null
|
|
27
|
-
private writeChar: BluetoothRemoteGATTCharacteristic | null = null
|
|
28
|
-
private notifyChar: BluetoothRemoteGATTCharacteristic | null = null
|
|
29
|
-
private defrag = new Defragmenter()
|
|
30
|
-
private mtuPayload = 509
|
|
26
|
+
private device: BluetoothDevice | null = null
|
|
27
|
+
private writeChar: BluetoothRemoteGATTCharacteristic | null = null
|
|
28
|
+
private notifyChar: BluetoothRemoteGATTCharacteristic | null = null
|
|
29
|
+
private defrag = new Defragmenter()
|
|
30
|
+
private mtuPayload = 509
|
|
31
31
|
|
|
32
|
-
private messageHandler: ((msg: ProtocolMessage) => void) | null = null
|
|
33
|
-
private closeHandler: (() => void) | null = null
|
|
34
|
-
private openHandler: (() => void) | null = null
|
|
32
|
+
private messageHandler: ((msg: ProtocolMessage) => void) | null = null
|
|
33
|
+
private closeHandler: (() => void) | null = null
|
|
34
|
+
private openHandler: (() => void) | null = null
|
|
35
35
|
|
|
36
|
-
onMessage(handler: (msg: ProtocolMessage) => void): void {
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
onMessage(handler: (msg: ProtocolMessage) => void): void {
|
|
37
|
+
this.messageHandler = handler
|
|
38
|
+
}
|
|
39
|
+
onClose(handler: () => void): void {
|
|
40
|
+
this.closeHandler = handler
|
|
41
|
+
}
|
|
42
|
+
onOpen(handler: () => void): void {
|
|
43
|
+
this.openHandler = handler
|
|
44
|
+
}
|
|
39
45
|
|
|
40
46
|
async connect(): Promise<void> {
|
|
41
47
|
if (!isWebBleSupported()) {
|
|
42
|
-
throw new Error('Web Bluetooth is not supported in this environment')
|
|
48
|
+
throw new Error('Web Bluetooth is not supported in this environment')
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
this.state = 'connecting'
|
|
51
|
+
this.state = 'connecting'
|
|
46
52
|
|
|
47
|
-
const device = await navigator.bluetooth
|
|
48
|
-
filters: [
|
|
49
|
-
{ namePrefix: 'WalletPair' },
|
|
50
|
-
{ services: [BLE_SERVICE_UUID] },
|
|
51
|
-
],
|
|
53
|
+
const device = await navigator.bluetooth?.requestDevice({
|
|
54
|
+
filters: [{ namePrefix: 'WalletPair' }, { services: [BLE_SERVICE_UUID] }],
|
|
52
55
|
optionalServices: [BLE_SERVICE_UUID],
|
|
53
|
-
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
if (!device) throw new Error('No BLE device selected')
|
|
54
59
|
|
|
55
|
-
this.device = device
|
|
56
|
-
device.addEventListener('gattserverdisconnected', this.onDisconnect)
|
|
60
|
+
this.device = device
|
|
61
|
+
device.addEventListener('gattserverdisconnected', this.onDisconnect)
|
|
57
62
|
|
|
58
|
-
const server = await device.gatt
|
|
59
|
-
|
|
63
|
+
const server = await device.gatt?.connect()
|
|
64
|
+
if (!server) throw new Error('Failed to connect to GATT server')
|
|
65
|
+
const service = await server.getPrimaryService(BLE_SERVICE_UUID)
|
|
60
66
|
|
|
61
|
-
this.writeChar = await service.getCharacteristic(BLE_WRITE_CHAR_UUID)
|
|
62
|
-
this.notifyChar = await service.getCharacteristic(BLE_NOTIFY_CHAR_UUID)
|
|
67
|
+
this.writeChar = await service.getCharacteristic(BLE_WRITE_CHAR_UUID)
|
|
68
|
+
this.notifyChar = await service.getCharacteristic(BLE_NOTIFY_CHAR_UUID)
|
|
63
69
|
|
|
64
|
-
await this.notifyChar.startNotifications()
|
|
65
|
-
this.notifyChar.addEventListener('characteristicvaluechanged', this.onNotification)
|
|
70
|
+
await this.notifyChar.startNotifications()
|
|
71
|
+
this.notifyChar.addEventListener('characteristicvaluechanged', this.onNotification)
|
|
66
72
|
|
|
67
|
-
this.state = 'connected'
|
|
68
|
-
this.openHandler?.()
|
|
73
|
+
this.state = 'connected'
|
|
74
|
+
this.openHandler?.()
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
send(msg: ProtocolMessage): void {
|
|
72
|
-
if (!this.writeChar || this.state !== 'connected') return
|
|
73
|
-
const frames = frameMessage(JSON.stringify(msg), this.mtuPayload)
|
|
78
|
+
if (!this.writeChar || this.state !== 'connected') return
|
|
79
|
+
const frames = frameMessage(JSON.stringify(msg), this.mtuPayload)
|
|
74
80
|
// Send frames sequentially
|
|
75
|
-
let chain = Promise.resolve()
|
|
81
|
+
let chain = Promise.resolve()
|
|
76
82
|
for (const frame of frames) {
|
|
77
|
-
chain = chain.then(() =>
|
|
83
|
+
chain = chain.then(() =>
|
|
84
|
+
this.writeChar?.writeValueWithoutResponse(frame as unknown as ArrayBuffer),
|
|
85
|
+
)
|
|
78
86
|
}
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
disconnect(): void {
|
|
82
90
|
if (this.device?.gatt?.connected) {
|
|
83
|
-
this.device.gatt.disconnect()
|
|
91
|
+
this.device.gatt.disconnect()
|
|
84
92
|
}
|
|
85
|
-
this.cleanup()
|
|
93
|
+
this.cleanup()
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
private onNotification = (event: Event): void => {
|
|
89
|
-
const target = event.target as BluetoothRemoteGATTCharacteristic
|
|
90
|
-
const dv = target.value
|
|
91
|
-
|
|
92
|
-
const
|
|
97
|
+
const target = event.target as BluetoothRemoteGATTCharacteristic
|
|
98
|
+
const dv = target.value
|
|
99
|
+
if (!dv) return
|
|
100
|
+
const data = new Uint8Array(dv.buffer, dv.byteOffset, dv.byteLength)
|
|
101
|
+
const json = this.defrag.push(data)
|
|
93
102
|
if (json && this.messageHandler) {
|
|
94
|
-
try {
|
|
103
|
+
try {
|
|
104
|
+
this.messageHandler(JSON.parse(json))
|
|
105
|
+
} catch {
|
|
106
|
+
/* bad json */
|
|
107
|
+
}
|
|
95
108
|
}
|
|
96
|
-
}
|
|
109
|
+
}
|
|
97
110
|
|
|
98
111
|
private onDisconnect = (): void => {
|
|
99
|
-
this.cleanup()
|
|
100
|
-
this.closeHandler?.()
|
|
101
|
-
}
|
|
112
|
+
this.cleanup()
|
|
113
|
+
this.closeHandler?.()
|
|
114
|
+
}
|
|
102
115
|
|
|
103
116
|
private cleanup(): void {
|
|
104
117
|
if (this.notifyChar) {
|
|
105
|
-
this.notifyChar.removeEventListener('characteristicvaluechanged', this.onNotification)
|
|
118
|
+
this.notifyChar.removeEventListener('characteristicvaluechanged', this.onNotification)
|
|
106
119
|
}
|
|
107
120
|
if (this.device) {
|
|
108
|
-
this.device.removeEventListener('gattserverdisconnected', this.onDisconnect)
|
|
121
|
+
this.device.removeEventListener('gattserverdisconnected', this.onDisconnect)
|
|
109
122
|
}
|
|
110
|
-
this.device = null
|
|
111
|
-
this.writeChar = null
|
|
112
|
-
this.notifyChar = null
|
|
113
|
-
this.defrag.reset()
|
|
114
|
-
this.state = 'disconnected'
|
|
123
|
+
this.device = null
|
|
124
|
+
this.writeChar = null
|
|
125
|
+
this.notifyChar = null
|
|
126
|
+
this.defrag.reset()
|
|
127
|
+
this.state = 'disconnected'
|
|
115
128
|
}
|
|
116
129
|
}
|
|
@@ -4,44 +4,44 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
interface BluetoothDevice extends EventTarget {
|
|
7
|
-
readonly id: string
|
|
8
|
-
readonly name?: string | undefined
|
|
9
|
-
readonly gatt?: BluetoothRemoteGATTServer | undefined
|
|
7
|
+
readonly id: string
|
|
8
|
+
readonly name?: string | undefined
|
|
9
|
+
readonly gatt?: BluetoothRemoteGATTServer | undefined
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
interface BluetoothRemoteGATTServer {
|
|
13
|
-
readonly device: BluetoothDevice
|
|
14
|
-
readonly connected: boolean
|
|
15
|
-
connect(): Promise<BluetoothRemoteGATTServer
|
|
16
|
-
disconnect(): void
|
|
17
|
-
getPrimaryService(service: string): Promise<BluetoothRemoteGATTService
|
|
13
|
+
readonly device: BluetoothDevice
|
|
14
|
+
readonly connected: boolean
|
|
15
|
+
connect(): Promise<BluetoothRemoteGATTServer>
|
|
16
|
+
disconnect(): void
|
|
17
|
+
getPrimaryService(service: string): Promise<BluetoothRemoteGATTService>
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
interface BluetoothRemoteGATTService {
|
|
21
|
-
getCharacteristic(characteristic: string): Promise<BluetoothRemoteGATTCharacteristic
|
|
21
|
+
getCharacteristic(characteristic: string): Promise<BluetoothRemoteGATTCharacteristic>
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
interface BluetoothRemoteGATTCharacteristic extends EventTarget {
|
|
25
|
-
readonly value: DataView | null
|
|
26
|
-
startNotifications(): Promise<BluetoothRemoteGATTCharacteristic
|
|
27
|
-
stopNotifications(): Promise<BluetoothRemoteGATTCharacteristic
|
|
28
|
-
writeValueWithoutResponse(value: BufferSource): Promise<void
|
|
25
|
+
readonly value: DataView | null
|
|
26
|
+
startNotifications(): Promise<BluetoothRemoteGATTCharacteristic>
|
|
27
|
+
stopNotifications(): Promise<BluetoothRemoteGATTCharacteristic>
|
|
28
|
+
writeValueWithoutResponse(value: BufferSource): Promise<void>
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
interface BluetoothRequestDeviceFilter {
|
|
32
|
-
namePrefix?: string
|
|
33
|
-
services?: string[]
|
|
32
|
+
namePrefix?: string
|
|
33
|
+
services?: string[]
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
interface RequestDeviceOptions {
|
|
37
|
-
filters?: BluetoothRequestDeviceFilter[]
|
|
38
|
-
optionalServices?: string[]
|
|
37
|
+
filters?: BluetoothRequestDeviceFilter[]
|
|
38
|
+
optionalServices?: string[]
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
interface Bluetooth {
|
|
42
|
-
requestDevice(options: RequestDeviceOptions): Promise<BluetoothDevice
|
|
42
|
+
requestDevice(options: RequestDeviceOptions): Promise<BluetoothDevice>
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
interface Navigator {
|
|
46
|
-
bluetooth?: Bluetooth | undefined
|
|
46
|
+
bluetooth?: Bluetooth | undefined
|
|
47
47
|
}
|