@solarisdk/core 0.4.5 → 0.4.6

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/dist/handle.js CHANGED
@@ -687,11 +687,10 @@ export class SessionHandle {
687
687
  if (!this.hooks.revert) {
688
688
  return Promise.reject(new Error("revert requires a client-created handle"));
689
689
  }
690
+ const hadSocket = this.channel.hasSocket;
690
691
  return this.hooks.revert(this.id, snapshotId).then(async () => {
691
- if (this.channel.connected) {
692
- this.close({ quiet: true });
693
- await this.channel.connect().catch(() => undefined);
694
- }
692
+ if (hadSocket)
693
+ await this.channel.refresh();
695
694
  });
696
695
  }
697
696
  async pause() {
@@ -81,6 +81,17 @@ export declare class ControlChannel {
81
81
  * `commands.start` / `pty.create` survive so callers can re-subscribe.
82
82
  */
83
83
  reconnect(): Promise<void>;
84
+ /**
85
+ * Swap the socket for a fresh, working one after the guest behind it was replaced in place (revert). The old socket
86
+ * is detached first, so its close, arriving whenever it likes (it may even have arrived already), cannot fail calls
87
+ * made on the new connection. Right after a restore the host can still be re-accepting the guest's control
88
+ * connection and closes a connection opened in that window within milliseconds (seen on staging as the first call
89
+ * failing with 1000/1005), so the new connection is proven with a cheap request and re-dialled a few times before
90
+ * this resolves. Never rejects: if no working connection can be had, calls retry lazily.
91
+ */
92
+ refresh(): Promise<void>;
93
+ /** True while the channel holds a socket, open or not. */
94
+ get hasSocket(): boolean;
84
95
  /** Register a "channel closed" callback for a stream id (cmdId/ptyId) so a
85
96
  * drop rejects its in-flight wait. Cleared on the stream's terminal frame.
86
97
  *
package/dist/transport.js CHANGED
@@ -2,6 +2,9 @@ import { ActionError, ConnectionError, SolariError, TimeoutError, } from "./erro
2
2
  import { openWebSocket } from "./ws.js";
3
3
  const DEFAULT_CALL_TIMEOUT_MS = 300_000;
4
4
  const CONNECT_TIMEOUT_MS = 15_000;
5
+ const REFRESH_MAX_ATTEMPTS = 4;
6
+ const REFRESH_RETRY_BASE_MS = 300;
7
+ const REFRESH_PROBE_TIMEOUT_MS = 5_000;
5
8
  const CONNECT_MAX_ATTEMPTS = 3;
6
9
  const CONNECT_RETRY_BASE_MS = 150;
7
10
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -41,6 +44,7 @@ export class ControlChannel {
41
44
  return Promise.resolve();
42
45
  if (this.connecting)
43
46
  return this.connecting;
47
+ this.#detachSocket();
44
48
  this.closed = false;
45
49
  this.connecting = this.#connectWithRetry();
46
50
  return this.connecting;
@@ -117,15 +121,39 @@ export class ControlChannel {
117
121
  async reconnect() {
118
122
  if (this.connected)
119
123
  return;
120
- if (this.ws) {
121
- const old = this.ws;
122
- this.ws = null;
123
- this.#failAll(new ConnectionError("Control channel replaced by a reconnect"));
124
- old.close();
125
- }
124
+ this.#detachSocket();
126
125
  this.connecting = null;
127
126
  await this.connect();
128
127
  }
128
+ async refresh() {
129
+ for (let attempt = 1; attempt <= REFRESH_MAX_ATTEMPTS; attempt++) {
130
+ if (this.closed)
131
+ return;
132
+ try {
133
+ this.#detachSocket();
134
+ this.connecting = null;
135
+ await this.connect();
136
+ await this.#send("fs.stat", { path: "/" }, { timeoutMs: REFRESH_PROBE_TIMEOUT_MS });
137
+ return;
138
+ }
139
+ catch (err) {
140
+ if (!(err instanceof ConnectionError))
141
+ return;
142
+ await sleep(REFRESH_RETRY_BASE_MS * attempt);
143
+ }
144
+ }
145
+ }
146
+ get hasSocket() {
147
+ return this.ws !== null;
148
+ }
149
+ #detachSocket() {
150
+ if (!this.ws)
151
+ return;
152
+ const old = this.ws;
153
+ this.ws = null;
154
+ this.#failAll(new ConnectionError("Control channel replaced by a reconnect"));
155
+ old.close();
156
+ }
129
157
  onStreamClose(id, cb) {
130
158
  if (this.closed || !this.ws || !this.ws.isOpen) {
131
159
  cb(new ConnectionError("Control channel closed"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solarisdk/core",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Shared runtime for the Solari SDK \u2014 transport, session handles, images, templates, and types used by @solarisdk/desktop and @solarisdk/sandbox.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",