tauri-plugin-blew 0.1.0

Tauri plugin for the blew BLE library
Documentation
package org.jakebot.blew

import android.bluetooth.BluetoothSocket
import android.util.Log
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger

/**
 * Shared L2CAP socket management used by both [BleCentralManager] and [BlePeripheralManager].
 *
 * Handles socket registration, read loops, writes, and closes. JNI callbacks
 * (channel data, channel closed) are forwarded via the provided lambdas so each
 * manager routes to its own `external fun` declarations.
 *
 * @param startId  Starting socket ID. Central and peripheral use non-overlapping
 *                 ranges (1 vs 100 000) to avoid collisions in the shared Rust-side state.
 */
class L2capSocketManager(
    private val tag: String,
    private val onData: (socketId: Int, data: ByteArray) -> Unit,
    private val onClosed: (socketId: Int) -> Unit,
    startId: Int = 1,
) {
    private val sockets = ConcurrentHashMap<Int, BluetoothSocket>()
    private val nextId = AtomicInteger(startId)

    fun register(socket: BluetoothSocket): Int {
        val id = nextId.getAndIncrement()
        sockets[id] = socket
        return id
    }

    fun write(
        socketId: Int,
        data: ByteArray,
    ) {
        val socket = sockets[socketId] ?: return
        try {
            socket.outputStream.write(data)
            socket.outputStream.flush()
        } catch (e: Exception) {
            Log.e(tag, "L2CAP write failed (socket $socketId): ${e.message}")
            close(socketId)
        }
    }

    fun close(socketId: Int) {
        val socket = sockets.remove(socketId) ?: return
        try {
            socket.close()
        } catch (_: Exception) {
        }
        onClosed(socketId)
    }

    fun startReadLoop(
        socketId: Int,
        deviceAddr: String,
        socket: BluetoothSocket,
    ) {
        val buf = ByteArray(4096)
        try {
            val input = socket.inputStream
            while (true) {
                val n = input.read(buf)
                if (n < 0) break
                onData(socketId, buf.copyOf(n))
            }
        } catch (e: Exception) {
            Log.d(tag, "L2CAP read ended (socket $socketId): ${e.message}")
        }
        close(socketId)
    }
}