run method

Future<Map<String, dynamic>> run({
  1. required String miniProgramId,
  2. required String action,
  3. required Map<String, dynamic> arguments,
  4. required MiniProgramBluetoothPolicy policy,
  5. MiniProgramBluetoothValueHandler? onValue,
  6. 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;
  }
}