run method
Future<Map<String, dynamic> >
run({
- required String miniProgramId,
- required String action,
- required Map<
String, dynamic> arguments, - required MiniProgramBluetoothPolicy policy,
- MiniProgramBluetoothValueHandler? onValue,
- MiniProgramBluetoothValueHandler? onConnection,
Implementation
Future<Map<String, dynamic>> run({
required String miniProgramId,
required String action,
required Map<String, dynamic> arguments,
required MiniProgramBluetoothPolicy policy,
MiniProgramBluetoothValueHandler? onValue,
MiniProgramBluetoothValueHandler? onConnection,
}) async {
if (_disposed) _fail('request_cancelled', 'The BLE runtime has closed.');
final args = MiniProgramBluetooth.arguments(action, arguments);
final cleanup =
action == 'bluetooth.disconnect' ||
action == 'bluetooth.stopScan' ||
action == 'bluetooth.unsubscribe';
if (!cleanup) policy.validate();
if (!cleanup && !policy.enabled) {
_fail('not_accepted', 'Bluetooth is not accepted by host policy.');
}
if (action == 'bluetooth.write' && !policy.allowWrite) {
_fail('not_accepted', 'BLE writes are not accepted by host policy.');
}
if (action == 'bluetooth.subscribe' && !policy.allowNotifications) {
_fail(
'not_accepted',
'BLE notifications are not accepted by host policy.',
);
}
final allowed = cleanup
? <String>[]
: MiniProgramBluetooth.uuids(policy.allowedServices);
final valueLimit = cleanup
? MiniProgramBluetooth.maxValueBytes
: policy.maxValueBytes;
if (args['serviceUuid'] != null &&
allowed.isNotEmpty &&
!allowed.contains(args['serviceUuid'])) {
_fail('not_accepted', 'This BLE service is not accepted by host policy.');
}
if (action == 'bluetooth.scan') {
final filters = List<String>.from(args['serviceUuids'] as List);
if (allowed.isNotEmpty && filters.any((id) => !allowed.contains(id))) {
_fail('not_accepted', 'The BLE scan requests an unaccepted service.');
}
args['serviceUuids'] = filters.isEmpty ? allowed : filters;
args['timeoutMs'] = (args['timeoutMs'] as int).clamp(
500,
policy.maxScanMs,
);
args['maxDevices'] = (args['maxDevices'] as int).clamp(
1,
policy.maxDevices,
);
}
if (action == 'bluetooth.write') {
MiniProgramBluetooth.hex(
args['valueHex'],
limit: policy.maxValueBytes < 20 ? policy.maxValueBytes : 20,
empty: false,
);
}
var session = _session;
if (session != null && session.appId != miniProgramId) {
_fail('in_use', 'Another mini-program owns this BLE session.');
}
if (action == 'bluetooth.disconnect') {
await releaseFor(miniProgramId);
return {'changed': session != null};
}
if (cleanup && session == null) {
return {
action == 'bluetooth.unsubscribe' ? 'subscribed' : 'changed': false,
};
}
if (session == null) {
session = _BluetoothSession(
miniProgramId,
'ble_session_${DateTime.now().microsecondsSinceEpoch}_${++_sequence}',
);
_sessionGeneration++;
}
_session = session;
if (session.busy && !cleanup) {
_fail('request_in_progress', 'A BLE operation is already pending.');
}
if (!cleanup) session.busy = true;
final owner = session;
if (action == 'bluetooth.connect' &&
!owner.devices.contains(args['deviceId'])) {
owner.busy = false;
_fail(
'device_not_found',
'Use a device handle from the current BLE scan.',
);
}
final key = '${args['serviceUuid']}/${args['characteristicUuid']}';
if (action == 'bluetooth.subscribe' &&
!owner.listeners.containsKey(key) &&
owner.listeners.length >= MiniProgramBluetooth.maxSubscriptions) {
owner.busy = false;
_fail('limit_exceeded', 'Too many BLE notification subscriptions.');
}
try {
final raw = await provider.invoke(
miniProgramId: miniProgramId,
sessionId: owner.id,
action: action,
arguments: {
...args,
'maxValueBytes': valueLimit,
'allowedServices': allowed,
},
);
if (_disposed || !identical(_session, owner)) {
await _release(owner);
_fail('request_cancelled', 'The BLE session ended before completion.');
}
final data = MiniProgramBluetooth.result(
action,
raw,
deviceLimit: action == 'bluetooth.scan'
? args['maxDevices'] as int
: policy.maxDevices,
valueLimit: valueLimit,
);
switch (action) {
case 'bluetooth.scan':
owner.devices.clear();
owner.devices.addAll(
(data['devices'] as List).map(
(v) => (v as Map)['deviceId'] as String,
),
);
case 'bluetooth.connect':
if (!owner.devices.contains(data['deviceId']) ||
data['deviceId'] != args['deviceId'] ||
data['connected'] != true) {
throw const FormatException('Unexpected BLE connection.');
}
owner.connectionListener = onConnection;
owner.connectedDevice = data['deviceId'] as String;
case 'bluetooth.discoverServices':
if (allowed.isNotEmpty) {
data['services'] = (data['services'] as List)
.where((s) => allowed.contains((s as Map)['uuid']))
.toList();
}
case 'bluetooth.read':
if (data['serviceUuid'] != args['serviceUuid'] ||
data['characteristicUuid'] != args['characteristicUuid']) {
throw const FormatException('Unexpected BLE characteristic.');
}
case 'bluetooth.subscribe':
if (data['subscribed'] != true || onValue == null) {
throw const FormatException('Missing BLE notification receiver.');
}
owner.listeners[key] = (policy.maxValueBytes, onValue);
case 'bluetooth.unsubscribe':
if (data['subscribed'] != false) {
throw const FormatException('Unexpected BLE subscription.');
}
owner.listeners.remove(key);
case 'bluetooth.write':
if (data['bytesWritten'] !=
(args['valueHex'] as String).length ~/ 2) {
throw const FormatException('Unexpected BLE write length.');
}
}
return data;
} on FormatException {
await releaseFor(miniProgramId);
_fail('invalid_result', 'The BLE provider returned invalid data.');
} finally {
if (!cleanup) owner.busy = false;
}
}