mirror of
https://github.com/google/bumble.git
synced 2026-04-16 00:25:31 +00:00
This command will build a wheel, copy it in the web directory, and create a file `packageFile` with the name of the wheel. If the correct override param is given, bumble.js will read `packageFile` and load that package.
219 lines
6.0 KiB
JavaScript
219 lines
6.0 KiB
JavaScript
function bufferToHex(buffer) {
|
|
return [...new Uint8Array(buffer)].map(x => x.toString(16).padStart(2, '0')).join('');
|
|
}
|
|
|
|
class PacketSource {
|
|
constructor(pyodide) {
|
|
this.parser = pyodide.runPython(`
|
|
from bumble.transport.common import PacketParser
|
|
class ProxiedPacketParser(PacketParser):
|
|
def feed_data(self, js_data):
|
|
super().feed_data(bytes(js_data.to_py()))
|
|
ProxiedPacketParser()
|
|
`);
|
|
}
|
|
|
|
set_packet_sink(sink) {
|
|
this.parser.set_packet_sink(sink);
|
|
}
|
|
|
|
data_received(data) {
|
|
//console.log(`HCI[controller->host]: ${bufferToHex(data)}`);
|
|
this.parser.feed_data(data);
|
|
}
|
|
}
|
|
|
|
class PacketSink {
|
|
constructor() {
|
|
this.queue = [];
|
|
this.isProcessing = false;
|
|
}
|
|
|
|
on_packet(packet) {
|
|
if (!this.writer) {
|
|
return;
|
|
}
|
|
const buffer = packet.toJs({create_proxies : false});
|
|
packet.destroy();
|
|
//console.log(`HCI[host->controller]: ${bufferToHex(buffer)}`);
|
|
this.queue.push(buffer);
|
|
this.processQueue();
|
|
}
|
|
|
|
async processQueue() {
|
|
if (this.isProcessing) {
|
|
return;
|
|
}
|
|
this.isProcessing = true;
|
|
while (this.queue.length > 0) {
|
|
const buffer = this.queue.shift();
|
|
await this.writer(buffer);
|
|
}
|
|
this.isProcessing = false;
|
|
}
|
|
}
|
|
|
|
|
|
class LogEvent extends Event {
|
|
constructor(message) {
|
|
super('log');
|
|
this.message = message;
|
|
}
|
|
}
|
|
|
|
export class Bumble extends EventTarget {
|
|
constructor(pyodide) {
|
|
super();
|
|
this.pyodide = pyodide;
|
|
}
|
|
|
|
async loadRuntime(bumblePackage) {
|
|
// Load pyodide if it isn't provided.
|
|
if (this.pyodide === undefined) {
|
|
this.log('Loading Pyodide');
|
|
this.pyodide = await loadPyodide();
|
|
}
|
|
|
|
// Load the Bumble module
|
|
console.log('Installing micropip');
|
|
this.log(`Installing ${bumblePackage}`)
|
|
await this.pyodide.loadPackage('micropip');
|
|
await this.pyodide.runPythonAsync(`
|
|
import micropip
|
|
await micropip.install('${bumblePackage}')
|
|
package_list = micropip.list()
|
|
print(package_list)
|
|
`)
|
|
|
|
// Mount a filesystem so that we can persist data like the Key Store
|
|
let mountDir = '/bumble';
|
|
this.pyodide.FS.mkdir(mountDir);
|
|
this.pyodide.FS.mount(this.pyodide.FS.filesystems.IDBFS, { root: '.' }, mountDir);
|
|
|
|
// Sync previously persisted filesystem data into memory
|
|
await new Promise(resolve => {
|
|
this.pyodide.FS.syncfs(true, () => {
|
|
console.log('FS synced in');
|
|
resolve();
|
|
});
|
|
})
|
|
|
|
// Setup the HCI source and sink
|
|
this.packetSource = new PacketSource(this.pyodide);
|
|
this.packetSink = new PacketSink();
|
|
}
|
|
|
|
log(message) {
|
|
this.dispatchEvent(new LogEvent(message));
|
|
}
|
|
|
|
async connectWebSocketTransport(hciWsUrl) {
|
|
return new Promise((resolve, reject) => {
|
|
let resolved = false;
|
|
|
|
let ws = new WebSocket(hciWsUrl);
|
|
ws.binaryType = 'arraybuffer';
|
|
|
|
ws.onopen = () => {
|
|
this.log('WebSocket open');
|
|
resolve();
|
|
resolved = true;
|
|
}
|
|
|
|
ws.onclose = () => {
|
|
this.log('WebSocket close');
|
|
if (!resolved) {
|
|
reject(`Failed to connect to ${hciWsUrl}`);
|
|
}
|
|
}
|
|
|
|
ws.onmessage = (event) => {
|
|
this.packetSource.data_received(event.data);
|
|
}
|
|
|
|
this.packetSink.writer = (packet) => {
|
|
if (ws.readyState === WebSocket.OPEN) {
|
|
ws.send(packet);
|
|
}
|
|
}
|
|
this.closeTransport = async () => {
|
|
if (ws.readyState === WebSocket.OPEN) {
|
|
ws.close();
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
async loadApp(appUrl) {
|
|
this.log('Loading app');
|
|
const script = await (await fetch(appUrl)).text();
|
|
await this.pyodide.runPythonAsync(script);
|
|
const pythonMain = this.pyodide.globals.get('main');
|
|
const app = await pythonMain(this.packetSource, this.packetSink);
|
|
if (app.on) {
|
|
app.on('key_store_update', this.onKeystoreUpdate.bind(this));
|
|
}
|
|
this.log('App is ready!');
|
|
return app;
|
|
}
|
|
|
|
onKeystoreUpdate() {
|
|
// Sync the FS
|
|
this.pyodide.FS.syncfs(() => {
|
|
console.log('FS synced out');
|
|
});
|
|
}
|
|
}
|
|
|
|
async function getBumblePackage() {
|
|
const params = (new URL(document.location)).searchParams;
|
|
// First check the packageFile override param
|
|
if (params.has('packageFile')) {
|
|
return await (await fetch('/packageFile')).text()
|
|
}
|
|
// Then check the package override param
|
|
if (params.has('package')) {
|
|
return params.get('package')
|
|
}
|
|
// If no override params, default to the main package
|
|
return 'bumble'
|
|
}
|
|
|
|
export async function setupSimpleApp(appUrl, bumbleControls, log) {
|
|
// Load Bumble
|
|
log('Loading Bumble');
|
|
const bumble = new Bumble();
|
|
bumble.addEventListener('log', (event) => {
|
|
log(event.message);
|
|
})
|
|
await bumble.loadRuntime(await getBumblePackage());
|
|
|
|
log('Bumble is ready!')
|
|
const app = await bumble.loadApp(appUrl);
|
|
|
|
bumbleControls.connector = async (hciWsUrl) => {
|
|
try {
|
|
// Connect the WebSocket HCI transport
|
|
await bumble.connectWebSocketTransport(hciWsUrl);
|
|
|
|
// Start the app
|
|
await app.start();
|
|
|
|
return true;
|
|
} catch (err) {
|
|
log(err);
|
|
return false;
|
|
}
|
|
}
|
|
bumbleControls.stopper = async () => {
|
|
// Stop the app
|
|
await app.stop();
|
|
|
|
// Close the HCI transport
|
|
await bumble.closeTransport();
|
|
}
|
|
bumbleControls.onBumbleLoaded();
|
|
|
|
return app;
|
|
}
|