@onekeyfe/hd-transport-react-native 1.2.0-alpha.33 → 1.2.0-alpha.35
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/dist/BleTransport.d.ts +1 -3
- package/dist/BleTransport.d.ts.map +1 -1
- package/dist/constants.d.ts +0 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +7 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +107 -158
- package/dist/transportLog.d.ts +1 -12
- package/dist/transportLog.d.ts.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/BleTransport.ts +2 -43
- package/src/__tests__/BleTransport.test.ts +41 -0
- package/src/__tests__/constants.test.ts +20 -0
- package/src/__tests__/protocolV2Link.test.ts +180 -2
- package/src/constants.ts +5 -19
- package/src/index.ts +139 -129
- package/src/transportLog.ts +1 -18
- package/src/types.ts +1 -0
package/src/BleTransport.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import { BleErrorCode } from 'react-native-ble-plx';
|
|
2
|
-
import { wait } from '@onekeyfe/hd-shared';
|
|
3
|
-
|
|
4
|
-
import { bleLogger } from './logger';
|
|
5
|
-
|
|
6
1
|
import type { Characteristic, Device, Subscription } from 'react-native-ble-plx';
|
|
7
2
|
|
|
8
|
-
const Log = bleLogger;
|
|
9
|
-
|
|
10
3
|
export default class BleTransport {
|
|
11
4
|
id: string;
|
|
12
5
|
|
|
@@ -28,10 +21,6 @@ export default class BleTransport {
|
|
|
28
21
|
|
|
29
22
|
monitorToken?: number;
|
|
30
23
|
|
|
31
|
-
static MAX_RETRIES = 5;
|
|
32
|
-
|
|
33
|
-
static RETRY_DELAY = 2000;
|
|
34
|
-
|
|
35
24
|
constructor(
|
|
36
25
|
device: Device,
|
|
37
26
|
writeCharacteristic: Characteristic,
|
|
@@ -43,37 +32,7 @@ export default class BleTransport {
|
|
|
43
32
|
this.notifyCharacteristic = notifyCharacteristic;
|
|
44
33
|
}
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
* @param data
|
|
49
|
-
* @param retryCount
|
|
50
|
-
* @returns
|
|
51
|
-
*/
|
|
52
|
-
async writeWithRetry(data: string, retryCount = BleTransport.MAX_RETRIES): Promise<void> {
|
|
53
|
-
try {
|
|
54
|
-
await this.writeCharacteristic.writeWithoutResponse(data);
|
|
55
|
-
} catch (error) {
|
|
56
|
-
Log?.debug(
|
|
57
|
-
`Write retry attempt ${BleTransport.MAX_RETRIES - retryCount + 1}, error: ${error}`
|
|
58
|
-
);
|
|
59
|
-
if (retryCount > 0) {
|
|
60
|
-
await wait(BleTransport.RETRY_DELAY);
|
|
61
|
-
if (
|
|
62
|
-
error.errorCode === BleErrorCode.DeviceDisconnected ||
|
|
63
|
-
error.errorCode === BleErrorCode.CharacteristicNotFound
|
|
64
|
-
) {
|
|
65
|
-
try {
|
|
66
|
-
await this.device.connect();
|
|
67
|
-
await this.device.discoverAllServicesAndCharacteristics();
|
|
68
|
-
} catch (e) {
|
|
69
|
-
Log?.debug(`Connect or discoverAllServicesAndCharacteristics error: ${e}`);
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
Log?.debug(`writeCharacteristic error: ${error}`);
|
|
73
|
-
}
|
|
74
|
-
return this.writeWithRetry(data, retryCount - 1);
|
|
75
|
-
}
|
|
76
|
-
throw error;
|
|
77
|
-
}
|
|
35
|
+
async writeWithRetry(data: string): Promise<void> {
|
|
36
|
+
await this.writeCharacteristic.writeWithoutResponse(data);
|
|
78
37
|
}
|
|
79
38
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { BleErrorCode } from 'react-native-ble-plx';
|
|
2
|
+
|
|
3
|
+
import BleTransport from '../BleTransport';
|
|
4
|
+
|
|
5
|
+
jest.mock(
|
|
6
|
+
'react-native-ble-plx',
|
|
7
|
+
() => ({
|
|
8
|
+
BleErrorCode: {
|
|
9
|
+
DeviceDisconnected: 205,
|
|
10
|
+
CharacteristicNotFound: 404,
|
|
11
|
+
},
|
|
12
|
+
}),
|
|
13
|
+
{ virtual: true }
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
jest.mock('@onekeyfe/hd-shared', () => ({
|
|
17
|
+
...jest.requireActual('@onekeyfe/hd-shared'),
|
|
18
|
+
wait: jest.fn(() => Promise.resolve()),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
describe('BleTransport side-effecting writes', () => {
|
|
22
|
+
test('does not reconnect or replay after the device disconnects', async () => {
|
|
23
|
+
const error = Object.assign(new Error('device disconnected'), {
|
|
24
|
+
errorCode: BleErrorCode.DeviceDisconnected,
|
|
25
|
+
});
|
|
26
|
+
const writeCharacteristic = {
|
|
27
|
+
writeWithoutResponse: jest.fn(() => Promise.reject(error)),
|
|
28
|
+
};
|
|
29
|
+
const device = {
|
|
30
|
+
id: 'classic-id',
|
|
31
|
+
connect: jest.fn(() => Promise.resolve()),
|
|
32
|
+
discoverAllServicesAndCharacteristics: jest.fn(() => Promise.resolve()),
|
|
33
|
+
};
|
|
34
|
+
const transport = new BleTransport(device as any, writeCharacteristic as any, {} as any);
|
|
35
|
+
|
|
36
|
+
await expect(transport.writeWithRetry('payload')).rejects.toBe(error);
|
|
37
|
+
|
|
38
|
+
expect(writeCharacteristic.writeWithoutResponse).toHaveBeenCalledTimes(1);
|
|
39
|
+
expect(device.connect).not.toHaveBeenCalled();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getBluetoothServiceUuids, getInfosForServiceUuid } from '../constants';
|
|
2
|
+
|
|
3
|
+
describe('React Native BLE service filters', () => {
|
|
4
|
+
test('does not scan or configure the ignored FFFD service', () => {
|
|
5
|
+
expect(getBluetoothServiceUuids()).not.toContain('fffd');
|
|
6
|
+
expect(getInfosForServiceUuid('fffd', 'classic')).toBeNull();
|
|
7
|
+
expect(getInfosForServiceUuid('0000fffd-0000-1000-8000-00805f9b34fb', 'classic')).toBeNull();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('keeps the OneKey communication service configured', () => {
|
|
11
|
+
expect(getBluetoothServiceUuids()).toContain('00000001-0000-1000-8000-00805f9b34fb');
|
|
12
|
+
expect(getInfosForServiceUuid('0001', 'classic')).toMatchObject({
|
|
13
|
+
serviceUuid: '00000001-0000-1000-8000-00805f9b34fb',
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('does not match a vendor-specific UUID containing the OneKey short key', () => {
|
|
18
|
+
expect(getInfosForServiceUuid('abcd0001-1234-5678-9012-abcdefabcdef', 'classic')).toBeNull();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -5,10 +5,11 @@ import transportPackage, {
|
|
|
5
5
|
TRANSPORT_EVENT,
|
|
6
6
|
bytesToHex,
|
|
7
7
|
} from '@onekeyfe/hd-transport';
|
|
8
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
8
|
+
import { HardwareErrorCode, createDeferred } from '@onekeyfe/hd-shared';
|
|
9
9
|
|
|
10
10
|
import ReactNativeBleTransport, {
|
|
11
11
|
configureProtocolV2BleTuning,
|
|
12
|
+
getFirmwareUploadWriteRetryType,
|
|
12
13
|
resetProtocolV2BleTuning,
|
|
13
14
|
} from '../index';
|
|
14
15
|
|
|
@@ -50,6 +51,7 @@ const { parseConfigure } = transportPackage;
|
|
|
50
51
|
const protocolV1Schema = {
|
|
51
52
|
nested: {
|
|
52
53
|
Initialize: { fields: {} },
|
|
54
|
+
GetFeatures: { fields: {} },
|
|
53
55
|
Success: {
|
|
54
56
|
fields: {
|
|
55
57
|
message: { type: 'string', id: 1 },
|
|
@@ -59,6 +61,7 @@ const protocolV1Schema = {
|
|
|
59
61
|
values: {
|
|
60
62
|
MessageType_Initialize: 1,
|
|
61
63
|
MessageType_Success: 2,
|
|
64
|
+
MessageType_GetFeatures: 55,
|
|
62
65
|
},
|
|
63
66
|
},
|
|
64
67
|
},
|
|
@@ -140,7 +143,7 @@ const createHarness = () => {
|
|
|
140
143
|
id: uuid,
|
|
141
144
|
name: 'OneKey Pro 2',
|
|
142
145
|
localName: 'OneKey Pro 2',
|
|
143
|
-
serviceUUIDs: ['
|
|
146
|
+
serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
|
|
144
147
|
isConnected: jest.fn(() => Promise.resolve(true)),
|
|
145
148
|
cancelConnection: jest.fn(() => Promise.resolve()),
|
|
146
149
|
onDisconnected: jest.fn(callback => {
|
|
@@ -181,11 +184,186 @@ const createHarness = () => {
|
|
|
181
184
|
};
|
|
182
185
|
};
|
|
183
186
|
|
|
187
|
+
const createV1Harness = () => {
|
|
188
|
+
const uuid = 'rn-classic-id';
|
|
189
|
+
const notifySubscriptionRemovers: jest.Mock[] = [];
|
|
190
|
+
const disconnectSubscriptionRemovers: jest.Mock[] = [];
|
|
191
|
+
let notifyCallback:
|
|
192
|
+
| ((error: Error | null, characteristic: { value: string } | null) => void)
|
|
193
|
+
| undefined;
|
|
194
|
+
const notifyCharacteristic = {
|
|
195
|
+
uuid: '0003',
|
|
196
|
+
deviceID: uuid,
|
|
197
|
+
isNotifiable: true,
|
|
198
|
+
monitor: jest.fn(callback => {
|
|
199
|
+
notifyCallback = callback;
|
|
200
|
+
const remove = jest.fn();
|
|
201
|
+
notifySubscriptionRemovers.push(remove);
|
|
202
|
+
return { remove };
|
|
203
|
+
}),
|
|
204
|
+
};
|
|
205
|
+
let writeCount = 0;
|
|
206
|
+
const writeCharacteristic = {
|
|
207
|
+
uuid: '0002',
|
|
208
|
+
deviceID: uuid,
|
|
209
|
+
isWritableWithResponse: true,
|
|
210
|
+
isWritableWithoutResponse: true,
|
|
211
|
+
writeWithoutResponse: jest.fn(() => {
|
|
212
|
+
writeCount += 1;
|
|
213
|
+
if (writeCount === 1) {
|
|
214
|
+
notifyCallback?.(null, {
|
|
215
|
+
value: Buffer.from('3f23230002000000040a026f6b', 'hex').toString('base64'),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return Promise.resolve();
|
|
219
|
+
}),
|
|
220
|
+
};
|
|
221
|
+
const device = {
|
|
222
|
+
id: uuid,
|
|
223
|
+
name: 'OneKey Classic',
|
|
224
|
+
localName: 'OneKey Classic',
|
|
225
|
+
serviceUUIDs: ['00000001-0000-1000-8000-00805f9b34fb'],
|
|
226
|
+
isConnected: jest.fn(() => Promise.resolve(true)),
|
|
227
|
+
cancelConnection: jest.fn(() => Promise.resolve()),
|
|
228
|
+
onDisconnected: jest.fn(() => {
|
|
229
|
+
const remove = jest.fn();
|
|
230
|
+
disconnectSubscriptionRemovers.push(remove);
|
|
231
|
+
return { remove };
|
|
232
|
+
}),
|
|
233
|
+
};
|
|
234
|
+
const transport = new ReactNativeBleTransport({ scanTimeout: 1 });
|
|
235
|
+
const bleManager = {
|
|
236
|
+
devices: jest.fn(() => Promise.resolve([device])),
|
|
237
|
+
connectedDevices: jest.fn(() => Promise.resolve([])),
|
|
238
|
+
cancelTransaction: jest.fn(() => Promise.resolve()),
|
|
239
|
+
};
|
|
240
|
+
transport.blePlxManager = bleManager as any;
|
|
241
|
+
transport.resolveCharacteristics = jest.fn(() =>
|
|
242
|
+
Promise.resolve({ writeCharacteristic, notifyCharacteristic })
|
|
243
|
+
);
|
|
244
|
+
transport.init({ debug: jest.fn(), error: jest.fn() }, new EventEmitter());
|
|
245
|
+
transport.configure(protocolV1Schema);
|
|
246
|
+
transport.configureProtocolV2(protocolV2Schema);
|
|
247
|
+
return {
|
|
248
|
+
transport,
|
|
249
|
+
uuid,
|
|
250
|
+
device,
|
|
251
|
+
bleManager,
|
|
252
|
+
notifySubscriptionRemovers,
|
|
253
|
+
disconnectSubscriptionRemovers,
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
|
|
184
257
|
describe('ReactNativeBleTransport Protocol V2 link lifecycle', () => {
|
|
258
|
+
test('does not classify disconnects as retryable firmware writes', () => {
|
|
259
|
+
expect(
|
|
260
|
+
getFirmwareUploadWriteRetryType({
|
|
261
|
+
errorCode: 205,
|
|
262
|
+
message: 'Device disconnected after write',
|
|
263
|
+
})
|
|
264
|
+
).toBeNull();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test('keeps another device reader when releasing a device with an active V1 call', async () => {
|
|
268
|
+
const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
|
|
269
|
+
const activeV1Call = createDeferred<string>();
|
|
270
|
+
const otherDeviceReader = createDeferred<Uint8Array>();
|
|
271
|
+
activeV1Call.promise.catch(() => undefined);
|
|
272
|
+
otherDeviceReader.promise.catch(() => undefined);
|
|
273
|
+
transport.runPromise = activeV1Call;
|
|
274
|
+
transport.runPromiseDeviceId = 'device-a';
|
|
275
|
+
transport.protocolV2FramePromises.set('device-b', otherDeviceReader);
|
|
276
|
+
|
|
277
|
+
await transport.releaseNative('device-a', true);
|
|
278
|
+
|
|
279
|
+
expect(transport.protocolV2FramePromises.get('device-b')).toBe(otherDeviceReader);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('rejects a pending reader when its device frame state resets', async () => {
|
|
283
|
+
const transport = new ReactNativeBleTransport({ scanTimeout: 1 }) as any;
|
|
284
|
+
const reader = createDeferred<Uint8Array>();
|
|
285
|
+
transport.protocolV2FramePromises.set('device-a', reader);
|
|
286
|
+
const result = Promise.race([
|
|
287
|
+
reader.promise.then(
|
|
288
|
+
() => 'resolved',
|
|
289
|
+
() => 'rejected'
|
|
290
|
+
),
|
|
291
|
+
new Promise(resolve => {
|
|
292
|
+
setTimeout(() => resolve('pending'), 20);
|
|
293
|
+
}),
|
|
294
|
+
]);
|
|
295
|
+
|
|
296
|
+
transport.resetProtocolV2Frames('device-a');
|
|
297
|
+
|
|
298
|
+
await expect(result).resolves.toBe('rejected');
|
|
299
|
+
});
|
|
300
|
+
|
|
185
301
|
test('keeps the legacy default BLE scan timeout', () => {
|
|
186
302
|
expect(new ReactNativeBleTransport({}).scanTimeout).toBe(3000);
|
|
187
303
|
});
|
|
188
304
|
|
|
305
|
+
test('reconnects before falling back to Protocol V1 after a fatal V2 probe failure', async () => {
|
|
306
|
+
const { transport, uuid, device, notifySubscriptionRemovers, disconnectSubscriptionRemovers } =
|
|
307
|
+
createV1Harness();
|
|
308
|
+
const probeProtocolV2 = jest
|
|
309
|
+
.spyOn(transport as any, 'probeProtocolV2')
|
|
310
|
+
.mockImplementationOnce(async () => {
|
|
311
|
+
await (transport as any).releaseNative(uuid, true);
|
|
312
|
+
return false;
|
|
313
|
+
});
|
|
314
|
+
const resolveCharacteristics = jest.spyOn(transport as any, 'resolveCharacteristics');
|
|
315
|
+
|
|
316
|
+
await expect(transport.acquire({ uuid, protocolHint: 'V2' })).resolves.toEqual({
|
|
317
|
+
uuid,
|
|
318
|
+
protocolType: 'V1',
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
expect(probeProtocolV2).toHaveBeenCalledTimes(1);
|
|
322
|
+
expect(resolveCharacteristics).toHaveBeenCalledTimes(2);
|
|
323
|
+
expect(transport.getProtocolType(uuid)).toBe('V1');
|
|
324
|
+
expect(device.onDisconnected).toHaveBeenCalledTimes(1);
|
|
325
|
+
expect(notifySubscriptionRemovers).toHaveLength(2);
|
|
326
|
+
expect(notifySubscriptionRemovers[0]).toHaveBeenCalledTimes(1);
|
|
327
|
+
|
|
328
|
+
await transport.release(uuid, true);
|
|
329
|
+
|
|
330
|
+
expect(notifySubscriptionRemovers[1]).toHaveBeenCalledTimes(1);
|
|
331
|
+
expect(disconnectSubscriptionRemovers).toHaveLength(1);
|
|
332
|
+
expect(disconnectSubscriptionRemovers[0]).toHaveBeenCalledTimes(1);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('cleans the rebuilt transport when Protocol V1 fallback also fails', async () => {
|
|
336
|
+
const { transport, uuid, device, bleManager, notifySubscriptionRemovers } = createV1Harness();
|
|
337
|
+
jest.spyOn(transport as any, 'probeProtocolV2').mockImplementationOnce(async () => {
|
|
338
|
+
await (transport as any).releaseNative(uuid, true);
|
|
339
|
+
return false;
|
|
340
|
+
});
|
|
341
|
+
jest.spyOn(transport as any, 'probeProtocolV1').mockResolvedValue(false);
|
|
342
|
+
|
|
343
|
+
await expect(transport.acquire({ uuid, protocolHint: 'V2' })).rejects.toMatchObject({
|
|
344
|
+
errorCode: HardwareErrorCode.BleTimeoutError,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
expect(device.onDisconnected).not.toHaveBeenCalled();
|
|
348
|
+
expect(notifySubscriptionRemovers).toHaveLength(2);
|
|
349
|
+
expect(notifySubscriptionRemovers[0]).toHaveBeenCalledTimes(1);
|
|
350
|
+
expect(notifySubscriptionRemovers[1]).toHaveBeenCalledTimes(1);
|
|
351
|
+
expect(bleManager.cancelTransaction).toHaveBeenCalled();
|
|
352
|
+
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test('disconnects and invalidates a Protocol V1 link after a response timeout', async () => {
|
|
356
|
+
const { transport, uuid, device } = createV1Harness();
|
|
357
|
+
|
|
358
|
+
await transport.acquire({ uuid, expectedProtocol: 'V1' });
|
|
359
|
+
await expect(transport.call(uuid, 'Initialize', {}, { timeoutMs: 5 })).rejects.toMatchObject({
|
|
360
|
+
errorCode: HardwareErrorCode.BleTimeoutError,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
expect(device.cancelConnection).toHaveBeenCalled();
|
|
364
|
+
expect(transport.getProtocolType(uuid)).toBeUndefined();
|
|
365
|
+
});
|
|
366
|
+
|
|
189
367
|
afterEach(() => {
|
|
190
368
|
resetProtocolV2BleTuning();
|
|
191
369
|
});
|
package/src/constants.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createKnownBleUuidAliases, matchesKnownBleUuid } from '@onekeyfe/hd-shared';
|
|
2
|
+
|
|
1
3
|
export const IOS_PACKET_LENGTH = 128;
|
|
2
4
|
export const ANDROID_PACKET_LENGTH = 192;
|
|
3
5
|
export const ANDROID_DEFAULT_MTU = 23;
|
|
@@ -12,7 +14,6 @@ type BluetoothServices = Record<
|
|
|
12
14
|
>;
|
|
13
15
|
|
|
14
16
|
const ClassicServiceUUID = '00000001-0000-1000-8000-00805f9b34fb';
|
|
15
|
-
const Pro2ServiceUUID = 'fffd';
|
|
16
17
|
|
|
17
18
|
const OneKeyServices: Record<string, BluetoothServices> = {
|
|
18
19
|
classic: {
|
|
@@ -21,11 +22,6 @@ const OneKeyServices: Record<string, BluetoothServices> = {
|
|
|
21
22
|
writeUuid: '00000002-0000-1000-8000-00805f9b34fb',
|
|
22
23
|
notifyUuid: '00000003-0000-1000-8000-00805f9b34fb',
|
|
23
24
|
},
|
|
24
|
-
[Pro2ServiceUUID]: {
|
|
25
|
-
serviceUuid: Pro2ServiceUUID,
|
|
26
|
-
writeUuid: '00000002-0000-1000-8000-00805f9b34fb',
|
|
27
|
-
notifyUuid: '00000003-0000-1000-8000-00805f9b34fb',
|
|
28
|
-
},
|
|
29
25
|
},
|
|
30
26
|
};
|
|
31
27
|
|
|
@@ -48,7 +44,7 @@ export const getInfosForServiceUuid = (serviceUuid: string, deviceType: 'classic
|
|
|
48
44
|
Object.values(services).find(
|
|
49
45
|
item =>
|
|
50
46
|
normalizeBleUuid(item.serviceUuid) === normalizedServiceUuid ||
|
|
51
|
-
|
|
47
|
+
matchesKnownBleUuid(serviceUuid, createKnownBleUuidAliases(item.serviceUuid))
|
|
52
48
|
);
|
|
53
49
|
if (!service) {
|
|
54
50
|
return null;
|
|
@@ -59,17 +55,7 @@ export const getInfosForServiceUuid = (serviceUuid: string, deviceType: 'classic
|
|
|
59
55
|
export const normalizeBleUuid = (uuid?: string | null) =>
|
|
60
56
|
(uuid ?? '').replace(/-/g, '').toLowerCase();
|
|
61
57
|
|
|
62
|
-
export const getBleUuidKey = (uuid?: string | null) => {
|
|
63
|
-
const normalized = normalizeBleUuid(uuid);
|
|
64
|
-
return normalized.length >= 8 ? normalized.substring(4, 8) : normalized;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
58
|
export const isSameBleUuid = (left?: string | null, right?: string | null) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
normalizedLeft === normalizedRight ||
|
|
73
|
-
(getBleUuidKey(left) !== '' && getBleUuidKey(left) === getBleUuidKey(right))
|
|
74
|
-
);
|
|
59
|
+
if (!left || !right) return false;
|
|
60
|
+
return matchesKnownBleUuid(left, createKnownBleUuidAliases(right));
|
|
75
61
|
};
|