@get-bb/plugin-sdk 0.4.45 → 0.4.48

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.
@@ -36,6 +36,7 @@ function parseNamespacedGlyph(glyph) {
36
36
 
37
37
  // ../domain/src/plugin-cli.ts
38
38
  var RESERVED_BB_CLI_COMMANDS = [
39
+ "browser",
39
40
  "environment",
40
41
  "file",
41
42
  "guide",
@@ -2311,6 +2312,8 @@ function createFakePluginHostInternal(options, sharedState) {
2311
2312
  }
2312
2313
  };
2313
2314
  const httpRoutes = [];
2315
+ const websocketRoutes = [];
2316
+ const websocketSessions = /* @__PURE__ */ new Set();
2314
2317
  const http = {
2315
2318
  route(method, path, handler, opts) {
2316
2319
  assertLive();
@@ -2344,6 +2347,29 @@ function createFakePluginHostInternal(options, sharedState) {
2344
2347
  );
2345
2348
  }
2346
2349
  httpRoutes.push({ method: normalizedMethod, path, auth, handler });
2350
+ },
2351
+ experimental_websocket(path, handler, opts) {
2352
+ assertLive();
2353
+ if (typeof path !== "string" || !path.startsWith("/")) {
2354
+ throw new Error(
2355
+ `websocket route path must be a string starting with "/", got ${JSON.stringify(path)}`
2356
+ );
2357
+ }
2358
+ if (typeof handler !== "function") {
2359
+ throw new Error(
2360
+ `websocket route handler for ${path} must be a function`
2361
+ );
2362
+ }
2363
+ const auth = opts?.auth ?? "local";
2364
+ if (auth !== "local" && auth !== "token" && auth !== "none") {
2365
+ throw new Error(
2366
+ `invalid auth mode "${String(auth)}" for websocket ${path} \u2014 use "local", "token", or "none"`
2367
+ );
2368
+ }
2369
+ if (websocketRoutes.some((route) => route.path === path)) {
2370
+ throw new Error(`websocket route ${path} is already registered`);
2371
+ }
2372
+ websocketRoutes.push({ path, auth, handler });
2347
2373
  }
2348
2374
  };
2349
2375
  const rpcHandlers = /* @__PURE__ */ new Map();
@@ -3059,6 +3085,9 @@ function createFakePluginHostInternal(options, sharedState) {
3059
3085
  pendingInteractions.delete(id);
3060
3086
  pending.resolve({ outcome: "cancelled", reason: "plugin-disposed" });
3061
3087
  }
3088
+ for (const session of [...websocketSessions]) {
3089
+ await session.closeForReload();
3090
+ }
3062
3091
  for (const controller of serviceControllers) controller.abort();
3063
3092
  for (const hook of [...disposeHooks].reverse()) {
3064
3093
  try {
@@ -3104,6 +3133,7 @@ function createFakePluginHostInternal(options, sharedState) {
3104
3133
  registrations: {
3105
3134
  settingsDescriptors,
3106
3135
  httpRoutes,
3136
+ websocketRoutes,
3107
3137
  get rpcMethods() {
3108
3138
  return [...rpcHandlers.keys()];
3109
3139
  },
@@ -3321,6 +3351,127 @@ function createFakePluginHostInternal(options, sharedState) {
3321
3351
  });
3322
3352
  return app.request(path, { ...init, method: normalizedMethod });
3323
3353
  },
3354
+ async experimental_openWebSocket(path, init) {
3355
+ assertLive();
3356
+ const url = new URL(path, "http://plugin.test");
3357
+ const route = websocketRoutes.find(
3358
+ (candidate) => candidate.path === url.pathname
3359
+ );
3360
+ if (!route) {
3361
+ throw new Error(
3362
+ `no websocket route ${url.pathname} is registered \u2014 registered: ${websocketRoutes.map((candidate) => candidate.path).join(", ") || "(none)"}`
3363
+ );
3364
+ }
3365
+ const request = new Request(url, { ...init, method: "GET" });
3366
+ let handlers;
3367
+ try {
3368
+ handlers = route.handler({
3369
+ request,
3370
+ url,
3371
+ headers: request.headers
3372
+ });
3373
+ if (typeof handlers !== "object" || handlers === null || Array.isArray(handlers)) {
3374
+ throw new Error("websocket route handler must return an object");
3375
+ }
3376
+ for (const name of [
3377
+ "onOpen",
3378
+ "onMessage",
3379
+ "onClose",
3380
+ "onError"
3381
+ ]) {
3382
+ const callback = handlers[name];
3383
+ if (callback !== void 0 && typeof callback !== "function") {
3384
+ throw new Error(
3385
+ `websocket route handler ${name} must be a function`
3386
+ );
3387
+ }
3388
+ }
3389
+ } catch (error) {
3390
+ emitLog(
3391
+ "warn",
3392
+ `websocket ${route.path} connect failed: ${errorMessage(error)}`
3393
+ );
3394
+ throw error;
3395
+ }
3396
+ const sent = [];
3397
+ const closeCalls = [];
3398
+ let readyState = 0;
3399
+ let closeNotified = false;
3400
+ let eventQueue = Promise.resolve();
3401
+ const invoke = async (event, run) => {
3402
+ eventQueue = eventQueue.then(async () => {
3403
+ try {
3404
+ await run();
3405
+ } catch (error) {
3406
+ emitLog(
3407
+ "warn",
3408
+ `websocket ${route.path} ${event} failed: ${errorMessage(error)}`
3409
+ );
3410
+ }
3411
+ });
3412
+ await eventQueue;
3413
+ };
3414
+ const socket = {
3415
+ send(data) {
3416
+ sent.push(typeof data === "string" ? data : new Uint8Array(data));
3417
+ },
3418
+ close(code, reason) {
3419
+ closeCalls.push({ code: code ?? null, reason: reason ?? null });
3420
+ if (readyState < 2) readyState = 2;
3421
+ },
3422
+ get readyState() {
3423
+ return readyState;
3424
+ }
3425
+ };
3426
+ const notifyClose = async (code, reason) => {
3427
+ if (closeNotified) return;
3428
+ closeNotified = true;
3429
+ readyState = 3;
3430
+ websocketSessions.delete(session);
3431
+ if (handlers.onClose !== void 0) {
3432
+ await invoke(
3433
+ "close",
3434
+ () => handlers.onClose?.(socket, { code, reason })
3435
+ );
3436
+ }
3437
+ };
3438
+ const session = {
3439
+ sent,
3440
+ closeCalls,
3441
+ get readyState() {
3442
+ return readyState;
3443
+ },
3444
+ async receive(data) {
3445
+ if (readyState !== 1) {
3446
+ throw new Error(
3447
+ "cannot receive a websocket message while not open"
3448
+ );
3449
+ }
3450
+ if (handlers.onMessage !== void 0) {
3451
+ await invoke("message", () => handlers.onMessage?.(socket, data));
3452
+ }
3453
+ },
3454
+ async close(code = 1e3, reason = "") {
3455
+ if (readyState < 2) socket.close(code, reason);
3456
+ await notifyClose(code, reason);
3457
+ },
3458
+ async error(error) {
3459
+ if (handlers.onError !== void 0) {
3460
+ await invoke("error", () => handlers.onError?.(socket, error));
3461
+ }
3462
+ },
3463
+ async closeForReload() {
3464
+ socket.close(1012, "Plugin reloaded or disabled");
3465
+ await notifyClose(1012, "Plugin reloaded or disabled");
3466
+ }
3467
+ };
3468
+ websocketSessions.add(session);
3469
+ readyState = 1;
3470
+ if (handlers.onOpen !== void 0) {
3471
+ await invoke("open", () => handlers.onOpen?.(socket));
3472
+ }
3473
+ return session;
3474
+ },
3324
3475
  runService(name) {
3325
3476
  const service = services.find((record) => record.name === name);
3326
3477
  if (!service) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@get-bb/plugin-sdk",
3
- "version": "0.4.45",
3
+ "version": "0.4.48",
4
4
  "homepage": "https://github.com/get-bb/bb#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/get-bb/bb/issues"