bunite-core 0.3.1 → 0.4.0
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 +1 -1
- package/src/bun/core/App.ts +2 -2
- package/src/bun/core/BrowserView.ts +10 -10
- package/src/bun/core/BrowserWindow.ts +2 -2
- package/src/bun/core/Socket.ts +16 -16
- package/src/bun/index.ts +20 -20
- package/src/shared/rpc.ts +60 -60
- package/src/shared/rpcDemux.ts +16 -16
- package/src/shared/rpcWire.ts +6 -6
- package/src/shared/webRpcHandler.ts +14 -14
- package/src/shared/webSocketTransport.ts +6 -6
- package/src/view/index.ts +27 -27
package/package.json
CHANGED
package/src/bun/core/App.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
toCString,
|
|
15
15
|
type NativeBootstrapOptions
|
|
16
16
|
} from "../proc/native";
|
|
17
|
-
import { attachGlobalIPCResolver,
|
|
17
|
+
import { attachGlobalIPCResolver, ensureRpcServer } from "./Socket";
|
|
18
18
|
import { BrowserWindow } from "./BrowserWindow";
|
|
19
19
|
import { getSurfaceIPCHandlers } from "./SurfaceManager";
|
|
20
20
|
import { getWebviewIPCHandlers } from "./SurfaceBrowserIPC";
|
|
@@ -134,7 +134,7 @@ export class AppRuntime {
|
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
ensureRpcServer();
|
|
138
138
|
buniteEventEmitter.emitEvent(
|
|
139
139
|
new BuniteEvent("ready", {
|
|
140
140
|
usingStub: runtime.usingStub,
|
|
@@ -2,9 +2,9 @@ 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 { type
|
|
5
|
+
import { type RpcPacket, type RpcTransport, type RpcWithTransport } from "../../shared/rpc";
|
|
6
6
|
import { ensureNativeRuntime, getNativeLibrary, toCString, waitForViewReady, cancelWaitForViewReady } from "../proc/native";
|
|
7
|
-
import { attachBrowserViewRegistry,
|
|
7
|
+
import { attachBrowserViewRegistry, getRpcPort, sendMessageToView } from "./Socket";
|
|
8
8
|
import { randomBytes } from "node:crypto";
|
|
9
9
|
import { resolveDefaultAppResRoot } from "../../shared/paths";
|
|
10
10
|
import { removeSurfacesForHostView } from "./SurfaceRegistry";
|
|
@@ -14,13 +14,13 @@ const BrowserViewMap: Record<number, BrowserView<any>> = {};
|
|
|
14
14
|
let nextWebviewId = 1;
|
|
15
15
|
|
|
16
16
|
function createNativeViewPipe(viewId: number) {
|
|
17
|
-
let handler: ((packet:
|
|
18
|
-
const transport:
|
|
17
|
+
let handler: ((packet: RpcPacket) => void) | undefined;
|
|
18
|
+
const transport: RpcTransport = {
|
|
19
19
|
send: (packet) => { sendMessageToView(viewId, packet); },
|
|
20
20
|
registerHandler: (h) => { handler = h; },
|
|
21
21
|
unregisterHandler: () => { handler = undefined; }
|
|
22
22
|
};
|
|
23
|
-
return { transport, receive: (packet:
|
|
23
|
+
return { transport, receive: (packet: RpcPacket) => handler?.(packet) };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export type BrowserViewOptions<T = undefined> = {
|
|
@@ -62,7 +62,7 @@ const defaultOptions: BrowserViewOptions = {
|
|
|
62
62
|
sandbox: false
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
-
export class BrowserView<T extends
|
|
65
|
+
export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
66
66
|
id = nextWebviewId++;
|
|
67
67
|
private nativeAttached = false;
|
|
68
68
|
private _readyPromise: Promise<void>;
|
|
@@ -75,7 +75,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
75
75
|
partition: string | null;
|
|
76
76
|
frame: BrowserViewOptions["frame"];
|
|
77
77
|
rpc?: T;
|
|
78
|
-
readonly transport:
|
|
78
|
+
readonly transport: RpcTransport;
|
|
79
79
|
private pipe: ReturnType<typeof createNativeViewPipe>;
|
|
80
80
|
autoResize: boolean;
|
|
81
81
|
navigationRules: string[] | null;
|
|
@@ -113,7 +113,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
113
113
|
preload: this.preload,
|
|
114
114
|
appresRoot: this.appresRoot,
|
|
115
115
|
webviewId: this.id,
|
|
116
|
-
rpcSocketPort:
|
|
116
|
+
rpcSocketPort: getRpcPort(),
|
|
117
117
|
secretKey: this.secretKey
|
|
118
118
|
});
|
|
119
119
|
|
|
@@ -174,12 +174,12 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
174
174
|
return Object.values(BrowserViewMap);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
|
|
177
|
+
handleIncomingRpc(packet: RpcPacket) {
|
|
178
178
|
this.pipe.receive(packet);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
get rpcPort() {
|
|
182
|
-
return
|
|
182
|
+
return getRpcPort();
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
setAnchor(mode: "none" | "fill" | "top" | "below-top", inset = 0) {
|
|
@@ -3,7 +3,7 @@ import { BuniteEvent } from "../events/event";
|
|
|
3
3
|
import { buniteEventEmitter } from "../events/eventEmitter";
|
|
4
4
|
import { ensureNativeRuntime, getNativeLibrary, toCString } from "../proc/native";
|
|
5
5
|
import { BrowserView, type BrowserViewOptions } from "./BrowserView";
|
|
6
|
-
import type {
|
|
6
|
+
import type { RpcWithTransport } from "../../shared/rpc";
|
|
7
7
|
import { getNextWindowId } from "./windowIds";
|
|
8
8
|
import { getBaseDir, resolveDefaultAppResRoot } from "../../shared/paths";
|
|
9
9
|
|
|
@@ -58,7 +58,7 @@ export function getLastFocusedWindowId(): number | null {
|
|
|
58
58
|
return lastFocusedWindowId;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
export class BrowserWindow<T extends
|
|
61
|
+
export class BrowserWindow<T extends RpcWithTransport = RpcWithTransport> {
|
|
62
62
|
id = getNextWindowId();
|
|
63
63
|
private nativeAttached = false;
|
|
64
64
|
title: string;
|
package/src/bun/core/Socket.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import type { Server, ServerWebSocket } from "bun";
|
|
2
2
|
import type { BrowserView } from "./BrowserView";
|
|
3
3
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
4
|
-
import type {
|
|
4
|
+
import type { RpcPacket, RpcRequestPacket } from "../../shared/rpc";
|
|
5
5
|
import type { GlobalIPCHandler } from "./App";
|
|
6
6
|
import { log } from "../../shared/log";
|
|
7
7
|
import {
|
|
8
8
|
asUint8Array,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
createEncryptedRpcFrame,
|
|
10
|
+
decodeRpcPacket,
|
|
11
|
+
encodeRpcPacket,
|
|
12
|
+
parseEncryptedRpcFrame
|
|
13
13
|
} from "../../shared/rpcWire";
|
|
14
14
|
import { RPC_AUTH_TAG_LENGTH } from "../../shared/rpcWireConstants";
|
|
15
15
|
|
|
@@ -36,11 +36,11 @@ function encrypt(secretKey: Uint8Array, payload: Uint8Array) {
|
|
|
36
36
|
const iv = new Uint8Array(randomBytes(12));
|
|
37
37
|
const cipher = createCipheriv("aes-256-gcm", secretKey, iv);
|
|
38
38
|
const encrypted = Buffer.concat([cipher.update(payload), cipher.final(), cipher.getAuthTag()]);
|
|
39
|
-
return
|
|
39
|
+
return createEncryptedRpcFrame(iv, new Uint8Array(encrypted));
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function decrypt(secretKey: Uint8Array, frame: Uint8Array) {
|
|
43
|
-
const { iv, encryptedPayload } =
|
|
43
|
+
const { iv, encryptedPayload } = parseEncryptedRpcFrame(frame);
|
|
44
44
|
const ciphertext = encryptedPayload.subarray(0, encryptedPayload.byteLength - RPC_AUTH_TAG_LENGTH);
|
|
45
45
|
const tag = encryptedPayload.subarray(encryptedPayload.byteLength - RPC_AUTH_TAG_LENGTH);
|
|
46
46
|
const decipher = createDecipheriv("aes-256-gcm", secretKey, iv);
|
|
@@ -61,7 +61,7 @@ export function attachBrowserViewRegistry(nextRegistry: ViewRegistry) {
|
|
|
61
61
|
registry = nextRegistry;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export function
|
|
64
|
+
export function ensureRpcServer() {
|
|
65
65
|
if (rpcServer) {
|
|
66
66
|
return { rpcServer, rpcPort };
|
|
67
67
|
}
|
|
@@ -106,14 +106,14 @@ export function ensureRPCServer() {
|
|
|
106
106
|
}
|
|
107
107
|
try {
|
|
108
108
|
const decryptedMessage = decrypt(view.secretKey, binaryMessage);
|
|
109
|
-
const packet =
|
|
109
|
+
const packet = decodeRpcPacket(decryptedMessage);
|
|
110
110
|
|
|
111
|
-
if (packet.type === "request" && (packet as
|
|
112
|
-
void handleGlobalIPC(packet as
|
|
111
|
+
if (packet.type === "request" && (packet as RpcRequestPacket).scope === "global") {
|
|
112
|
+
void handleGlobalIPC(packet as RpcRequestPacket, ws.data.webviewId);
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
view.
|
|
116
|
+
view.handleIncomingRpc(packet);
|
|
117
117
|
} catch (error) {
|
|
118
118
|
log.error("Failed to parse RPC payload", error);
|
|
119
119
|
}
|
|
@@ -138,11 +138,11 @@ export function ensureRPCServer() {
|
|
|
138
138
|
return { rpcServer, rpcPort };
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
export function
|
|
141
|
+
export function getRpcPort(): number {
|
|
142
142
|
return rpcPort;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
async function handleGlobalIPC(packet:
|
|
145
|
+
async function handleGlobalIPC(packet: RpcRequestPacket, viewId: number) {
|
|
146
146
|
const handler = globalIPCResolver?.(packet.method);
|
|
147
147
|
if (!handler) {
|
|
148
148
|
sendMessageToView(viewId, {
|
|
@@ -174,14 +174,14 @@ async function handleGlobalIPC(packet: RPCRequestPacket, viewId: number) {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
export function sendMessageToView(viewId: number, message:
|
|
177
|
+
export function sendMessageToView(viewId: number, message: RpcPacket): boolean {
|
|
178
178
|
const socket = socketMap[viewId];
|
|
179
179
|
const view = registry?.getById(viewId);
|
|
180
180
|
if (!socket || socket.readyState !== WebSocket.OPEN || !view) {
|
|
181
181
|
return false;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
const encrypted = encrypt(view.secretKey,
|
|
184
|
+
const encrypted = encrypt(view.secretKey, encodeRpcPacket(message));
|
|
185
185
|
socket.send(encrypted);
|
|
186
186
|
return true;
|
|
187
187
|
}
|
package/src/bun/index.ts
CHANGED
|
@@ -6,16 +6,16 @@ import { buniteEventEmitter } from "./events/eventEmitter";
|
|
|
6
6
|
import { BuniteEvent } from "./events/event";
|
|
7
7
|
import { completePermissionRequest } from "./proc/native";
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type
|
|
12
|
-
type
|
|
13
|
-
type
|
|
14
|
-
type
|
|
9
|
+
createRpc,
|
|
10
|
+
defineBunRpc,
|
|
11
|
+
type BuniteRpcConfig,
|
|
12
|
+
type BuniteRpcSchema,
|
|
13
|
+
type RpcSchema,
|
|
14
|
+
type RpcWithTransport
|
|
15
15
|
} from "../shared/rpc";
|
|
16
|
-
import {
|
|
16
|
+
import { createRpcTransportDemuxer, type RpcChannelHandle, type RpcTransportDemuxer, type RpcTransportDemuxerOptions } from "../shared/rpcDemux";
|
|
17
17
|
import { createWebSocketTransport, type WebSocketLike, type WebSocketTransportPipe } from "../shared/webSocketTransport";
|
|
18
|
-
import {
|
|
18
|
+
import { createWebRpcHandler, type WebRpcClient } from "../shared/webRpcHandler";
|
|
19
19
|
import type { MessageBoxOptions, MessageBoxResponse } from "./core/Utils";
|
|
20
20
|
import { log, type LogLevel } from "../shared/log";
|
|
21
21
|
|
|
@@ -26,28 +26,28 @@ export {
|
|
|
26
26
|
Utils,
|
|
27
27
|
buniteEventEmitter,
|
|
28
28
|
completePermissionRequest,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
createRpc,
|
|
30
|
+
createRpcTransportDemuxer,
|
|
31
|
+
createWebRpcHandler,
|
|
32
32
|
createWebSocketTransport,
|
|
33
|
-
|
|
33
|
+
defineBunRpc,
|
|
34
34
|
log
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
export type {
|
|
38
38
|
LogLevel,
|
|
39
39
|
BuniteEvent,
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
BuniteRpcConfig,
|
|
41
|
+
BuniteRpcSchema,
|
|
42
42
|
BrowserViewOptions,
|
|
43
|
-
|
|
43
|
+
RpcChannelHandle,
|
|
44
44
|
MessageBoxOptions,
|
|
45
45
|
MessageBoxResponse,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
RpcSchema,
|
|
47
|
+
RpcWithTransport,
|
|
48
|
+
RpcTransportDemuxer,
|
|
49
|
+
RpcTransportDemuxerOptions,
|
|
50
|
+
WebRpcClient,
|
|
51
51
|
WebSocketLike,
|
|
52
52
|
WebSocketTransportPipe,
|
|
53
53
|
WindowOptionsType
|
package/src/shared/rpc.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type RpcRequestPacket = {
|
|
2
2
|
type: "request";
|
|
3
3
|
id: number;
|
|
4
4
|
method: string;
|
|
@@ -6,54 +6,54 @@ export type RPCRequestPacket = {
|
|
|
6
6
|
scope?: "global";
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
export type
|
|
9
|
+
export type RpcResponsePacket =
|
|
10
10
|
| { type: "response"; id: number; success: true; payload: unknown; scope?: "global" }
|
|
11
11
|
| { type: "response"; id: number; success: false; error?: string; scope?: "global" };
|
|
12
12
|
|
|
13
|
-
export type
|
|
13
|
+
export type RpcMessagePacket = {
|
|
14
14
|
type: "message";
|
|
15
15
|
id: string;
|
|
16
16
|
payload: unknown;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
export type
|
|
19
|
+
export type RpcEventPacket = {
|
|
20
20
|
type: "event";
|
|
21
21
|
channel: string;
|
|
22
22
|
data: unknown;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
export type
|
|
25
|
+
export type RpcPacket = RpcRequestPacket | RpcResponsePacket | RpcMessagePacket | RpcEventPacket;
|
|
26
26
|
|
|
27
|
-
type
|
|
28
|
-
type
|
|
27
|
+
type BaseRpcRequestsSchema = Record<string, { params: unknown; response: unknown }>;
|
|
28
|
+
type BaseRpcMessagesSchema = Record<string, unknown>;
|
|
29
29
|
|
|
30
|
-
export type
|
|
31
|
-
export type
|
|
30
|
+
export type RpcRequestsSchema<T extends BaseRpcRequestsSchema = BaseRpcRequestsSchema> = T;
|
|
31
|
+
export type RpcMessagesSchema<T extends BaseRpcMessagesSchema = BaseRpcMessagesSchema> = T;
|
|
32
32
|
|
|
33
|
-
type
|
|
34
|
-
requests?:
|
|
35
|
-
messages?:
|
|
33
|
+
type InputRpcSchema = {
|
|
34
|
+
requests?: RpcRequestsSchema;
|
|
35
|
+
messages?: RpcMessagesSchema;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
type
|
|
39
|
-
requests: undefined extends I["requests"] ?
|
|
40
|
-
messages: undefined extends I["messages"] ?
|
|
38
|
+
type ResolvedRpcSchema<I extends InputRpcSchema> = {
|
|
39
|
+
requests: undefined extends I["requests"] ? BaseRpcRequestsSchema : NonNullable<I["requests"]>;
|
|
40
|
+
messages: undefined extends I["messages"] ? BaseRpcMessagesSchema : NonNullable<I["messages"]>;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
export type
|
|
44
|
-
I extends
|
|
43
|
+
export type RpcSchema<I extends InputRpcSchema | void = InputRpcSchema> = ResolvedRpcSchema<
|
|
44
|
+
I extends InputRpcSchema ? I : InputRpcSchema
|
|
45
45
|
>;
|
|
46
46
|
|
|
47
|
-
type RequestParams<RS extends
|
|
48
|
-
type RequestResponse<RS extends
|
|
49
|
-
type MessagePayload<MS extends
|
|
47
|
+
type RequestParams<RS extends RpcRequestsSchema, M extends keyof RS> = RS[M]["params"];
|
|
48
|
+
type RequestResponse<RS extends RpcRequestsSchema, M extends keyof RS> = RS[M]["response"];
|
|
49
|
+
type MessagePayload<MS extends RpcMessagesSchema, N extends keyof MS> = MS[N];
|
|
50
50
|
|
|
51
|
-
type
|
|
51
|
+
type RpcRequestHandlerFn<RS extends RpcRequestsSchema> = <M extends keyof RS>(
|
|
52
52
|
method: M,
|
|
53
53
|
params: RequestParams<RS, M>
|
|
54
54
|
) => Promise<RequestResponse<RS, M>> | RequestResponse<RS, M>;
|
|
55
55
|
|
|
56
|
-
type
|
|
56
|
+
type RpcRequestHandlerObject<RS extends RpcRequestsSchema> = {
|
|
57
57
|
[M in keyof RS]?: (
|
|
58
58
|
...args: undefined extends RS[M]["params"] ? [params?: RS[M]["params"]] : [params: RS[M]["params"]]
|
|
59
59
|
) => Promise<Awaited<RequestResponse<RS, M>>> | Awaited<RequestResponse<RS, M>>;
|
|
@@ -61,34 +61,34 @@ type RPCRequestHandlerObject<RS extends RPCRequestsSchema> = {
|
|
|
61
61
|
_?: (method: keyof RS, params: RequestParams<RS, keyof RS>) => unknown;
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
export type
|
|
65
|
-
|
|
|
66
|
-
|
|
|
64
|
+
export type RpcRequestHandler<RS extends RpcRequestsSchema = RpcRequestsSchema> =
|
|
65
|
+
| RpcRequestHandlerFn<RS>
|
|
66
|
+
| RpcRequestHandlerObject<RS>;
|
|
67
67
|
|
|
68
|
-
export type
|
|
69
|
-
send?: (data:
|
|
70
|
-
registerHandler?: (handler: (packet:
|
|
68
|
+
export type RpcTransport = {
|
|
69
|
+
send?: (data: RpcPacket) => void;
|
|
70
|
+
registerHandler?: (handler: (packet: RpcPacket) => void) => void;
|
|
71
71
|
unregisterHandler?: () => void;
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
export interface
|
|
75
|
-
setTransport: (transport:
|
|
74
|
+
export interface RpcWithTransport {
|
|
75
|
+
setTransport: (transport: RpcTransport) => void;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
export type
|
|
79
|
-
bun:
|
|
80
|
-
webview:
|
|
78
|
+
export type BuniteRpcSchema = {
|
|
79
|
+
bun: RpcSchema;
|
|
80
|
+
webview: RpcSchema;
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
type RemoteSideOf<S extends "bun" | "webview"> = S extends "bun" ? "webview" : "bun";
|
|
84
84
|
|
|
85
|
-
export type
|
|
86
|
-
Schema extends
|
|
85
|
+
export type BuniteRpcConfig<
|
|
86
|
+
Schema extends BuniteRpcSchema,
|
|
87
87
|
Side extends "bun" | "webview"
|
|
88
88
|
> = {
|
|
89
89
|
maxRequestTime?: number;
|
|
90
90
|
handlers: {
|
|
91
|
-
requests?:
|
|
91
|
+
requests?: RpcRequestHandler<Schema[Side]["requests"]>;
|
|
92
92
|
messages?: {
|
|
93
93
|
[K in keyof Schema[RemoteSideOf<Side>]["messages"]]?: (
|
|
94
94
|
payload: MessagePayload<Schema[RemoteSideOf<Side>]["messages"], K>
|
|
@@ -111,33 +111,33 @@ function missingTransportMethodError(methods: string[], action: string): Error {
|
|
|
111
111
|
);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
export function
|
|
115
|
-
Schema extends
|
|
116
|
-
RemoteSchema extends
|
|
114
|
+
export function createRpc<
|
|
115
|
+
Schema extends RpcSchema = RpcSchema,
|
|
116
|
+
RemoteSchema extends RpcSchema = Schema
|
|
117
117
|
>(options: {
|
|
118
|
-
transport?:
|
|
119
|
-
requestHandler?:
|
|
118
|
+
transport?: RpcTransport;
|
|
119
|
+
requestHandler?: RpcRequestHandler<Schema["requests"]>;
|
|
120
120
|
maxRequestTime?: number;
|
|
121
121
|
} = {}) {
|
|
122
|
-
let transport:
|
|
122
|
+
let transport: RpcTransport = {};
|
|
123
123
|
let requestHandler:
|
|
124
|
-
|
|
|
124
|
+
| RpcRequestHandlerFn<Schema["requests"]>
|
|
125
125
|
| undefined;
|
|
126
126
|
|
|
127
|
-
function setTransport(nextTransport:
|
|
127
|
+
function setTransport(nextTransport: RpcTransport) {
|
|
128
128
|
transport.unregisterHandler?.();
|
|
129
129
|
transport = nextTransport;
|
|
130
130
|
transport.registerHandler?.(handlePacket);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
function setRequestHandler(handler:
|
|
133
|
+
function setRequestHandler(handler: RpcRequestHandler<Schema["requests"]>) {
|
|
134
134
|
if (typeof handler === "function") {
|
|
135
|
-
requestHandler = handler as
|
|
135
|
+
requestHandler = handler as RpcRequestHandlerFn<Schema["requests"]>;
|
|
136
136
|
return;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
requestHandler = (method, params) => {
|
|
140
|
-
const requestHandlerObject = handler as
|
|
140
|
+
const requestHandlerObject = handler as RpcRequestHandlerObject<Schema["requests"]>;
|
|
141
141
|
const specificHandler = requestHandlerObject[method];
|
|
142
142
|
if (specificHandler) {
|
|
143
143
|
return (specificHandler as (...args: [unknown]) => unknown)(params);
|
|
@@ -190,7 +190,7 @@ export function createRPC<
|
|
|
190
190
|
|
|
191
191
|
const id = nextRequestId();
|
|
192
192
|
const params = args[0];
|
|
193
|
-
const packet:
|
|
193
|
+
const packet: RpcRequestPacket = {
|
|
194
194
|
type: "request",
|
|
195
195
|
id,
|
|
196
196
|
method: String(method),
|
|
@@ -257,7 +257,7 @@ export function createRPC<
|
|
|
257
257
|
messageListeners.get(String(messageName))?.delete(listener as (payload: unknown) => void);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
async function handlePacket(packet:
|
|
260
|
+
async function handlePacket(packet: RpcPacket) {
|
|
261
261
|
if (packet.type === "request") {
|
|
262
262
|
if (!transport.send || !requestHandler) {
|
|
263
263
|
throw missingTransportMethodError(["send", "requestHandler"], "handle requests");
|
|
@@ -370,11 +370,11 @@ export function createRPC<
|
|
|
370
370
|
};
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
function
|
|
374
|
-
Schema extends
|
|
373
|
+
function defineSideRpc<
|
|
374
|
+
Schema extends BuniteRpcSchema,
|
|
375
375
|
Side extends "bun" | "webview"
|
|
376
376
|
>(
|
|
377
|
-
config:
|
|
377
|
+
config: BuniteRpcConfig<Schema, Side> & {
|
|
378
378
|
extraRequestHandlers?: Record<string, (...args: any[]) => unknown>;
|
|
379
379
|
}
|
|
380
380
|
) {
|
|
@@ -388,12 +388,12 @@ function defineSideRPC<
|
|
|
388
388
|
messages: Schema[Side]["messages"];
|
|
389
389
|
};
|
|
390
390
|
|
|
391
|
-
const rpc =
|
|
391
|
+
const rpc = createRpc<LocalSchema, RemoteSchema>({
|
|
392
392
|
maxRequestTime: config.maxRequestTime,
|
|
393
393
|
requestHandler: {
|
|
394
394
|
...(config.handlers.requests ?? {}),
|
|
395
395
|
...(config.extraRequestHandlers ?? {})
|
|
396
|
-
} as
|
|
396
|
+
} as RpcRequestHandler<LocalSchema["requests"]>,
|
|
397
397
|
transport: {
|
|
398
398
|
registerHandler: () => {}
|
|
399
399
|
}
|
|
@@ -416,18 +416,18 @@ function defineSideRPC<
|
|
|
416
416
|
return rpc;
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
-
export function
|
|
420
|
-
config:
|
|
419
|
+
export function defineBunRpc<Schema extends BuniteRpcSchema>(
|
|
420
|
+
config: BuniteRpcConfig<Schema, "bun"> & {
|
|
421
421
|
extraRequestHandlers?: Record<string, (...args: any[]) => unknown>;
|
|
422
422
|
}
|
|
423
423
|
) {
|
|
424
|
-
return
|
|
424
|
+
return defineSideRpc<Schema, "bun">(config);
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
-
export function
|
|
428
|
-
config:
|
|
427
|
+
export function defineWebviewRpc<Schema extends BuniteRpcSchema>(
|
|
428
|
+
config: BuniteRpcConfig<Schema, "webview"> & {
|
|
429
429
|
extraRequestHandlers?: Record<string, (...args: any[]) => unknown>;
|
|
430
430
|
}
|
|
431
431
|
) {
|
|
432
|
-
return
|
|
432
|
+
return defineSideRpc<Schema, "webview">(config);
|
|
433
433
|
}
|
package/src/shared/rpcDemux.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RpcPacket, RpcTransport, RpcWithTransport } from "./rpc";
|
|
2
2
|
|
|
3
|
-
type DemuxPacketEnvelope = { channel: string; packet:
|
|
3
|
+
type DemuxPacketEnvelope = { channel: string; packet: RpcPacket };
|
|
4
4
|
type DemuxHelloFrame = { channel: string; hello: true };
|
|
5
5
|
type DemuxFrame = DemuxPacketEnvelope | DemuxHelloFrame;
|
|
6
6
|
|
|
@@ -18,27 +18,27 @@ function isHelloFrame(frame: DemuxFrame): frame is DemuxHelloFrame {
|
|
|
18
18
|
return (frame as DemuxHelloFrame).hello === true;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export type
|
|
21
|
+
export type RpcChannelHandle = {
|
|
22
22
|
/**
|
|
23
23
|
* Connect an RPC instance to this channel. Returns a promise that resolves
|
|
24
24
|
* once both sides have registered a handler (HELLO handshake). Awaiting
|
|
25
25
|
* guarantees the first subsequent request reaches the peer.
|
|
26
26
|
*/
|
|
27
|
-
bindTo(rpc:
|
|
27
|
+
bindTo(rpc: RpcWithTransport): Promise<void>;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
export type
|
|
31
|
-
channel(name: string):
|
|
30
|
+
export type RpcTransportDemuxer = {
|
|
31
|
+
channel(name: string): RpcChannelHandle;
|
|
32
32
|
dispose(): void;
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
export type
|
|
35
|
+
export type RpcTransportDemuxerOptions = {
|
|
36
36
|
/** ms to wait for peer before `bindTo` rejects. Default 10_000. */
|
|
37
37
|
readyTimeout?: number;
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
type ChannelState = {
|
|
41
|
-
handler?: (packet:
|
|
41
|
+
handler?: (packet: RpcPacket) => void;
|
|
42
42
|
peerSawUs: boolean;
|
|
43
43
|
ready: Promise<void>;
|
|
44
44
|
resolveReady: () => void;
|
|
@@ -49,12 +49,12 @@ type ChannelState = {
|
|
|
49
49
|
|
|
50
50
|
const DEFAULT_READY_TIMEOUT = 10_000;
|
|
51
51
|
|
|
52
|
-
export function
|
|
53
|
-
base:
|
|
54
|
-
options:
|
|
55
|
-
):
|
|
52
|
+
export function createRpcTransportDemuxer(
|
|
53
|
+
base: RpcTransport,
|
|
54
|
+
options: RpcTransportDemuxerOptions = {}
|
|
55
|
+
): RpcTransportDemuxer {
|
|
56
56
|
if (!base.send || !base.registerHandler) {
|
|
57
|
-
throw new Error("
|
|
57
|
+
throw new Error("createRpcTransportDemuxer requires a base transport with both send and registerHandler");
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
const readyTimeout = options.readyTimeout ?? DEFAULT_READY_TIMEOUT;
|
|
@@ -93,7 +93,7 @@ export function createTransportDemuxer(
|
|
|
93
93
|
|
|
94
94
|
function sendHello(name: string) {
|
|
95
95
|
const frame: DemuxHelloFrame = { channel: name, hello: true };
|
|
96
|
-
base.send!(frame as unknown as
|
|
96
|
+
base.send!(frame as unknown as RpcPacket);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
base.registerHandler((data) => {
|
|
@@ -120,11 +120,11 @@ export function createTransportDemuxer(
|
|
|
120
120
|
channel(name) {
|
|
121
121
|
const state = getOrCreateState(name);
|
|
122
122
|
|
|
123
|
-
const transport:
|
|
123
|
+
const transport: RpcTransport = {
|
|
124
124
|
send(packet) {
|
|
125
125
|
if (disposed) throw new Error(`Demuxer disposed; cannot send on channel "${name}"`);
|
|
126
126
|
const envelope: DemuxPacketEnvelope = { channel: name, packet };
|
|
127
|
-
base.send!(envelope as unknown as
|
|
127
|
+
base.send!(envelope as unknown as RpcPacket);
|
|
128
128
|
},
|
|
129
129
|
registerHandler(handler) {
|
|
130
130
|
if (disposed) throw new Error(`Demuxer disposed; cannot register on channel "${name}"`);
|
package/src/shared/rpcWire.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { pack, unpack } from "msgpackr";
|
|
2
|
-
import type {
|
|
2
|
+
import type { RpcPacket } from "./rpc";
|
|
3
3
|
import {
|
|
4
4
|
RPC_AUTH_TAG_LENGTH,
|
|
5
5
|
RPC_FRAME_VERSION,
|
|
6
6
|
RPC_IV_LENGTH
|
|
7
7
|
} from "./rpcWireConstants";
|
|
8
8
|
|
|
9
|
-
export function
|
|
9
|
+
export function encodeRpcPacket(packet: RpcPacket): Uint8Array {
|
|
10
10
|
return pack(packet) as Uint8Array;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function
|
|
14
|
-
return unpack(data) as
|
|
13
|
+
export function decodeRpcPacket(data: Uint8Array): RpcPacket {
|
|
14
|
+
return unpack(data) as RpcPacket;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function
|
|
17
|
+
export function createEncryptedRpcFrame(
|
|
18
18
|
iv: Uint8Array,
|
|
19
19
|
encryptedPayload: Uint8Array
|
|
20
20
|
): Uint8Array {
|
|
@@ -29,7 +29,7 @@ export function createEncryptedRPCFrame(
|
|
|
29
29
|
return frame;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export function
|
|
32
|
+
export function parseEncryptedRpcFrame(frame: Uint8Array) {
|
|
33
33
|
if (frame.byteLength < 1 + RPC_IV_LENGTH + RPC_AUTH_TAG_LENGTH) {
|
|
34
34
|
throw new Error("Invalid RPC frame: payload is too short.");
|
|
35
35
|
}
|
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
type
|
|
4
|
-
type
|
|
2
|
+
defineBunRpc,
|
|
3
|
+
type BuniteRpcConfig,
|
|
4
|
+
type BuniteRpcSchema
|
|
5
5
|
} from "./rpc";
|
|
6
6
|
import { createWebSocketTransport, type WebSocketLike } from "./webSocketTransport";
|
|
7
7
|
import { log } from "./log";
|
|
8
8
|
|
|
9
|
-
export type
|
|
9
|
+
export type WebRpcClient<Schema extends BuniteRpcSchema = BuniteRpcSchema> = {
|
|
10
10
|
ws: WebSocketLike;
|
|
11
|
-
rpc: ReturnType<typeof
|
|
11
|
+
rpc: ReturnType<typeof defineBunRpc<Schema>>;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
export function
|
|
15
|
-
config:
|
|
14
|
+
export function createWebRpcHandler<Schema extends BuniteRpcSchema>(
|
|
15
|
+
config: BuniteRpcConfig<Schema, "bun"> & {
|
|
16
16
|
extraRequestHandlers?: Record<string, (...args: any[]) => unknown>;
|
|
17
17
|
}
|
|
18
18
|
) {
|
|
19
|
-
type Entry = { client:
|
|
19
|
+
type Entry = { client: WebRpcClient<Schema>; receive: (raw: ArrayBuffer | Uint8Array) => void };
|
|
20
20
|
|
|
21
21
|
const connections = new WeakMap<WebSocketLike, Entry>();
|
|
22
|
-
const webClients = new Set<
|
|
22
|
+
const webClients = new Set<WebRpcClient<Schema>>();
|
|
23
23
|
|
|
24
24
|
const handler = {
|
|
25
25
|
open(ws: WebSocketLike) {
|
|
26
26
|
const pipe = createWebSocketTransport(ws);
|
|
27
|
-
const rpc =
|
|
27
|
+
const rpc = defineBunRpc(config);
|
|
28
28
|
rpc.setTransport(pipe.transport);
|
|
29
29
|
|
|
30
|
-
const client:
|
|
30
|
+
const client: WebRpcClient<Schema> = { ws, rpc: rpc as WebRpcClient<Schema>["rpc"] };
|
|
31
31
|
|
|
32
32
|
connections.set(ws, { client, receive: pipe.receive });
|
|
33
33
|
webClients.add(client);
|
|
@@ -56,7 +56,7 @@ export function createWebRPCHandler<Schema extends BuniteRPCSchema>(
|
|
|
56
56
|
handler.onWebClientDisconnected?.(entry.client);
|
|
57
57
|
},
|
|
58
58
|
|
|
59
|
-
webClients: webClients as ReadonlySet<
|
|
59
|
+
webClients: webClients as ReadonlySet<WebRpcClient<Schema>>,
|
|
60
60
|
|
|
61
61
|
broadcast<M extends keyof Schema["bun"]["messages"]>(
|
|
62
62
|
messageName: M,
|
|
@@ -71,8 +71,8 @@ export function createWebRPCHandler<Schema extends BuniteRPCSchema>(
|
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
73
|
|
|
74
|
-
onWebClientConnected: undefined as ((client:
|
|
75
|
-
onWebClientDisconnected: undefined as ((client:
|
|
74
|
+
onWebClientConnected: undefined as ((client: WebRpcClient<Schema>) => void) | undefined,
|
|
75
|
+
onWebClientDisconnected: undefined as ((client: WebRpcClient<Schema>) => void) | undefined,
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
return handler;
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { asUint8Array,
|
|
1
|
+
import type { RpcPacket, RpcTransport } from "./rpc";
|
|
2
|
+
import { asUint8Array, decodeRpcPacket, encodeRpcPacket } from "./rpcWire";
|
|
3
3
|
|
|
4
4
|
export type WebSocketLike = {
|
|
5
5
|
send(data: Uint8Array | ArrayBuffer): void | number;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
export type WebSocketTransportPipe = {
|
|
9
|
-
transport:
|
|
9
|
+
transport: RpcTransport;
|
|
10
10
|
receive(raw: ArrayBuffer | ArrayBufferView | Uint8Array): void;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export function createWebSocketTransport(ws: WebSocketLike): WebSocketTransportPipe {
|
|
14
|
-
let handler: ((packet:
|
|
14
|
+
let handler: ((packet: RpcPacket) => void) | undefined;
|
|
15
15
|
|
|
16
16
|
return {
|
|
17
17
|
transport: {
|
|
18
|
-
send(packet) { ws.send(
|
|
18
|
+
send(packet) { ws.send(encodeRpcPacket(packet)); },
|
|
19
19
|
registerHandler(h) { handler = h; },
|
|
20
20
|
unregisterHandler() { handler = undefined; }
|
|
21
21
|
},
|
|
22
22
|
receive(raw) {
|
|
23
|
-
handler?.(
|
|
23
|
+
handler?.(decodeRpcPacket(asUint8Array(raw)));
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
}
|
package/src/view/index.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import "../shared/webviewPolyfill";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
type
|
|
5
|
-
type
|
|
6
|
-
type
|
|
7
|
-
type
|
|
8
|
-
type
|
|
9
|
-
type
|
|
3
|
+
defineWebviewRpc,
|
|
4
|
+
type BuniteRpcConfig,
|
|
5
|
+
type RpcPacket,
|
|
6
|
+
type BuniteRpcSchema,
|
|
7
|
+
type RpcSchema,
|
|
8
|
+
type RpcTransport,
|
|
9
|
+
type RpcWithTransport
|
|
10
10
|
} from "../shared/rpc";
|
|
11
|
-
import {
|
|
11
|
+
import { createRpcTransportDemuxer, type RpcChannelHandle, type RpcTransportDemuxer, type RpcTransportDemuxerOptions } from "../shared/rpcDemux";
|
|
12
12
|
import { createWebSocketTransport, type WebSocketLike, type WebSocketTransportPipe } from "../shared/webSocketTransport";
|
|
13
|
-
import {
|
|
13
|
+
import { decodeRpcPacket, encodeRpcPacket } from "../shared/rpcWire";
|
|
14
14
|
import { log } from "../shared/log";
|
|
15
15
|
|
|
16
16
|
type BuniteWindowGlobals = Window &
|
|
@@ -36,13 +36,13 @@ function toArrayBuffer(bytes: Uint8Array): ArrayBuffer {
|
|
|
36
36
|
return copy.buffer;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export class BuniteView<T extends
|
|
39
|
+
export class BuniteView<T extends RpcWithTransport = RpcWithTransport> {
|
|
40
40
|
bunSocket?: WebSocket;
|
|
41
41
|
rpc?: T;
|
|
42
|
-
readonly transport:
|
|
42
|
+
readonly transport: RpcTransport;
|
|
43
43
|
|
|
44
|
-
private handler?: (packet:
|
|
45
|
-
private pendingPackets:
|
|
44
|
+
private handler?: (packet: RpcPacket) => void;
|
|
45
|
+
private pendingPackets: RpcPacket[] = [];
|
|
46
46
|
|
|
47
47
|
constructor(config?: { rpc?: T }) {
|
|
48
48
|
this.rpc = config?.rpc;
|
|
@@ -63,19 +63,19 @@ export class BuniteView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
63
63
|
if (isNative) {
|
|
64
64
|
buniteWindow.__bunite ??= {};
|
|
65
65
|
buniteWindow.__bunite.receiveMessageFromBun = (message) => {
|
|
66
|
-
this.handler?.(message as
|
|
66
|
+
this.handler?.(message as RpcPacket);
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
this.rpc?.setTransport(this.transport);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
private sendPacket(packet:
|
|
72
|
+
private sendPacket(packet: RpcPacket) {
|
|
73
73
|
if (isNative) {
|
|
74
74
|
void this.bunBridge(packet).catch((error) => {
|
|
75
75
|
log.error("Failed to send RPC packet", error);
|
|
76
76
|
});
|
|
77
77
|
} else {
|
|
78
|
-
this.bunSocket!.send(toArrayBuffer(
|
|
78
|
+
this.bunSocket!.send(toArrayBuffer(encodeRpcPacket(packet)));
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -90,7 +90,7 @@ export class BuniteView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
90
90
|
const bytes = await messageToUint8Array(event.data);
|
|
91
91
|
if (!bytes) return;
|
|
92
92
|
try {
|
|
93
|
-
this.handler?.(
|
|
93
|
+
this.handler?.(decodeRpcPacket(bytes));
|
|
94
94
|
} catch (error) {
|
|
95
95
|
log.error("Failed to parse WebSocket message", error);
|
|
96
96
|
}
|
|
@@ -122,7 +122,7 @@ export class BuniteView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
const decrypted = await decrypt(binaryMessage);
|
|
125
|
-
const packet =
|
|
125
|
+
const packet = decodeRpcPacket(decrypted);
|
|
126
126
|
if ((packet as any).scope === "global") return;
|
|
127
127
|
this.handler?.(packet);
|
|
128
128
|
} catch (error) {
|
|
@@ -148,7 +148,7 @@ export class BuniteView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
148
148
|
});
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
async bunBridge(message:
|
|
151
|
+
async bunBridge(message: RpcPacket) {
|
|
152
152
|
if (this.bunSocket?.readyState !== WebSocket.OPEN) return;
|
|
153
153
|
|
|
154
154
|
const encrypt = buniteWindow.__bunite_encrypt;
|
|
@@ -157,7 +157,7 @@ export class BuniteView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
157
157
|
return;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
const encrypted = await encrypt(
|
|
160
|
+
const encrypted = await encrypt(encodeRpcPacket(message));
|
|
161
161
|
this.bunSocket.send(toArrayBuffer(encrypted));
|
|
162
162
|
}
|
|
163
163
|
|
|
@@ -171,15 +171,15 @@ async function messageToUint8Array(data: unknown) {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
export { log, type LogLevel } from "../shared/log";
|
|
174
|
-
export {
|
|
174
|
+
export { createRpcTransportDemuxer, createWebSocketTransport, defineWebviewRpc };
|
|
175
175
|
|
|
176
176
|
export type {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
177
|
+
BuniteRpcConfig,
|
|
178
|
+
BuniteRpcSchema,
|
|
179
|
+
RpcChannelHandle,
|
|
180
|
+
RpcSchema,
|
|
181
|
+
RpcTransportDemuxer,
|
|
182
|
+
RpcTransportDemuxerOptions,
|
|
183
183
|
WebSocketLike,
|
|
184
184
|
WebSocketTransportPipe
|
|
185
185
|
};
|