@phpsandbox/sdk 0.0.32 → 0.0.34

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/README.md CHANGED
@@ -79,12 +79,20 @@ await fetched.ready();
79
79
 
80
80
  const forked = await created.fork();
81
81
  await forked.delete();
82
+
83
+ const persistent = await client.notebook.create('laravel', {
84
+ title: 'Persistent Laravel',
85
+ persistent: true,
86
+ });
87
+
88
+ console.log(persistent.data.policy);
82
89
  ```
83
90
 
84
91
  Notes:
85
92
 
86
93
  - `create()` and `fork()` initialize automatically.
87
94
  - `open()` and `get()` return a notebook instance; call `await notebook.ready()` before using tools.
95
+ - API notebook creates are ephemeral by default. `persistent: true` requires an entitled account.
88
96
 
89
97
  ## Services At A Glance
90
98
 
@@ -103,6 +111,12 @@ Each `NotebookInstance` exposes service clients:
103
111
  - `notebook.log` (`Log`)
104
112
  - `notebook.services` (`Services`)
105
113
 
114
+ Notes:
115
+
116
+ - `terminal.spawn()` and `shell.exec()` are part of the current okra websocket action set. `terminal.start` currently is not.
117
+ - `notebook.repl.eval()` is part of the current okra websocket action set. `repl.start` is currently not.
118
+ - `notebook.laravel` is exposed by the SDK, but the current okra websocket action set does not expose Laravel maintenance actions.
119
+
106
120
  ## Common Operations
107
121
 
108
122
  ### Files
@@ -639,19 +639,30 @@ var Terminal = class {
639
639
  },
640
640
  close: dispose
641
641
  });
642
+ let controller = null;
642
643
  const output = new ReadableStream({
643
- start: (controller) => {
644
+ start: (_controller) => {
645
+ controller = _controller;
644
646
  disposables.add(
645
647
  this.listen(`terminal.output.${id}`, (data) => {
646
- controller.enqueue(data.output);
648
+ controller?.enqueue(data.output);
647
649
  })
648
650
  );
649
651
  },
650
- cancel: dispose
652
+ cancel: () => {
653
+ controller = null;
654
+ dispose();
655
+ }
651
656
  });
652
657
  const exit = new Promise((resolve) => {
653
658
  disposables.add(
654
659
  this.listen(`terminal.close.${id}`, (data) => {
660
+ if (controller) {
661
+ try {
662
+ controller.close();
663
+ } catch {
664
+ }
665
+ }
655
666
  dispose();
656
667
  resolve(data.exitCode);
657
668
  })
@@ -3091,6 +3102,89 @@ var ReconnectingWebSocket = (
3091
3102
  );
3092
3103
  var reconnecting_websocket_mjs_default = ReconnectingWebSocket;
3093
3104
 
3105
+ // src/utils/websocket.ts
3106
+ var getReadyStateLabel = (state) => {
3107
+ if (typeof state !== "number") {
3108
+ return void 0;
3109
+ }
3110
+ return {
3111
+ 0: "CONNECTING",
3112
+ 1: "OPEN",
3113
+ 2: "CLOSING",
3114
+ 3: "CLOSED"
3115
+ }[state] ?? `STATE_${state}`;
3116
+ };
3117
+ var sanitizeWebSocketUrl = (value) => {
3118
+ try {
3119
+ const url = new URL(value);
3120
+ return `${url.origin}${url.pathname}`;
3121
+ } catch {
3122
+ return value;
3123
+ }
3124
+ };
3125
+ var extractNestedErrorMessage = (value) => {
3126
+ if (value instanceof Error) {
3127
+ return value.message ? `${value.name}: ${value.message}` : value.name;
3128
+ }
3129
+ if (typeof value === "string") {
3130
+ return value;
3131
+ }
3132
+ if (!value || typeof value !== "object") {
3133
+ return void 0;
3134
+ }
3135
+ const record = value;
3136
+ const message = typeof record.message === "string" && record.message ? record.message : typeof record.reason === "string" && record.reason ? record.reason : void 0;
3137
+ if (!message) {
3138
+ return void 0;
3139
+ }
3140
+ const name = typeof record.name === "string" && record.name ? record.name : void 0;
3141
+ return name ? `${name}: ${message}` : message;
3142
+ };
3143
+ var describeWebSocketEvent = (event) => {
3144
+ if (event instanceof Error) {
3145
+ return event.message ? `${event.name}: ${event.message}` : event.name;
3146
+ }
3147
+ if (typeof event === "string") {
3148
+ return event;
3149
+ }
3150
+ if (!event || typeof event !== "object") {
3151
+ return String(event);
3152
+ }
3153
+ const record = event;
3154
+ const parts = [];
3155
+ const topLevelMessage = extractNestedErrorMessage(record);
3156
+ if (topLevelMessage) {
3157
+ parts.push(topLevelMessage);
3158
+ }
3159
+ const nestedErrorMessage = extractNestedErrorMessage(record.error);
3160
+ if (nestedErrorMessage && nestedErrorMessage !== topLevelMessage) {
3161
+ parts.push(`error=${nestedErrorMessage}`);
3162
+ }
3163
+ if (typeof record.type === "string" && record.type) {
3164
+ parts.push(`type=${record.type}`);
3165
+ }
3166
+ if (typeof record.code === "number") {
3167
+ parts.push(`code=${record.code}`);
3168
+ }
3169
+ if (typeof record.reason === "string" && record.reason && record.reason !== topLevelMessage) {
3170
+ parts.push(`reason=${record.reason}`);
3171
+ }
3172
+ if (typeof record.wasClean === "boolean") {
3173
+ parts.push(`wasClean=${record.wasClean}`);
3174
+ }
3175
+ const target = record.target ?? record.currentTarget;
3176
+ if (target && typeof target === "object") {
3177
+ if (typeof target.url === "string" && target.url) {
3178
+ parts.push(`url=${sanitizeWebSocketUrl(target.url)}`);
3179
+ }
3180
+ const readyState = getReadyStateLabel(target.readyState);
3181
+ if (readyState) {
3182
+ parts.push(`readyState=${readyState}`);
3183
+ }
3184
+ }
3185
+ return parts.length > 0 ? parts.join(" | ") : "Unknown websocket event";
3186
+ };
3187
+
3094
3188
  // node_modules/isomorphic-ws/browser.js
3095
3189
  var ws = null;
3096
3190
  if (typeof WebSocket !== "undefined") {
@@ -3128,6 +3222,13 @@ var NamedDisposable = class {
3128
3222
  };
3129
3223
 
3130
3224
  // src/socket/index.ts
3225
+ var ConnectionFailedError = class extends Error {
3226
+ constructor(message, originalError) {
3227
+ super(message);
3228
+ this.originalError = originalError;
3229
+ this.name = "ConnectionFailedError";
3230
+ }
3231
+ };
3131
3232
  var InvalidMessageError = class extends Error {
3132
3233
  constructor(message, data) {
3133
3234
  super(message);
@@ -3182,7 +3283,7 @@ var Transport = class {
3182
3283
  this.connectionStats.connectionStartTime = Date.now();
3183
3284
  const startClosed = options.startClosed !== false;
3184
3285
  this.rws = new reconnecting_websocket_mjs_default(this.url.toString(), [], {
3185
- WebSocket: globalThis.WebSocket ?? browser_default,
3286
+ WebSocket: options.webSocket ?? globalThis.WebSocket ?? browser_default,
3186
3287
  connectionTimeout: options.connectionTimeout,
3187
3288
  maxReconnectionDelay: 2e3,
3188
3289
  minReconnectionDelay: 200,
@@ -3237,7 +3338,19 @@ var Transport = class {
3237
3338
  async registerWatchers() {
3238
3339
  const onMessage = (ev) => {
3239
3340
  if (!(ev.data instanceof Blob)) {
3240
- throw new Error("Unexpected message type: " + typeof ev.data);
3341
+ const error = new Error("Unexpected message type: " + typeof ev.data);
3342
+ this.connectionStats.totalErrors++;
3343
+ this.log("error", "Unexpected WebSocket message type", {
3344
+ error: error.message,
3345
+ messageType: typeof ev.data
3346
+ });
3347
+ this.eventEmitter.emit("transport.error", {
3348
+ type: "message_type_error",
3349
+ error,
3350
+ rawMessage: ev.data,
3351
+ timestamp: Date.now()
3352
+ });
3353
+ return;
3241
3354
  }
3242
3355
  ev.data.arrayBuffer().then((buffer) => {
3243
3356
  if (buffer.byteLength === 0) {
@@ -3245,7 +3358,19 @@ var Transport = class {
3245
3358
  return;
3246
3359
  }
3247
3360
  try {
3248
- this.handleRawMessage(decode(buffer));
3361
+ void this.handleRawMessage(decode(buffer)).catch((error) => {
3362
+ this.connectionStats.totalErrors++;
3363
+ this.log("error", "Failed to process WebSocket frame", {
3364
+ error: error instanceof Error ? error.message : String(error),
3365
+ byteLength: buffer.byteLength
3366
+ });
3367
+ this.eventEmitter.emit("transport.error", {
3368
+ type: "message_handle_error",
3369
+ error,
3370
+ rawMessage: buffer,
3371
+ timestamp: Date.now()
3372
+ });
3373
+ });
3249
3374
  } catch (error) {
3250
3375
  this.connectionStats.totalErrors++;
3251
3376
  this.log("error", "Failed to decode WebSocket frame", {
@@ -3269,6 +3394,10 @@ var Transport = class {
3269
3394
  });
3270
3395
  }
3271
3396
  async handleRawMessage(ev) {
3397
+ if (this.closed) {
3398
+ this.log("debug", "Ignoring message received after transport was closed");
3399
+ return;
3400
+ }
3272
3401
  if (typeof ev !== "object" || ev === null) {
3273
3402
  this.log("debug", "Received invalid message format", { ev });
3274
3403
  return;
@@ -3466,7 +3595,8 @@ var Transport = class {
3466
3595
  reject(new RateLimitError(_ev.reason || "Rate limit exceeded", _ev));
3467
3596
  return;
3468
3597
  }
3469
- reject(this.terminalError || new Error(`Connection lost to the notebook during request: ${_ev.reason || "Unknown reason"}`));
3598
+ const detail = describeWebSocketEvent(_ev);
3599
+ reject(this.terminalError || new ConnectionFailedError(`Connection lost to the notebook during request: ${detail}`, _ev));
3470
3600
  };
3471
3601
  this.rws.addEventListener("close", closeHandler);
3472
3602
  this.rws.addEventListener("error", closeHandler);
@@ -3654,8 +3784,10 @@ var Transport = class {
3654
3784
  });
3655
3785
  this.rws.addEventListener("error", (event) => {
3656
3786
  this.connectionStats.totalErrors++;
3787
+ const detail = describeWebSocketEvent(event);
3657
3788
  this.log("error", "Connection error", {
3658
- error: event,
3789
+ error: detail,
3790
+ rawError: event,
3659
3791
  totalErrors: this.connectionStats.totalErrors
3660
3792
  });
3661
3793
  });
@@ -3928,7 +4060,8 @@ connect_fn = function() {
3928
4060
  this.rws.removeEventListener("error", errorHandler);
3929
4061
  clearTimeout(timeoutId);
3930
4062
  this.connectPromise = null;
3931
- reject(new Error(`WebSocket connection failed: ${error}`));
4063
+ const detail = describeWebSocketEvent(error);
4064
+ reject(new ConnectionFailedError(`WebSocket connection failed: ${detail}`, error));
3932
4065
  };
3933
4066
  timeoutId = setTimeout(() => {
3934
4067
  this.rws.removeEventListener("open", openHandler);
@@ -4930,7 +5063,8 @@ var NotebookInstance = class {
4930
5063
  this.emitter = EventManager.createInstance();
4931
5064
  this.socket = new Transport(data.okraUrl, this.emitter, {
4932
5065
  debug: client.options.debug,
4933
- startClosed: client.options.startClosed
5066
+ startClosed: client.options.startClosed,
5067
+ webSocket: client.options.webSocket
4934
5068
  });
4935
5069
  this.watchConnection();
4936
5070
  __privateSet(this, _initPromise, __privateMethod(this, _NotebookInstance_instances, init_fn).call(this));
@@ -5098,6 +5232,8 @@ init_fn = function() {
5098
5232
  reject(error);
5099
5233
  });
5100
5234
  }));
5235
+ void __privateGet(this, _initPromise).catch(() => {
5236
+ });
5101
5237
  return __privateGet(this, _initPromise);
5102
5238
  };
5103
5239
  var NotebookSecrets = class {