@adhdev/session-host-core 1.0.82-rc.26 → 1.0.82-rc.27
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/index.d.mts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +71 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +70 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/ipc.ts +69 -12
package/src/ipc.ts
CHANGED
|
@@ -124,6 +124,8 @@ export interface SessionHostClientOptions {
|
|
|
124
124
|
endpoint?: SessionHostEndpoint;
|
|
125
125
|
appName?: string;
|
|
126
126
|
requestTimeoutMs?: number;
|
|
127
|
+
/** Join the shared client registry (default true). Disable for short-lived probes. */
|
|
128
|
+
registerAsShared?: boolean;
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
/**
|
|
@@ -155,6 +157,20 @@ export function disconnectSessionHostClients(endpoint?: SessionHostEndpoint, err
|
|
|
155
157
|
}
|
|
156
158
|
}
|
|
157
159
|
|
|
160
|
+
export function findConnectedSessionHostClient(endpoint?: SessionHostEndpoint): SessionHostClient | null {
|
|
161
|
+
for (const ref of activeClientRefs) {
|
|
162
|
+
const client = ref.deref();
|
|
163
|
+
if (!client) {
|
|
164
|
+
activeClientRefs.delete(ref);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if ((!endpoint || client.endpoint.path === endpoint.path) && client.isConnected()) {
|
|
168
|
+
return client;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
158
174
|
export interface SessionHostDisconnectInfo {
|
|
159
175
|
reason: SessionHostDisconnectReason;
|
|
160
176
|
endpointPath: string;
|
|
@@ -192,13 +208,21 @@ export class SessionHostClient {
|
|
|
192
208
|
if (typeof options.requestTimeoutMs === 'number' && options.requestTimeoutMs > 0) {
|
|
193
209
|
this.requestTimeoutMs = options.requestTimeoutMs;
|
|
194
210
|
}
|
|
195
|
-
activeClientRefs.add(new WeakRef(this));
|
|
211
|
+
if (options.registerAsShared !== false) activeClientRefs.add(new WeakRef(this));
|
|
196
212
|
}
|
|
197
213
|
|
|
198
214
|
static disconnectClients(endpoint?: SessionHostEndpoint, error?: Error): void {
|
|
199
215
|
disconnectSessionHostClients(endpoint, error);
|
|
200
216
|
}
|
|
201
217
|
|
|
218
|
+
static findConnectedClient(endpoint?: SessionHostEndpoint): SessionHostClient | null {
|
|
219
|
+
return findConnectedSessionHostClient(endpoint);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
isConnected(): boolean {
|
|
223
|
+
return Boolean(this.socket && !this.socket.destroyed && establishedSockets.has(this.socket));
|
|
224
|
+
}
|
|
225
|
+
|
|
202
226
|
/**
|
|
203
227
|
* Fires when an established connection to the session host goes away.
|
|
204
228
|
*
|
|
@@ -278,6 +302,7 @@ export class SessionHostClient {
|
|
|
278
302
|
private sendRawRequestOnSocket<T = unknown>(
|
|
279
303
|
socket: net.Socket,
|
|
280
304
|
request: SessionHostRequest,
|
|
305
|
+
timeoutMs = this.requestTimeoutMs,
|
|
281
306
|
): Promise<SessionHostResponse<T>> {
|
|
282
307
|
const requestId = generateUUID();
|
|
283
308
|
const envelope: SessionHostRequestEnvelope = {
|
|
@@ -285,11 +310,10 @@ export class SessionHostClient {
|
|
|
285
310
|
requestId,
|
|
286
311
|
request,
|
|
287
312
|
};
|
|
288
|
-
const timeoutMs = this.requestTimeoutMs > 0 ? this.requestTimeoutMs : 30_000;
|
|
289
313
|
const timeoutLabel = timeoutMs % 1000 === 0 ? `${timeoutMs / 1000}s` : `${timeoutMs}ms`;
|
|
290
314
|
return new Promise<SessionHostResponse<T>>((resolve, reject) => {
|
|
291
315
|
const timeout = setTimeout(() => {
|
|
292
|
-
const timeoutError = new Error(`Session host request timed out after ${timeoutLabel} (${request.type})`);
|
|
316
|
+
const timeoutError = Object.assign(new Error(`Session host request timed out after ${timeoutLabel} (${request.type})`), { code: 'SESSION_HOST_REQUEST_TIMEOUT', requestType: request.type });
|
|
293
317
|
if (this.socket === socket) {
|
|
294
318
|
this.disconnect(timeoutError);
|
|
295
319
|
} else {
|
|
@@ -307,7 +331,7 @@ export class SessionHostClient {
|
|
|
307
331
|
});
|
|
308
332
|
}
|
|
309
333
|
|
|
310
|
-
async connect(): Promise<void> {
|
|
334
|
+
async connect(timeoutMs = this.requestTimeoutMs): Promise<void> {
|
|
311
335
|
if (this.socket && !this.socket.destroyed) {
|
|
312
336
|
if (this.connectPromise) await this.connectPromise;
|
|
313
337
|
return;
|
|
@@ -381,11 +405,23 @@ export class SessionHostClient {
|
|
|
381
405
|
|
|
382
406
|
const connectTask = (async () => {
|
|
383
407
|
await new Promise<void>((resolve, reject) => {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
408
|
+
const cleanup = () => {
|
|
409
|
+
clearTimeout(timer);
|
|
410
|
+
socket.off('connect', connected);
|
|
411
|
+
socket.off('error', failed);
|
|
412
|
+
socket.off('close', closed);
|
|
413
|
+
};
|
|
414
|
+
const connected = () => { cleanup(); establishedSockets.add(socket); resolve(); };
|
|
415
|
+
const failed = (error: Error) => { cleanup(); reject(error); };
|
|
416
|
+
const closed = () => failed(new Error('Session host connection closed before connect'));
|
|
417
|
+
const timer = setTimeout(() => {
|
|
418
|
+
const error = Object.assign(new Error(`Session host connect timed out after ${timeoutMs}ms`), { code: 'SESSION_HOST_CONNECT_TIMEOUT' });
|
|
419
|
+
failed(error);
|
|
420
|
+
if (this.socket === socket) this.disconnect(error);
|
|
421
|
+
}, timeoutMs);
|
|
422
|
+
socket.once('connect', connected);
|
|
423
|
+
socket.once('error', failed);
|
|
424
|
+
socket.once('close', closed);
|
|
389
425
|
});
|
|
390
426
|
if (this.attachedSessions.size > 0 && this.socket === socket && !socket.destroyed) {
|
|
391
427
|
for (const [sessionId, sub] of [...this.attachedSessions.entries()]) {
|
|
@@ -418,12 +454,33 @@ export class SessionHostClient {
|
|
|
418
454
|
};
|
|
419
455
|
}
|
|
420
456
|
|
|
421
|
-
async request<T = unknown>(request: SessionHostRequest): Promise<SessionHostResponse<T>> {
|
|
422
|
-
|
|
457
|
+
async request<T = unknown>(request: SessionHostRequest, options: { timeoutMs?: number } = {}): Promise<SessionHostResponse<T>> {
|
|
458
|
+
const timeoutMs = Math.max(1, options.timeoutMs ?? this.requestTimeoutMs);
|
|
459
|
+
const deadline = Date.now() + timeoutMs;
|
|
460
|
+
const alreadyConnected = this.isConnected() && !this.connectPromise;
|
|
461
|
+
// A probe must also bound a connect/re-attach already in flight on this
|
|
462
|
+
// client; that task may have started with the normal 30s timeout.
|
|
463
|
+
if (options.timeoutMs !== undefined) {
|
|
464
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
465
|
+
try {
|
|
466
|
+
await Promise.race([
|
|
467
|
+
this.connect(timeoutMs),
|
|
468
|
+
new Promise<never>((_resolve, reject) => {
|
|
469
|
+
timer = setTimeout(() => {
|
|
470
|
+
const error = Object.assign(new Error(`Session host connect timed out after ${timeoutMs}ms`), { code: 'SESSION_HOST_CONNECT_TIMEOUT' });
|
|
471
|
+
reject(error);
|
|
472
|
+
this.disconnect(error);
|
|
473
|
+
}, timeoutMs);
|
|
474
|
+
}),
|
|
475
|
+
]);
|
|
476
|
+
} finally { if (timer) clearTimeout(timer); }
|
|
477
|
+
} else {
|
|
478
|
+
await this.connect(timeoutMs);
|
|
479
|
+
}
|
|
423
480
|
const requestSocket = this.socket;
|
|
424
481
|
if (!requestSocket) throw new Error('Session host socket unavailable');
|
|
425
482
|
|
|
426
|
-
const response = await this.sendRawRequestOnSocket<T>(requestSocket, request);
|
|
483
|
+
const response = await this.sendRawRequestOnSocket<T>(requestSocket, request, options.timeoutMs === undefined || alreadyConnected ? timeoutMs : Math.max(1, deadline - Date.now()));
|
|
427
484
|
if (response.success) {
|
|
428
485
|
if (request.type === 'attach_session') {
|
|
429
486
|
this.attachedSessions.set(request.payload.sessionId, {
|