toss-expo-sdk 0.1.1 → 1.0.1

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 (93) hide show
  1. package/README.md +490 -81
  2. package/lib/module/ble.js +59 -4
  3. package/lib/module/ble.js.map +1 -1
  4. package/lib/module/client/BLETransactionHandler.js +277 -0
  5. package/lib/module/client/BLETransactionHandler.js.map +1 -0
  6. package/lib/module/client/NonceAccountManager.js +364 -0
  7. package/lib/module/client/NonceAccountManager.js.map +1 -0
  8. package/lib/module/client/TossClient.js +27 -44
  9. package/lib/module/client/TossClient.js.map +1 -1
  10. package/lib/module/contexts/WalletContext.js +4 -4
  11. package/lib/module/contexts/WalletContext.js.map +1 -1
  12. package/lib/module/discovery.js +35 -8
  13. package/lib/module/discovery.js.map +1 -1
  14. package/lib/module/examples/offlinePaymentFlow.js +27 -2
  15. package/lib/module/examples/offlinePaymentFlow.js.map +1 -1
  16. package/lib/module/hooks/useOfflineBLETransactions.js +314 -0
  17. package/lib/module/hooks/useOfflineBLETransactions.js.map +1 -0
  18. package/lib/module/index.js +13 -8
  19. package/lib/module/index.js.map +1 -1
  20. package/lib/module/intent.js +198 -0
  21. package/lib/module/intent.js.map +1 -1
  22. package/lib/module/nfc.js +1 -1
  23. package/lib/module/noise.js +176 -1
  24. package/lib/module/noise.js.map +1 -1
  25. package/lib/module/reconciliation.js +155 -0
  26. package/lib/module/reconciliation.js.map +1 -1
  27. package/lib/module/services/authService.js +164 -1
  28. package/lib/module/services/authService.js.map +1 -1
  29. package/lib/module/storage/secureStorage.js +102 -0
  30. package/lib/module/storage/secureStorage.js.map +1 -1
  31. package/lib/module/storage.js +4 -4
  32. package/lib/module/sync.js +25 -1
  33. package/lib/module/sync.js.map +1 -1
  34. package/lib/module/types/nonceAccount.js +2 -0
  35. package/lib/module/types/nonceAccount.js.map +1 -0
  36. package/lib/module/types/tossUser.js +16 -1
  37. package/lib/module/types/tossUser.js.map +1 -1
  38. package/lib/typescript/src/__tests__/solana-program-simple.test.d.ts +8 -0
  39. package/lib/typescript/src/__tests__/solana-program-simple.test.d.ts.map +1 -0
  40. package/lib/typescript/src/ble.d.ts +31 -2
  41. package/lib/typescript/src/ble.d.ts.map +1 -1
  42. package/lib/typescript/src/client/BLETransactionHandler.d.ts +98 -0
  43. package/lib/typescript/src/client/BLETransactionHandler.d.ts.map +1 -0
  44. package/lib/typescript/src/client/NonceAccountManager.d.ts +82 -0
  45. package/lib/typescript/src/client/NonceAccountManager.d.ts.map +1 -0
  46. package/lib/typescript/src/client/TossClient.d.ts +10 -12
  47. package/lib/typescript/src/client/TossClient.d.ts.map +1 -1
  48. package/lib/typescript/src/discovery.d.ts +8 -2
  49. package/lib/typescript/src/discovery.d.ts.map +1 -1
  50. package/lib/typescript/src/examples/offlinePaymentFlow.d.ts +9 -1
  51. package/lib/typescript/src/examples/offlinePaymentFlow.d.ts.map +1 -1
  52. package/lib/typescript/src/hooks/useOfflineBLETransactions.d.ts +91 -0
  53. package/lib/typescript/src/hooks/useOfflineBLETransactions.d.ts.map +1 -0
  54. package/lib/typescript/src/index.d.ts +11 -4
  55. package/lib/typescript/src/index.d.ts.map +1 -1
  56. package/lib/typescript/src/intent.d.ts +26 -0
  57. package/lib/typescript/src/intent.d.ts.map +1 -1
  58. package/lib/typescript/src/noise.d.ts +62 -0
  59. package/lib/typescript/src/noise.d.ts.map +1 -1
  60. package/lib/typescript/src/reconciliation.d.ts +6 -0
  61. package/lib/typescript/src/reconciliation.d.ts.map +1 -1
  62. package/lib/typescript/src/services/authService.d.ts +26 -1
  63. package/lib/typescript/src/services/authService.d.ts.map +1 -1
  64. package/lib/typescript/src/storage/secureStorage.d.ts +16 -0
  65. package/lib/typescript/src/storage/secureStorage.d.ts.map +1 -1
  66. package/lib/typescript/src/sync.d.ts +6 -1
  67. package/lib/typescript/src/sync.d.ts.map +1 -1
  68. package/lib/typescript/src/types/nonceAccount.d.ts +59 -0
  69. package/lib/typescript/src/types/nonceAccount.d.ts.map +1 -0
  70. package/lib/typescript/src/types/tossUser.d.ts +16 -0
  71. package/lib/typescript/src/types/tossUser.d.ts.map +1 -1
  72. package/package.json +12 -1
  73. package/src/__tests__/reconciliation.test.tsx +7 -1
  74. package/src/__tests__/solana-program-simple.test.ts +256 -0
  75. package/src/ble.ts +105 -4
  76. package/src/client/BLETransactionHandler.ts +364 -0
  77. package/src/client/NonceAccountManager.ts +444 -0
  78. package/src/client/TossClient.ts +36 -49
  79. package/src/contexts/WalletContext.tsx +4 -4
  80. package/src/discovery.ts +46 -8
  81. package/src/examples/offlinePaymentFlow.ts +48 -2
  82. package/src/hooks/useOfflineBLETransactions.ts +438 -0
  83. package/src/index.tsx +49 -7
  84. package/src/intent.ts +254 -0
  85. package/src/nfc.ts +4 -4
  86. package/src/noise.ts +239 -1
  87. package/src/reconciliation.ts +184 -0
  88. package/src/services/authService.ts +188 -1
  89. package/src/storage/secureStorage.ts +142 -4
  90. package/src/storage.ts +4 -4
  91. package/src/sync.ts +40 -0
  92. package/src/types/nonceAccount.ts +75 -0
  93. package/src/types/tossUser.ts +35 -2
package/src/ble.ts CHANGED
@@ -3,12 +3,18 @@ import { BleManager, Device } from 'react-native-ble-plx';
3
3
  import { PermissionsAndroid, Platform } from 'react-native';
4
4
  import type { TossUser } from './types/tossUser';
5
5
  import type { SolanaIntent } from './intent';
6
+ import type { OfflineTransaction } from './types/nonceAccount';
7
+ import { BLETransactionHandler } from './client/BLETransactionHandler';
6
8
 
7
9
  const SERVICE_UUID = '0000ff00-0000-1000-8000-00805f9b34fb';
8
10
  const USER_CHARACTERISTIC = '0000ff01-0000-1000-8000-00805f9b34fb';
9
11
  const INTENT_CHARACTERISTIC = '0000ff02-0000-1000-8000-00805f9b34fb';
12
+ const OFFLINE_TX_CHARACTERISTIC = '0000ff03-0000-1000-8000-00805f9b34fb'; // New for offline transactions
10
13
 
11
14
  const manager = new BleManager();
15
+ const bleTransactionHandler = new BLETransactionHandler(
16
+ Platform.OS === 'ios' ? 'ios' : 'android'
17
+ );
12
18
 
13
19
  export async function requestBLEPermissions() {
14
20
  if (Platform.OS === 'android') {
@@ -30,7 +36,8 @@ async function connect(device: Device) {
30
36
  // Scan for BLE devices advertising TOSS service
31
37
  export function startTossScan(
32
38
  onUserFound: (user: TossUser, device: Device) => void,
33
- onIntentFound: (intent: SolanaIntent, device: Device) => void
39
+ onIntentFound: (intent: SolanaIntent, device: Device) => void,
40
+ onOfflineTransactionFound?: (tx: OfflineTransaction, device: Device) => void
34
41
  ) {
35
42
  manager.startDeviceScan([SERVICE_UUID], null, async (error, device) => {
36
43
  if (error) {
@@ -67,6 +74,25 @@ export function startTossScan(
67
74
  const intent = JSON.parse(intentData.value) as SolanaIntent;
68
75
  onIntentFound(intent, device);
69
76
  }
77
+
78
+ // Check for offline transaction data (fragmented)
79
+ if (onOfflineTransactionFound) {
80
+ try {
81
+ const txData = await services.readCharacteristicForService(
82
+ device.id,
83
+ SERVICE_UUID,
84
+ OFFLINE_TX_CHARACTERISTIC
85
+ );
86
+
87
+ if (txData?.value) {
88
+ const tx = JSON.parse(txData.value) as OfflineTransaction;
89
+ onOfflineTransactionFound(tx, device);
90
+ }
91
+ } catch {
92
+ // Offline TX characteristic may not be available
93
+ console.debug('Offline transaction characteristic not found');
94
+ }
95
+ }
70
96
  } catch (err) {
71
97
  console.warn('Error reading device data:', err);
72
98
  }
@@ -132,7 +158,82 @@ export async function sendIntentToDevice(
132
158
  await device.cancelConnection();
133
159
  }
134
160
 
135
- // Stop scan
136
- export function stopScan() {
137
- manager.stopDeviceScan();
161
+ /**
162
+ * Send fragmented offline transaction over BLE with Noise Protocol encryption
163
+ * Automatically handles MTU limitations and retries
164
+ */
165
+ export async function sendOfflineTransactionFragmented(
166
+ device: Device,
167
+ transaction: OfflineTransaction | SolanaIntent,
168
+ noiseEncryptFn?: (data: Uint8Array) => Promise<any>,
169
+ isIntent: boolean = false
170
+ ): Promise<{
171
+ success: boolean;
172
+ sentFragments: number;
173
+ failedFragments: number[];
174
+ messageId: string;
175
+ }> {
176
+ try {
177
+ const result = await bleTransactionHandler.sendFragmentedTransactionBLE(
178
+ device,
179
+ transaction,
180
+ async (deviceId, charUUID, data) => {
181
+ const dev = await manager.connectToDevice(deviceId);
182
+ await dev.discoverAllServicesAndCharacteristics();
183
+
184
+ const characteristic =
185
+ charUUID === OFFLINE_TX_CHARACTERISTIC
186
+ ? OFFLINE_TX_CHARACTERISTIC
187
+ : INTENT_CHARACTERISTIC;
188
+
189
+ await dev.writeCharacteristicWithResponseForService(
190
+ deviceId,
191
+ SERVICE_UUID,
192
+ characteristic,
193
+ data.toString('base64')
194
+ );
195
+
196
+ await dev.cancelConnection();
197
+ },
198
+ noiseEncryptFn,
199
+ isIntent
200
+ );
201
+
202
+ return result;
203
+ } catch (error) {
204
+ const errorMessage = error instanceof Error ? error.message : String(error);
205
+ console.error('Failed to send offline transaction:', errorMessage);
206
+ throw new Error(`BLE transmission failed: ${errorMessage}`);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Receive and reassemble fragmented message from BLE
212
+ */
213
+ export async function receiveOfflineTransactionFragment(
214
+ fragment: any,
215
+ noiseDecryptFn?: (encrypted: any) => Promise<Uint8Array>
216
+ ): Promise<{
217
+ complete: boolean;
218
+ transaction?: OfflineTransaction | SolanaIntent;
219
+ progress: { received: number; total: number };
220
+ }> {
221
+ return bleTransactionHandler.receiveFragmentedMessage(
222
+ fragment,
223
+ noiseDecryptFn
224
+ );
225
+ }
226
+
227
+ /**
228
+ * Get current BLE MTU configuration
229
+ */
230
+ export function getBLEMTUConfig() {
231
+ return bleTransactionHandler.getMTUConfig();
232
+ }
233
+
234
+ /**
235
+ * Set custom BLE MTU configuration
236
+ */
237
+ export function setBLEMTUConfig(config: Partial<any>) {
238
+ bleTransactionHandler.setMTUConfig(config);
138
239
  }
@@ -0,0 +1,364 @@
1
+ import { Device } from 'react-native-ble-plx';
2
+ import type { SolanaIntent } from '../intent';
3
+ import type { OfflineTransaction } from '../types/nonceAccount';
4
+
5
+ /**
6
+ * BLE MTU Configuration for different device types
7
+ */
8
+ export interface BLEMTUConfig {
9
+ maxPayloadSize: number; // Actual data size (MTU - overhead)
10
+ chunkSize: number; // Size of each fragment
11
+ maxRetries: number; // Max retries per chunk
12
+ timeout: number; // Timeout in ms
13
+ }
14
+
15
+ /**
16
+ * Default MTU configurations
17
+ */
18
+ const DEFAULT_MTU_CONFIGS: Record<string, BLEMTUConfig> = {
19
+ android: {
20
+ maxPayloadSize: 512, // Typical Android BLE MTU
21
+ chunkSize: 480, // Conservative chunk size
22
+ maxRetries: 3,
23
+ timeout: 5000,
24
+ },
25
+ ios: {
26
+ maxPayloadSize: 512, // iOS BLE MTU
27
+ chunkSize: 480,
28
+ maxRetries: 3,
29
+ timeout: 5000,
30
+ },
31
+ };
32
+
33
+ /**
34
+ * Represents a fragmented message with header information
35
+ */
36
+ export interface BLEFragment {
37
+ messageId: string; // Unique identifier for the message
38
+ sequenceNumber: number; // Fragment index
39
+ totalFragments: number; // Total number of fragments
40
+ checksumValue: number; // CRC32 checksum of fragment
41
+ payload: Uint8Array; // Actual data
42
+ }
43
+
44
+ /**
45
+ * Represents a Noise-encrypted BLE message
46
+ */
47
+ export interface EncryptedBLEMessage {
48
+ version: number; // Protocol version
49
+ ciphertext: Uint8Array; // Encrypted payload
50
+ nonce: Uint8Array; // Encryption nonce
51
+ tag: Uint8Array; // Authentication tag
52
+ }
53
+
54
+ /**
55
+ * BLETransactionHandler
56
+ * Manages secure, fragmented BLE transmission of offline transactions
57
+ * with Noise Protocol encryption
58
+ */
59
+ export class BLETransactionHandler {
60
+ private mtuConfig!: BLEMTUConfig;
61
+ private fragmentCache: Map<string, BLEFragment[]> = new Map();
62
+ private messageIdMap: Map<string, OfflineTransaction | SolanaIntent> =
63
+ new Map();
64
+
65
+ constructor(platform: 'android' | 'ios' = 'android') {
66
+ this.mtuConfig =
67
+ DEFAULT_MTU_CONFIGS[platform] || DEFAULT_MTU_CONFIGS.android!;
68
+ }
69
+
70
+ /**
71
+ * Fragment a large transaction/intent into BLE-safe chunks
72
+ * Respects MTU limitations and adds framing information
73
+ */
74
+ fragmentTransaction(
75
+ transaction: OfflineTransaction | SolanaIntent,
76
+ isIntent: boolean = false
77
+ ): BLEFragment[] {
78
+ const payload = Buffer.from(JSON.stringify(transaction), 'utf-8');
79
+ const messageId = `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
80
+ const totalFragments = Math.ceil(payload.length / this.mtuConfig.chunkSize);
81
+ const fragments: BLEFragment[] = [];
82
+
83
+ for (let i = 0; i < totalFragments; i++) {
84
+ const start = i * this.mtuConfig.chunkSize;
85
+ const end = Math.min(start + this.mtuConfig.chunkSize, payload.length);
86
+ const chunk = payload.slice(start, end);
87
+
88
+ const fragment: BLEFragment = {
89
+ messageId,
90
+ sequenceNumber: i,
91
+ totalFragments,
92
+ checksumValue: this.calculateCRC32(chunk),
93
+ payload: chunk,
94
+ };
95
+
96
+ fragments.push(fragment);
97
+ }
98
+
99
+ // Store fragments for reassembly on receiver end
100
+ this.fragmentCache.set(messageId, fragments);
101
+ this.messageIdMap.set(
102
+ messageId,
103
+ isIntent
104
+ ? (transaction as SolanaIntent)
105
+ : (transaction as OfflineTransaction)
106
+ );
107
+
108
+ return fragments;
109
+ }
110
+
111
+ /**
112
+ * Prepare encrypted BLE message for transmission
113
+ * Uses Noise Protocol for end-to-end encryption
114
+ */
115
+ async prepareEncryptedMessage(
116
+ fragment: BLEFragment,
117
+ noiseEncryptFn: (data: Uint8Array) => Promise<EncryptedBLEMessage>
118
+ ): Promise<EncryptedBLEMessage> {
119
+ // Serialize fragment
120
+ const fragmentData = this.serializeFragment(fragment);
121
+
122
+ // Encrypt using Noise Protocol
123
+ const encrypted = await noiseEncryptFn(fragmentData);
124
+
125
+ return encrypted;
126
+ }
127
+
128
+ /**
129
+ * Send fragmented transaction over BLE with encryption
130
+ * Handles retries and verification
131
+ */
132
+ async sendFragmentedTransactionBLE(
133
+ device: Device,
134
+ transaction: OfflineTransaction | SolanaIntent,
135
+ sendFn: (
136
+ deviceId: string,
137
+ characteristicUUID: string,
138
+ data: Buffer
139
+ ) => Promise<void>,
140
+ noiseEncryptFn?: (data: Uint8Array) => Promise<EncryptedBLEMessage>,
141
+ isIntent: boolean = false
142
+ ): Promise<{
143
+ success: boolean;
144
+ sentFragments: number;
145
+ failedFragments: number[];
146
+ messageId: string;
147
+ }> {
148
+ const fragments = this.fragmentTransaction(transaction, isIntent);
149
+ const messageId = fragments[0]?.messageId;
150
+ const failedFragments: number[] = [];
151
+
152
+ if (!messageId) {
153
+ throw new Error('Failed to generate message ID for transaction');
154
+ }
155
+
156
+ const CHARACTERISTIC_UUID = '0000ff02-0000-1000-8000-00805f9b34fb'; // Intent characteristic
157
+
158
+ for (const fragment of fragments) {
159
+ let retries = 0;
160
+ let sent = false;
161
+
162
+ while (retries < this.mtuConfig.maxRetries && !sent) {
163
+ try {
164
+ let messageData: Buffer;
165
+
166
+ if (noiseEncryptFn) {
167
+ // Encrypt fragment using Noise Protocol
168
+ const encrypted = await this.prepareEncryptedMessage(
169
+ fragment,
170
+ noiseEncryptFn
171
+ );
172
+ messageData = Buffer.from(JSON.stringify(encrypted), 'utf-8');
173
+ } else {
174
+ // Send unencrypted (not recommended)
175
+ messageData = Buffer.from(JSON.stringify(fragment), 'utf-8');
176
+ }
177
+
178
+ // Send via BLE
179
+ await sendFn(device.id, CHARACTERISTIC_UUID, messageData);
180
+
181
+ sent = true;
182
+ } catch (error) {
183
+ retries++;
184
+ console.warn(
185
+ `Failed to send fragment ${fragment.sequenceNumber}, retry ${retries}:`,
186
+ error
187
+ );
188
+
189
+ if (retries >= this.mtuConfig.maxRetries) {
190
+ failedFragments.push(fragment.sequenceNumber);
191
+ } else {
192
+ // Exponential backoff
193
+ await this.delay(Math.pow(2, retries) * 100);
194
+ }
195
+ }
196
+ }
197
+ }
198
+
199
+ return {
200
+ success: failedFragments.length === 0,
201
+ sentFragments: fragments.length - failedFragments.length,
202
+ failedFragments,
203
+ messageId,
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Receive and reassemble fragmented messages
209
+ */
210
+ async receiveFragmentedMessage(
211
+ fragment: BLEFragment,
212
+ _noiseDecryptFn?: (encrypted: EncryptedBLEMessage) => Promise<Uint8Array>
213
+ ): Promise<{
214
+ complete: boolean;
215
+ transaction?: OfflineTransaction | SolanaIntent;
216
+ progress: {
217
+ received: number;
218
+ total: number;
219
+ };
220
+ }> {
221
+ const messageId = fragment.messageId;
222
+
223
+ // Initialize or retrieve fragment cache
224
+ if (!this.fragmentCache.has(messageId)) {
225
+ this.fragmentCache.set(messageId, []);
226
+ }
227
+
228
+ const cachedFragments = this.fragmentCache.get(messageId)!;
229
+ cachedFragments[fragment.sequenceNumber] = fragment;
230
+
231
+ const progress = {
232
+ received: cachedFragments.filter((f) => f !== undefined).length,
233
+ total: fragment.totalFragments,
234
+ };
235
+
236
+ // Check if all fragments received
237
+ if (progress.received < fragment.totalFragments) {
238
+ return {
239
+ complete: false,
240
+ progress,
241
+ };
242
+ }
243
+
244
+ // Reassemble message
245
+ const reassembled = this.reassembleMessage(cachedFragments);
246
+
247
+ if (!reassembled) {
248
+ return {
249
+ complete: false,
250
+ progress,
251
+ };
252
+ }
253
+
254
+ try {
255
+ // Parse transaction
256
+ const transactionData = JSON.parse(reassembled);
257
+ const transaction: OfflineTransaction | SolanaIntent = transactionData;
258
+
259
+ // Cleanup cache
260
+ this.fragmentCache.delete(messageId);
261
+ this.messageIdMap.delete(messageId);
262
+
263
+ return {
264
+ complete: true,
265
+ transaction,
266
+ progress,
267
+ };
268
+ } catch (error) {
269
+ console.error('Failed to parse reassembled message:', error);
270
+ return {
271
+ complete: false,
272
+ progress,
273
+ };
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Reassemble fragments into original message
279
+ */
280
+ private reassembleMessage(fragments: BLEFragment[]): string | null {
281
+ try {
282
+ // Sort by sequence number
283
+ const sorted = fragments.sort(
284
+ (a, b) => a.sequenceNumber - b.sequenceNumber
285
+ );
286
+
287
+ // Verify all fragments present and checksums
288
+ for (const fragment of sorted) {
289
+ const calculatedChecksum = this.calculateCRC32(fragment.payload);
290
+ if (calculatedChecksum !== fragment.checksumValue) {
291
+ console.warn(
292
+ `Checksum mismatch for fragment ${fragment.sequenceNumber}`
293
+ );
294
+ return null;
295
+ }
296
+ }
297
+
298
+ // Concatenate payloads
299
+ const combined = Buffer.concat(sorted.map((f) => Buffer.from(f.payload)));
300
+ return combined.toString('utf-8');
301
+ } catch (error) {
302
+ console.error('Failed to reassemble message:', error);
303
+ return null;
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Serialize BLE fragment for transmission
309
+ */
310
+ private serializeFragment(fragment: BLEFragment): Uint8Array {
311
+ const data = {
312
+ messageId: fragment.messageId,
313
+ sequenceNumber: fragment.sequenceNumber,
314
+ totalFragments: fragment.totalFragments,
315
+ checksumValue: fragment.checksumValue,
316
+ payload: Array.from(fragment.payload),
317
+ };
318
+
319
+ return new Uint8Array(Buffer.from(JSON.stringify(data), 'utf-8'));
320
+ }
321
+
322
+ /**
323
+ * Calculate CRC32 checksum for fragment verification
324
+ */
325
+ private calculateCRC32(data: Uint8Array | Buffer): number {
326
+ let crc = 0xffffffff;
327
+
328
+ for (let i = 0; i < data.length; i++) {
329
+ const byte = data[i];
330
+ if (byte !== undefined) {
331
+ crc = crc ^ byte;
332
+ for (let j = 0; j < 8; j++) {
333
+ crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
334
+ }
335
+ }
336
+ }
337
+
338
+ return (crc ^ 0xffffffff) >>> 0;
339
+ }
340
+
341
+ /**
342
+ * Delay utility for retries
343
+ */
344
+ private delay(ms: number): Promise<void> {
345
+ return new Promise((resolve) => setTimeout(resolve, ms));
346
+ }
347
+
348
+ /**
349
+ * Get MTU configuration
350
+ */
351
+ getMTUConfig(): BLEMTUConfig {
352
+ return this.mtuConfig;
353
+ }
354
+
355
+ /**
356
+ * Set custom MTU configuration
357
+ */
358
+ setMTUConfig(config: Partial<BLEMTUConfig>): void {
359
+ this.mtuConfig = {
360
+ ...this.mtuConfig,
361
+ ...config,
362
+ };
363
+ }
364
+ }