bunite-core 0.0.12 → 0.0.14

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bunite-core",
3
3
  "description": "Uniting UI and Bun",
4
- "version": "0.0.12",
4
+ "version": "0.0.14",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "setup:cef": "bun ../tools/bunite-dev/scripts/setup-cef.ts",
@@ -2,7 +2,7 @@ import { ptr } from "bun:ffi";
2
2
  import { buildViewPreloadScript } from "../preload/inline";
3
3
  import { log } from "../../shared/log";
4
4
  import { buniteEventEmitter } from "../events/eventEmitter";
5
- import { defineBuniteRPC, type BuniteRPCConfig, type BuniteRPCSchema, type RPCWithTransport, type RPCRequestHandler } from "../../shared/rpc";
5
+ import { defineBuniteRPC, type BuniteRPCConfig, type BuniteRPCSchema, type RPCWithTransport } from "../../shared/rpc";
6
6
  import { createWebRPCHandler } from "../../shared/webRpcHandler";
7
7
  import { ensureNativeRuntime, getNativeLibrary, toCString, waitForViewReady, cancelWaitForViewReady } from "../proc/native";
8
8
  import { attachBrowserViewRegistry, getRPCPort, sendMessageToView } from "./Socket";
@@ -165,8 +165,11 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
165
165
  config: BuniteRPCConfig<Schema, "bun">
166
166
  ) {
167
167
  const rpc = defineBuniteRPC("bun", config);
168
+ const webRpc = createWebRPCHandler<Schema>(config);
168
169
  return Object.assign(rpc, {
169
- webHandler: createWebRPCHandler((config.handlers.requests ?? {}) as any)
170
+ webHandler: webRpc,
171
+ webClients: webRpc.webClients,
172
+ broadcast: webRpc.broadcast,
170
173
  });
171
174
  }
172
175
 
package/src/shared/rpc.ts CHANGED
@@ -343,6 +343,16 @@ export function createRPC<
343
343
  ) => void;
344
344
  };
345
345
 
346
+ function dispose() {
347
+ for (const [id, pending] of pendingRequests) {
348
+ clearTimeout(pending.timeout);
349
+ pending.reject(new Error("RPC disposed"));
350
+ }
351
+ pendingRequests.clear();
352
+ transport.unregisterHandler?.();
353
+ transport = {};
354
+ }
355
+
346
356
  return {
347
357
  setTransport,
348
358
  setRequestHandler,
@@ -352,6 +362,7 @@ export function createRPC<
352
362
  sendProxy,
353
363
  addMessageListener,
354
364
  removeMessageListener,
365
+ dispose,
355
366
  proxy: {
356
367
  request: requestProxy,
357
368
  send: sendProxy
@@ -1,48 +1,102 @@
1
+ import {
2
+ defineBuniteRPC,
3
+ type BuniteRPCConfig,
4
+ type BuniteRPCSchema,
5
+ type RPCTransport,
6
+ type RPCPacket
7
+ } from "./rpc";
1
8
  import { decodeRPCPacket, encodeRPCPacket, asUint8Array } from "./rpcWire";
9
+ import { log } from "./log";
2
10
 
3
- type WebRPCHandlers = Record<string, (params?: unknown) => unknown>;
11
+ type WebRPCSocket = { send(data: Uint8Array | ArrayBuffer): void | number };
4
12
 
5
- /** Minimal ws interface compatible with Bun's ServerWebSocket. */
6
- type WebRPCSocket = { send(data: Uint8Array): void | number };
13
+ export type WebRPCClient<Schema extends BuniteRPCSchema = BuniteRPCSchema> = {
14
+ ws: WebRPCSocket;
15
+ rpc: ReturnType<typeof defineBuniteRPC<Schema, "bun">>;
16
+ handlePacket: (packet: RPCPacket) => void | Promise<void>;
17
+ };
7
18
 
8
- function errorMessage(error: unknown): string {
9
- return error instanceof Error ? error.message : String(error);
10
- }
19
+ export function createWebRPCHandler<Schema extends BuniteRPCSchema>(
20
+ config: BuniteRPCConfig<Schema, "bun"> & {
21
+ extraRequestHandlers?: Record<string, (...args: any[]) => unknown>;
22
+ }
23
+ ) {
24
+ const connections = new WeakMap<WebRPCSocket, WebRPCClient<Schema>>();
25
+ const webClients = new Set<WebRPCClient<Schema>>();
26
+
27
+ const handler = {
28
+ open(ws: WebRPCSocket) {
29
+ let handlePacket: ((packet: RPCPacket) => void | Promise<void>) | undefined;
30
+
31
+ const transport: RPCTransport = {
32
+ send(packet) {
33
+ ws.send(encodeRPCPacket(packet));
34
+ },
35
+ registerHandler(h) {
36
+ handlePacket = h;
37
+ },
38
+ unregisterHandler() {
39
+ handlePacket = undefined;
40
+ }
41
+ };
42
+
43
+ const rpc = defineBuniteRPC("bun", config);
44
+ rpc.setTransport(transport);
45
+
46
+ const client: WebRPCClient<Schema> = {
47
+ ws,
48
+ rpc: rpc as WebRPCClient<Schema>["rpc"],
49
+ handlePacket: (packet) => handlePacket?.(packet)
50
+ };
51
+
52
+ connections.set(ws, client);
53
+ webClients.add(client);
54
+ handler.onWebClientConnected?.(client);
55
+ },
11
56
 
12
- export function createWebRPCHandler(handlers: WebRPCHandlers) {
13
- return {
14
- message(ws: WebRPCSocket, raw: string | Buffer) {
57
+ message(ws: WebRPCSocket, raw: string | Buffer | ArrayBuffer | Uint8Array) {
15
58
  if (typeof raw === "string") return;
16
59
 
17
- let packet;
60
+ const client = connections.get(ws);
61
+ if (!client) return;
62
+
18
63
  try {
19
- packet = decodeRPCPacket(asUint8Array(raw));
64
+ Promise.resolve(client.handlePacket(decodeRPCPacket(asUint8Array(raw)))).catch((error) => {
65
+ log.error("Web RPC packet handler error", error);
66
+ });
20
67
  } catch {
21
- return;
68
+ // malformed packet — decode failure
22
69
  }
70
+ },
23
71
 
24
- if (packet.type !== "request" || typeof packet.method !== "string") return;
72
+ close(ws: WebRPCSocket) {
73
+ const client = connections.get(ws);
74
+ if (!client) return;
25
75
 
26
- const handler = handlers[packet.method];
27
- if (!handler) {
28
- ws.send(
29
- encodeRPCPacket({ type: "response", id: packet.id, success: false, error: `Unknown method: ${packet.method}` })
30
- );
31
- return;
32
- }
76
+ client.rpc.dispose();
77
+ webClients.delete(client);
78
+ connections.delete(ws);
79
+ handler.onWebClientDisconnected?.(client);
80
+ },
33
81
 
34
- try {
35
- Promise.resolve(handler(packet.params)).then(
36
- (payload) =>
37
- ws.send(encodeRPCPacket({ type: "response", id: packet.id, success: true, payload })),
38
- (error) =>
39
- ws.send(encodeRPCPacket({ type: "response", id: packet.id, success: false, error: errorMessage(error) }))
40
- );
41
- } catch (error) {
42
- ws.send(
43
- encodeRPCPacket({ type: "response", id: packet.id, success: false, error: errorMessage(error) })
44
- );
82
+ webClients: webClients as ReadonlySet<WebRPCClient<Schema>>,
83
+
84
+ broadcast<M extends keyof Schema["bun"]["messages"]>(
85
+ messageName: M,
86
+ ...args: void extends Schema["bun"]["messages"][M]
87
+ ? []
88
+ : undefined extends Schema["bun"]["messages"][M]
89
+ ? [payload?: Schema["bun"]["messages"][M]]
90
+ : [payload: Schema["bun"]["messages"][M]]
91
+ ) {
92
+ for (const client of webClients) {
93
+ (client.rpc.send as any)(messageName, ...args);
45
94
  }
46
- }
95
+ },
96
+
97
+ onWebClientConnected: undefined as ((client: WebRPCClient<Schema>) => void) | undefined,
98
+ onWebClientDisconnected: undefined as ((client: WebRPCClient<Schema>) => void) | undefined,
47
99
  };
100
+
101
+ return handler;
48
102
  }