bunite-core 0.17.1 → 0.17.3

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.
@@ -1,12 +1,12 @@
1
1
  import {
2
+ type AnyCapDef,
3
+ type ClientOf,
4
+ type Connection,
2
5
  createConnection,
3
6
  createFrameTransport,
4
7
  createWebSocketPipe,
5
- type Connection,
6
- type CapDef,
7
8
  type Schema,
8
9
  type SchemaRoots,
9
- type ClientOf,
10
10
  type WebSocketLike,
11
11
  } from "./index";
12
12
 
@@ -22,8 +22,10 @@ export interface BuniteWebGlobal {
22
22
  declare global {
23
23
  interface Window {
24
24
  host?: {
25
- bootstrap<C extends CapDef<any, any>>(cap: C): Promise<ClientOf<C>>;
26
- bootstrap<R extends SchemaRoots>(schema: Schema<R>): Promise<{ [K in keyof R]: ClientOf<R[K]> }>;
25
+ bootstrap<C extends AnyCapDef>(cap: C): Promise<ClientOf<C>>;
26
+ bootstrap<R extends SchemaRoots>(
27
+ schema: Schema<R>,
28
+ ): Promise<{ [K in keyof R]: ClientOf<R[K]> }>;
27
29
  runtime(): Promise<ClientOf<typeof import("./framework").RuntimeCap>>;
28
30
  releaseRef(proxy: unknown): Promise<void>;
29
31
  /** Full Connection for renderer-as-server (serve / serveAll / unserve / replace / on). */
@@ -44,7 +46,13 @@ function isNative(): boolean {
44
46
  }
45
47
 
46
48
  function ensureWebConnection(path = "/rpc"): Promise<Connection> {
47
- if (_webConn) return Promise.resolve(_webConn);
49
+ // Drop a closed cached conn so a disconnect (WS close/error → onClose) leads to
50
+ // a fresh reconnect on the next call, instead of handing back a dead connection.
51
+ if (_webConn) {
52
+ if (!_webConn.closed) return Promise.resolve(_webConn);
53
+ _webConn = null;
54
+ _webConnPromise = null;
55
+ }
48
56
  // Host-provided shared Connection — cross-bundle reachability for renderer
49
57
  // ecosystems (e.g. extension hosts) that bundle bunite-core 0-externals per
50
58
  // plugin. Page-author trust applies; bunite policy/attestation still gates.
@@ -61,8 +69,16 @@ function ensureWebConnection(path = "/rpc"): Promise<Connection> {
61
69
  const ws = new WebSocket(`${proto}//${location.host}${path}`);
62
70
  ws.binaryType = "arraybuffer";
63
71
  await new Promise<void>((resolve, reject) => {
64
- ws.addEventListener("open", () => resolve(), { once: true });
65
- ws.addEventListener("error", () => reject(new Error("web RPC ws connect failed")), { once: true });
72
+ const onOpen = () => {
73
+ ws.removeEventListener("error", onError);
74
+ resolve();
75
+ };
76
+ const onError = () => {
77
+ ws.removeEventListener("open", onOpen);
78
+ reject(new Error("web RPC ws connect failed"));
79
+ };
80
+ ws.addEventListener("open", onOpen, { once: true });
81
+ ws.addEventListener("error", onError, { once: true });
66
82
  });
67
83
  const conn = createConnection({
68
84
  transport: createFrameTransport(createWebSocketPipe(ws as unknown as WebSocketLike)),
@@ -79,9 +95,11 @@ function ensureWebConnection(path = "/rpc"): Promise<Connection> {
79
95
  return attempt;
80
96
  }
81
97
 
82
- export function bootstrap<C extends CapDef<any, any>>(cap: C): Promise<ClientOf<C>>;
83
- export function bootstrap<R extends SchemaRoots>(schema: Schema<R>): Promise<{ [K in keyof R]: ClientOf<R[K]> }>;
84
- export async function bootstrap(target: CapDef<any, any> | Schema<any>): Promise<unknown> {
98
+ export function bootstrap<C extends AnyCapDef>(cap: C): Promise<ClientOf<C>>;
99
+ export function bootstrap<R extends SchemaRoots>(
100
+ schema: Schema<R>,
101
+ ): Promise<{ [K in keyof R]: ClientOf<R[K]> }>;
102
+ export async function bootstrap(target: AnyCapDef | Schema<any>): Promise<unknown> {
85
103
  if (isNative()) {
86
104
  if (!window.host?.bootstrap) throw new Error("host preload not ready");
87
105
  return (window.host.bootstrap as (t: unknown) => Promise<unknown>)(target);
package/src/rpc/schema.ts CHANGED
@@ -10,31 +10,37 @@ declare const EXPORTED_CAP_BRAND: unique symbol;
10
10
 
11
11
  export type ReturnsKind = "type" | "cap" | "capArray" | "capRecord";
12
12
 
13
- export type CapRefToken<C extends CapDef<any, any>> = {
13
+ export type CapRefToken<C extends AnyCapDef> = {
14
14
  readonly [CAP_REF_TAG]: true;
15
15
  readonly cap: C;
16
16
  };
17
17
 
18
- export type CapArrayToken<C extends CapDef<any, any>> = {
18
+ export type CapArrayToken<C extends AnyCapDef> = {
19
19
  readonly [CAP_ARRAY_TAG]: true;
20
20
  readonly cap: C;
21
21
  };
22
22
 
23
- export type CapRecordToken<C extends CapDef<any, any>> = {
23
+ export type CapRecordToken<C extends AnyCapDef> = {
24
24
  readonly [CAP_RECORD_TAG]: true;
25
25
  readonly cap: C;
26
26
  };
27
27
 
28
- export type AnyCapToken<C extends CapDef<any, any> = CapDef<any, any>> =
28
+ export type AnyCapToken<C extends AnyCapDef = AnyCapDef> =
29
29
  | CapRefToken<C>
30
30
  | CapArrayToken<C>
31
31
  | CapRecordToken<C>;
32
32
 
33
- function _cap<C extends CapDef<any, any>>(c: C): CapRefToken<C> {
33
+ function _cap<C extends AnyCapDef>(c: C): CapRefToken<C> {
34
34
  return { [CAP_REF_TAG]: true, cap: c };
35
35
  }
36
- _cap.array = <C extends CapDef<any, any>>(c: C): CapArrayToken<C> => ({ [CAP_ARRAY_TAG]: true, cap: c });
37
- _cap.record = <C extends CapDef<any, any>>(c: C): CapRecordToken<C> => ({ [CAP_RECORD_TAG]: true, cap: c });
36
+ _cap.array = <C extends AnyCapDef>(c: C): CapArrayToken<C> => ({
37
+ [CAP_ARRAY_TAG]: true,
38
+ cap: c,
39
+ });
40
+ _cap.record = <C extends AnyCapDef>(c: C): CapRecordToken<C> => ({
41
+ [CAP_RECORD_TAG]: true,
42
+ cap: c,
43
+ });
38
44
  export const cap = _cap;
39
45
 
40
46
  export function isCapRef(v: unknown): v is CapRefToken<any> {
@@ -75,15 +81,18 @@ export function isStreamDef(v: unknown): v is StreamDef {
75
81
  return typeof v === "object" && v !== null && (v as any)[STREAM_TAG] === true;
76
82
  }
77
83
 
78
- export function call<P, C extends CapDef<any, any>>(
79
- opts: { returns: CapRefToken<C>; idempotent?: boolean }
80
- ): CallDef<P, CapRefToken<C>>;
81
- export function call<P, C extends CapDef<any, any>>(
82
- opts: { returns: CapArrayToken<C>; idempotent?: boolean }
83
- ): CallDef<P, CapArrayToken<C>>;
84
- export function call<P, C extends CapDef<any, any>>(
85
- opts: { returns: CapRecordToken<C>; idempotent?: boolean }
86
- ): CallDef<P, CapRecordToken<C>>;
84
+ export function call<P, C extends AnyCapDef>(opts: {
85
+ returns: CapRefToken<C>;
86
+ idempotent?: boolean;
87
+ }): CallDef<P, CapRefToken<C>>;
88
+ export function call<P, C extends AnyCapDef>(opts: {
89
+ returns: CapArrayToken<C>;
90
+ idempotent?: boolean;
91
+ }): CallDef<P, CapArrayToken<C>>;
92
+ export function call<P, C extends AnyCapDef>(opts: {
93
+ returns: CapRecordToken<C>;
94
+ idempotent?: boolean;
95
+ }): CallDef<P, CapRecordToken<C>>;
87
96
  export function call<P = void, R = void>(opts?: { idempotent?: boolean }): CallDef<P, R>;
88
97
  export function call(opts?: { idempotent?: boolean; returns?: AnyCapToken }): CallDef<any, any> {
89
98
  return {
@@ -93,7 +102,9 @@ export function call(opts?: { idempotent?: boolean; returns?: AnyCapToken }): Ca
93
102
  };
94
103
  }
95
104
 
96
- export function stream<P = void, Y = unknown>(opts?: { hint?: Record<string, unknown> }): StreamDef<P, Y> {
105
+ export function stream<P = void, Y = unknown>(opts?: {
106
+ hint?: Record<string, unknown>;
107
+ }): StreamDef<P, Y> {
97
108
  return { [STREAM_TAG]: true, hint: opts?.hint };
98
109
  }
99
110
 
@@ -104,7 +115,10 @@ export interface DisposalSpec<M extends MethodsRecord = MethodsRecord> {
104
115
 
105
116
  export type MethodsRecord = Record<string, MethodDef>;
106
117
 
107
- export interface CapDef<M extends MethodsRecord = MethodsRecord, D extends DisposalSpec<M> | undefined = undefined> {
118
+ export interface CapDef<
119
+ M extends MethodsRecord = MethodsRecord,
120
+ D extends DisposalSpec<M> | undefined = undefined,
121
+ > {
108
122
  readonly [CAP_TAG]: true;
109
123
  readonly name: string;
110
124
  readonly version?: string;
@@ -112,16 +126,17 @@ export interface CapDef<M extends MethodsRecord = MethodsRecord, D extends Dispo
112
126
  readonly disposal: D;
113
127
  }
114
128
 
129
+ export type AnyCapDef = CapDef<any, any>;
130
+
115
131
  export interface DefineCapOpts<M extends MethodsRecord, D extends DisposalSpec<M> | undefined> {
116
132
  version?: string | number;
117
133
  disposal?: D;
118
134
  }
119
135
 
120
- export function defineCap<M extends MethodsRecord, D extends DisposalSpec<M> | undefined = undefined>(
121
- name: string,
122
- methods: M,
123
- opts?: DefineCapOpts<M, D>
124
- ): CapDef<M, D> {
136
+ export function defineCap<
137
+ M extends MethodsRecord,
138
+ D extends DisposalSpec<M> | undefined = undefined,
139
+ >(name: string, methods: M, opts?: DefineCapOpts<M, D>): CapDef<M, D> {
125
140
  return {
126
141
  [CAP_TAG]: true,
127
142
  name,
@@ -136,7 +151,7 @@ export function isCapDef(v: unknown): v is CapDef {
136
151
  }
137
152
 
138
153
  // Schema = grouping sugar (Record<rootName, CapDef>). TS atomicity for serveAll.
139
- export type SchemaRoots = Record<string, CapDef<any, any>>;
154
+ export type SchemaRoots = Record<string, AnyCapDef>;
140
155
 
141
156
  export interface Schema<R extends SchemaRoots = SchemaRoots> {
142
157
  readonly [SCHEMA_TAG]: true;
@@ -155,7 +170,7 @@ export function isSchema(v: unknown): v is Schema {
155
170
  return typeof v === "object" && v !== null && (v as any)[SCHEMA_TAG] === true;
156
171
  }
157
172
 
158
- export interface ExportedCap<C extends CapDef<any, any>> {
173
+ export interface ExportedCap<C extends AnyCapDef> {
159
174
  readonly [EXPORTED_CAP_BRAND]: true;
160
175
  readonly cap: C;
161
176
  readonly capId: number;
@@ -168,7 +183,7 @@ export interface CallCtx {
168
183
  attestation: Attestation;
169
184
  signal: AbortSignal;
170
185
  deadline?: number;
171
- exportCap<C extends CapDef<any, any>>(capDef: C, impl: ImplOf<C>): ExportedCap<C>;
186
+ exportCap<C extends AnyCapDef>(capDef: C, impl: ImplOf<C>): ExportedCap<C>;
172
187
  }
173
188
 
174
189
  export interface Attestation {
@@ -184,45 +199,55 @@ export interface Attestation {
184
199
  type MaybePromise<T> = T | Promise<T>;
185
200
 
186
201
  type ServerReturn<R> =
187
- R extends CapRefToken<infer C> ? ExportedCap<C> :
188
- R extends CapArrayToken<infer C> ? ExportedCap<C>[] :
189
- R extends CapRecordToken<infer C> ? Record<string, ExportedCap<C>> :
190
- R;
202
+ R extends CapRefToken<infer C>
203
+ ? ExportedCap<C>
204
+ : R extends CapArrayToken<infer C>
205
+ ? ExportedCap<C>[]
206
+ : R extends CapRecordToken<infer C>
207
+ ? Record<string, ExportedCap<C>>
208
+ : R;
191
209
 
192
210
  export type ClientReturn<R> =
193
- R extends CapRefToken<infer C> ? ClientOf<C> :
194
- R extends CapArrayToken<infer C> ?
195
- C extends CapDef<any, infer D>
196
- ? [D] extends [DisposalSpec] ? ClientOf<C>[] & Disposable
197
- : ClientOf<C>[]
198
- : ClientOf<C>[] :
199
- R extends CapRecordToken<infer C> ? Record<string, ClientOf<C>> :
200
- R;
211
+ R extends CapRefToken<infer C>
212
+ ? ClientOf<C>
213
+ : R extends CapArrayToken<infer C>
214
+ ? C extends CapDef<any, infer D>
215
+ ? [D] extends [DisposalSpec]
216
+ ? ClientOf<C>[] & Disposable
217
+ : ClientOf<C>[]
218
+ : ClientOf<C>[]
219
+ : R extends CapRecordToken<infer C>
220
+ ? Record<string, ClientOf<C>>
221
+ : R;
201
222
 
202
223
  export type Stream<T> = AsyncIterable<T> & Disposable & { cancel(): void };
203
224
 
204
- export type ClientOf<T> = T extends CapDef<infer M, infer D>
205
- ? {
206
- [K in keyof M]: M[K] extends CallDef<infer P, infer R>
207
- ? [P] extends [void] ? () => Promise<ClientReturn<R>>
208
- : (params: P) => Promise<ClientReturn<R>>
209
- : M[K] extends StreamDef<infer P, infer Y>
210
- ? [P] extends [void] ? () => Stream<Y>
211
- : (params: P) => Stream<Y>
212
- : never;
213
- } & ([D] extends [DisposalSpec] ? Disposable : {})
214
- : never;
215
-
216
- export type ImplOf<T> = T extends CapDef<infer M, any>
217
- ? {
218
- [K in keyof M]: M[K] extends CallDef<infer P, infer R>
219
- ? (params: P, ctx: CallCtx) => MaybePromise<ServerReturn<R>>
220
- : M[K] extends StreamDef<infer P, infer Y>
221
- ? (params: P, ctx: CallCtx) => Stream<Y>
222
- : never;
223
- }
224
- : never;
225
-
226
- export function methodKeys(cap: CapDef<any, any>): string[] {
225
+ export type ClientOf<T> =
226
+ T extends CapDef<infer M, infer D>
227
+ ? {
228
+ [K in keyof M]: M[K] extends CallDef<infer P, infer R>
229
+ ? [P] extends [void]
230
+ ? () => Promise<ClientReturn<R>>
231
+ : (params: P) => Promise<ClientReturn<R>>
232
+ : M[K] extends StreamDef<infer P, infer Y>
233
+ ? [P] extends [void]
234
+ ? () => Stream<Y>
235
+ : (params: P) => Stream<Y>
236
+ : never;
237
+ } & ([D] extends [DisposalSpec] ? Disposable : unknown)
238
+ : never;
239
+
240
+ export type ImplOf<T> =
241
+ T extends CapDef<infer M, any>
242
+ ? {
243
+ [K in keyof M]: M[K] extends CallDef<infer P, infer R>
244
+ ? (params: P, ctx: CallCtx) => MaybePromise<ServerReturn<R>>
245
+ : M[K] extends StreamDef<infer P, infer Y>
246
+ ? (params: P, ctx: CallCtx) => Stream<Y>
247
+ : never;
248
+ }
249
+ : never;
250
+
251
+ export function methodKeys(cap: AnyCapDef): string[] {
227
252
  return Object.keys(cap.methods);
228
253
  }
package/src/rpc/stream.ts CHANGED
@@ -4,7 +4,10 @@ type Setup<T> = (emit: (chunk: T) => void, signal: AbortSignal) => void | (() =>
4
4
 
5
5
  class ServerStream<T> implements AsyncIterable<T>, Disposable {
6
6
  private readonly buffer: T[] = [];
7
- private readonly waiters: Array<{ resolve: (r: IteratorResult<T>) => void; reject: (e: unknown) => void }> = [];
7
+ private readonly waiters: Array<{
8
+ resolve: (r: IteratorResult<T>) => void;
9
+ reject: (e: unknown) => void;
10
+ }> = [];
8
11
  private readonly ctrl = new AbortController();
9
12
  private cleanup?: () => void;
10
13
  private ended = false;
@@ -36,7 +39,9 @@ class ServerStream<T> implements AsyncIterable<T>, Disposable {
36
39
  }
37
40
  if (this.failure) throw this.failure;
38
41
  if (this.ended) return { value: undefined as unknown as T, done: true };
39
- return new Promise<IteratorResult<T>>((resolve, reject) => this.waiters.push({ resolve, reject }));
42
+ return new Promise<IteratorResult<T>>((resolve, reject) =>
43
+ this.waiters.push({ resolve, reject }),
44
+ );
40
45
  },
41
46
  return: async (): Promise<IteratorResult<T>> => {
42
47
  this.dispose();
@@ -57,7 +62,11 @@ class ServerStream<T> implements AsyncIterable<T>, Disposable {
57
62
  if (this.ended) return;
58
63
  this.ended = true;
59
64
  this.ctrl.abort();
60
- try { this.cleanup?.(); } catch { /* swallow */ }
65
+ try {
66
+ this.cleanup?.();
67
+ } catch {
68
+ /* swallow */
69
+ }
61
70
  while (this.waiters.length > 0) {
62
71
  const w = this.waiters.shift()!;
63
72
  w.resolve({ value: undefined as unknown as T, done: true });
@@ -1,10 +1,11 @@
1
- import { type Frame, createCodec, isFrame } from "./wire";
2
- import type { Transport } from "./peer";
1
+ import type { CloseInfo, Transport } from "./peer";
2
+ import { createCodec, type Frame, isFrame } from "./wire";
3
3
 
4
4
  export interface BytesPipe {
5
5
  send(bytes: Uint8Array): void;
6
6
  setReceive(handler: (bytes: Uint8Array) => void): void;
7
7
  close(): void;
8
+ onClose?(handler: (info?: CloseInfo) => void): void;
8
9
  }
9
10
 
10
11
  export interface FrameTransportOptions {
@@ -14,6 +15,16 @@ export interface FrameTransportOptions {
14
15
  export function createFrameTransport(pipe: BytesPipe, opts: FrameTransportOptions = {}): Transport {
15
16
  const codec = createCodec();
16
17
  let handler: ((frame: Frame) => void) | undefined;
18
+ let closeHandler: ((info?: CloseInfo) => void) | undefined;
19
+ let closeFired = false;
20
+ // Fire once for any close cause — pipe disconnect (WS close/error) or a
21
+ // protocol error that forces pipe.close() — so the Connection always tears down.
22
+ const fireClose = (info?: CloseInfo) => {
23
+ if (closeFired) return;
24
+ closeFired = true;
25
+ closeHandler?.(info);
26
+ };
27
+ pipe.onClose?.(fireClose);
17
28
 
18
29
  pipe.setReceive((bytes) => {
19
30
  let frame: unknown;
@@ -21,11 +32,13 @@ export function createFrameTransport(pipe: BytesPipe, opts: FrameTransportOption
21
32
  frame = codec.unpackr.unpack(bytes);
22
33
  } catch (err) {
23
34
  opts.onProtocolError?.(`unpack failed: ${err instanceof Error ? err.message : String(err)}`);
35
+ fireClose();
24
36
  pipe.close();
25
37
  return;
26
38
  }
27
39
  if (!isFrame(frame)) {
28
40
  opts.onProtocolError?.("malformed frame");
41
+ fireClose();
29
42
  pipe.close();
30
43
  return;
31
44
  }
@@ -43,39 +56,90 @@ export function createFrameTransport(pipe: BytesPipe, opts: FrameTransportOption
43
56
  close() {
44
57
  pipe.close();
45
58
  },
59
+ onClose(h) {
60
+ closeHandler = h;
61
+ },
46
62
  };
47
63
  }
48
64
 
65
+ type WsMessageEvent = { data: ArrayBuffer | Uint8Array | Blob | string };
66
+ type WsCloseEvent = { code?: number; reason?: string };
67
+
49
68
  export interface WebSocketLike {
50
69
  send(data: Uint8Array | ArrayBuffer): unknown;
51
- addEventListener(type: "message", listener: (event: { data: ArrayBuffer | Uint8Array | Blob | string }) => void): void;
52
- removeEventListener?(type: "message", listener: (event: { data: ArrayBuffer | Uint8Array | Blob | string }) => void): void;
70
+ addEventListener(type: "message", listener: (event: WsMessageEvent) => void): void;
71
+ removeEventListener?(type: "message", listener: (event: WsMessageEvent) => void): void;
53
72
  close?(): void;
54
73
  }
55
74
 
75
+ // close/error listeners — real WebSockets have them; kept off the public type so
76
+ // message-only implementations stay assignable.
77
+ type WsLifecycle = {
78
+ addEventListener(type: "close", listener: (event: WsCloseEvent) => void): void;
79
+ addEventListener(type: "error", listener: (event: unknown) => void): void;
80
+ removeEventListener?(type: "close", listener: (event: WsCloseEvent) => void): void;
81
+ removeEventListener?(type: "error", listener: (event: unknown) => void): void;
82
+ };
83
+
56
84
  export function createWebSocketPipe(ws: WebSocketLike): BytesPipe {
57
85
  if ("binaryType" in ws) {
58
- try { (ws as { binaryType?: string }).binaryType = "arraybuffer"; } catch { /* readonly in some envs */ }
86
+ try {
87
+ (ws as { binaryType?: string }).binaryType = "arraybuffer";
88
+ } catch {
89
+ /* readonly in some envs */
90
+ }
59
91
  }
92
+ const wsLife = ws as WebSocketLike & WsLifecycle;
60
93
  let handler: ((bytes: Uint8Array) => void) | undefined;
61
- const onMessage = (event: { data: ArrayBuffer | Uint8Array | Blob | string }) => {
94
+ let closeHandler: ((info?: CloseInfo) => void) | undefined;
95
+ let closeFired = false;
96
+ const onMessage = (event: WsMessageEvent) => {
62
97
  if (!handler) return;
63
98
  const d = event.data;
64
- if (d instanceof Uint8Array) { handler(d); return; }
65
- if (d instanceof ArrayBuffer) { handler(new Uint8Array(d)); return; }
99
+ if (d instanceof Uint8Array) {
100
+ handler(d);
101
+ return;
102
+ }
103
+ if (d instanceof ArrayBuffer) {
104
+ handler(new Uint8Array(d));
105
+ return;
106
+ }
66
107
  if (typeof Blob !== "undefined" && d instanceof Blob) {
67
108
  void d.arrayBuffer().then((buf) => handler?.(new Uint8Array(buf)));
68
109
  return;
69
110
  }
70
111
  };
112
+ const detach = () => {
113
+ ws.removeEventListener?.("message", onMessage);
114
+ wsLife.removeEventListener?.("close", onClose);
115
+ wsLife.removeEventListener?.("error", onError);
116
+ };
117
+ const fireClose = (info?: CloseInfo) => {
118
+ if (closeFired) return;
119
+ closeFired = true;
120
+ detach();
121
+ closeHandler?.(info);
122
+ };
123
+ const onClose = (event: WsCloseEvent) => fireClose({ code: event?.code, reason: event?.reason });
124
+ // `error` is usually followed by `close` (which carries code/reason); defer so
125
+ // close wins, and this still fires for an error with no following close.
126
+ const onError = () => setTimeout(() => fireClose(), 0);
71
127
  ws.addEventListener("message", onMessage);
128
+ wsLife.addEventListener("close", onClose);
129
+ wsLife.addEventListener("error", onError);
72
130
  return {
73
- send(bytes) { ws.send(bytes); },
74
- setReceive(h) { handler = h; },
131
+ send(bytes) {
132
+ ws.send(bytes);
133
+ },
134
+ setReceive(h) {
135
+ handler = h;
136
+ },
137
+ onClose(h) {
138
+ closeHandler = h;
139
+ },
75
140
  close() {
76
- ws.removeEventListener?.("message", onMessage);
141
+ detach();
77
142
  ws.close?.();
78
143
  },
79
144
  };
80
145
  }
81
-
package/src/rpc/wire.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Packr, Unpackr, addExtension } from "msgpackr";
1
+ import { addExtension, Packr, Unpackr } from "msgpackr";
2
2
  import type { IpcStatus } from "./error";
3
3
 
4
4
  export type u32 = number;
@@ -75,28 +75,41 @@ export function isFrame(value: unknown): value is Frame {
75
75
  if (typeof op !== "string" || !OPS.has(op as Frame["op"])) return false;
76
76
  switch (op as Frame["op"]) {
77
77
  case "hello":
78
- return f.v === 1 && (f.mode === "native" || f.mode === "web")
79
- && Array.isArray(f.features) && typeof f.maxBytes === "number" && typeof f.origin === "string";
78
+ return (
79
+ f.v === 1 &&
80
+ (f.mode === "native" || f.mode === "web") &&
81
+ Array.isArray(f.features) &&
82
+ typeof f.maxBytes === "number" &&
83
+ typeof f.origin === "string"
84
+ );
80
85
  case "goaway":
81
86
  return f.reason === undefined || typeof f.reason === "string";
82
87
  case "call":
83
- return typeof f.id === "number" && typeof f.method === "string"
84
- && typeof f.target === "object" && f.target !== null
85
- && (f.target as { kind?: unknown }).kind === "cap"
86
- && typeof (f.target as { id?: unknown }).id === "number";
88
+ return (
89
+ typeof f.id === "number" &&
90
+ typeof f.method === "string" &&
91
+ typeof f.target === "object" &&
92
+ f.target !== null &&
93
+ (f.target as { kind?: unknown }).kind === "cap" &&
94
+ typeof (f.target as { id?: unknown }).id === "number"
95
+ );
87
96
  case "result":
88
97
  return typeof f.id === "number" && (f.ok === true || f.ok === false);
89
98
  case "cancel":
90
99
  return typeof f.id === "number";
91
100
  case "stream":
92
- return typeof f.id === "number" && typeof f.ev === "string"
93
- && (f.ev === "next" || f.ev === "credit" || f.ev === "end" || f.ev === "error");
101
+ return (
102
+ typeof f.id === "number" &&
103
+ typeof f.ev === "string" &&
104
+ (f.ev === "next" || f.ev === "credit" || f.ev === "end" || f.ev === "error")
105
+ );
94
106
  case "drop":
95
107
  return Array.isArray(f.caps);
96
108
  case "cap_revoked":
97
109
  if (!Array.isArray(f.capIds)) return false;
98
110
  for (const id of f.capIds) {
99
- if (typeof id !== "number" || !Number.isInteger(id) || id < 0 || id > 0xFFFFFFFF) return false;
111
+ if (typeof id !== "number" || !Number.isInteger(id) || id < 0 || id > 0xffffffff)
112
+ return false;
100
113
  }
101
114
  return true;
102
115
  }