@crosshands/runtime 0.1.5 → 0.2.0
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/broker/broker.d.ts +4 -0
- package/dist/broker/broker.d.ts.map +1 -1
- package/dist/broker/broker.js +194 -47
- package/dist/broker/broker.js.map +1 -1
- package/dist/diagnostics/paths.d.ts +5 -0
- package/dist/diagnostics/paths.d.ts.map +1 -0
- package/dist/diagnostics/paths.js +25 -0
- package/dist/diagnostics/paths.js.map +1 -0
- package/dist/diagnostics/record.d.ts +99 -0
- package/dist/diagnostics/record.d.ts.map +1 -0
- package/dist/diagnostics/record.js +154 -0
- package/dist/diagnostics/record.js.map +1 -0
- package/dist/diagnostics/writer.d.ts +27 -0
- package/dist/diagnostics/writer.d.ts.map +1 -0
- package/dist/diagnostics/writer.js +95 -0
- package/dist/diagnostics/writer.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/ipc/control-transport.d.ts +6 -0
- package/dist/ipc/control-transport.d.ts.map +1 -1
- package/dist/ipc/control-transport.js +13 -1
- package/dist/ipc/control-transport.js.map +1 -1
- package/dist/ipc/endpoint.d.ts +1 -0
- package/dist/ipc/endpoint.d.ts.map +1 -1
- package/dist/ipc/endpoint.js +3 -0
- package/dist/ipc/endpoint.js.map +1 -1
- package/dist/private-directory.d.ts +2 -0
- package/dist/private-directory.d.ts.map +1 -0
- package/dist/private-directory.js +15 -0
- package/dist/private-directory.js.map +1 -0
- package/package.json +2 -2
- package/src/broker/broker.ts +233 -50
- package/src/diagnostics/paths.ts +30 -0
- package/src/diagnostics/record.ts +262 -0
- package/src/diagnostics/writer.ts +112 -0
- package/src/index.ts +4 -0
- package/src/ipc/control-transport.ts +20 -1
- package/src/ipc/endpoint.ts +4 -0
- package/src/private-directory.ts +14 -0
package/src/broker/broker.ts
CHANGED
|
@@ -13,6 +13,18 @@ import {
|
|
|
13
13
|
type TargetReference
|
|
14
14
|
} from '@crosshands/contract'
|
|
15
15
|
|
|
16
|
+
import {
|
|
17
|
+
DIAGNOSTIC_RECORD_VERSION,
|
|
18
|
+
diagnosticError,
|
|
19
|
+
diagnosticPlatform,
|
|
20
|
+
diagnosticRequestResult,
|
|
21
|
+
diagnosticTarget,
|
|
22
|
+
emitDiagnostic,
|
|
23
|
+
type DiagnosticEnvelope,
|
|
24
|
+
type DiagnosticRecord,
|
|
25
|
+
type DiagnosticsSink
|
|
26
|
+
} from '../diagnostics/record.js'
|
|
27
|
+
import { graphicalSessionKey } from '../ipc/endpoint.js'
|
|
16
28
|
import { assertAppAllowed, type StableAppIdentity } from '../policy/policy.js'
|
|
17
29
|
import { ProviderSupervisor } from '../providers/supervisor.js'
|
|
18
30
|
|
|
@@ -51,6 +63,7 @@ export type LocalBrokerOptions = {
|
|
|
51
63
|
input: unknown
|
|
52
64
|
) => Promise<TargetInspection | null>
|
|
53
65
|
publish?: (response: BrokerResponse) => Promise<void>
|
|
66
|
+
diagnostics?: DiagnosticsSink
|
|
54
67
|
contextTtlMs?: number
|
|
55
68
|
now?: () => number
|
|
56
69
|
}
|
|
@@ -73,10 +86,6 @@ class SerialQueue {
|
|
|
73
86
|
}
|
|
74
87
|
}
|
|
75
88
|
|
|
76
|
-
function isMutation(operation: ComputerOperationName): boolean {
|
|
77
|
-
return COMPUTER_OPERATIONS[operation].mutation
|
|
78
|
-
}
|
|
79
|
-
|
|
80
89
|
function assertedInputApp(input: unknown): void {
|
|
81
90
|
if (input === null || typeof input !== 'object') return
|
|
82
91
|
const app = (input as { app?: unknown }).app
|
|
@@ -169,6 +178,31 @@ function contextToken(input: unknown): string | undefined {
|
|
|
169
178
|
return typeof token === 'string' ? token : undefined
|
|
170
179
|
}
|
|
171
180
|
|
|
181
|
+
function observationApp(input: unknown): string | undefined {
|
|
182
|
+
if (input === null || typeof input !== 'object') return undefined
|
|
183
|
+
const app = (input as Record<string, unknown>).app
|
|
184
|
+
return typeof app === 'string' && app.length > 0 ? app : undefined
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function bindGetAppStateInput(
|
|
188
|
+
input: unknown,
|
|
189
|
+
resolve: (token: string) => InteractionContext
|
|
190
|
+
): unknown {
|
|
191
|
+
if (input === null || typeof input !== 'object') return input
|
|
192
|
+
const record = input as Record<string, unknown>
|
|
193
|
+
const token = contextToken(record)
|
|
194
|
+
if (token === undefined || observationApp(record) !== undefined) return input
|
|
195
|
+
const context = resolve(token)
|
|
196
|
+
return {
|
|
197
|
+
app: context.appId,
|
|
198
|
+
window: { id: context.window.id },
|
|
199
|
+
...(typeof record.captureScreenshot === 'boolean'
|
|
200
|
+
? { captureScreenshot: record.captureScreenshot }
|
|
201
|
+
: {}),
|
|
202
|
+
...(typeof record.restoreWindow === 'boolean' ? { restoreWindow: record.restoreWindow } : {})
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
172
206
|
function computerError(cause: unknown): { code?: string; message: string; toJSON?: () => unknown } {
|
|
173
207
|
if (cause instanceof Error) return cause
|
|
174
208
|
return { message: 'Unknown provider failure' }
|
|
@@ -225,21 +259,26 @@ export class BrokerClient {
|
|
|
225
259
|
export class LocalBroker {
|
|
226
260
|
readonly generation: string
|
|
227
261
|
readonly #identity: BrokerPeer
|
|
262
|
+
readonly #session: string
|
|
228
263
|
readonly #supervisor: ProviderSupervisor
|
|
229
264
|
readonly #contexts: InteractionContextStore
|
|
230
265
|
readonly #inspectTarget: LocalBrokerOptions['inspectTarget']
|
|
231
266
|
readonly #publish: LocalBrokerOptions['publish']
|
|
267
|
+
readonly #diagnostics: DiagnosticsSink | undefined
|
|
232
268
|
readonly #now: () => number
|
|
233
269
|
readonly #queue = new SerialQueue()
|
|
234
270
|
#desktopEpoch = 0
|
|
235
271
|
#requestSequence = 0
|
|
272
|
+
#closed = false
|
|
236
273
|
|
|
237
274
|
constructor(options: LocalBrokerOptions) {
|
|
238
275
|
this.generation = options.generation ?? `broker-${randomUUID()}`
|
|
239
276
|
this.#identity = options.identity
|
|
277
|
+
this.#session = graphicalSessionKey(options.identity.graphicalSessionId)
|
|
240
278
|
this.#inspectTarget = options.inspectTarget
|
|
241
279
|
this.#publish = options.publish
|
|
242
280
|
this.#now = options.now ?? Date.now
|
|
281
|
+
this.#diagnostics = options.diagnostics
|
|
243
282
|
this.#contexts = new InteractionContextStore({
|
|
244
283
|
...(options.contextTtlMs === undefined ? {} : { ttlMs: options.contextTtlMs }),
|
|
245
284
|
now: this.#now
|
|
@@ -254,6 +293,20 @@ export class LocalBroker {
|
|
|
254
293
|
return this.#desktopEpoch
|
|
255
294
|
}
|
|
256
295
|
|
|
296
|
+
diagnosticEnvelope(): DiagnosticEnvelope {
|
|
297
|
+
return {
|
|
298
|
+
v: DIAGNOSTIC_RECORD_VERSION,
|
|
299
|
+
ts: new Date(this.#now()).toISOString(),
|
|
300
|
+
session: this.#session,
|
|
301
|
+
brokerGeneration: this.generation,
|
|
302
|
+
...(this.#supervisor.generation === undefined
|
|
303
|
+
? {}
|
|
304
|
+
: { providerGeneration: this.#supervisor.generation }),
|
|
305
|
+
desktopEpoch: this.#desktopEpoch,
|
|
306
|
+
platform: diagnosticPlatform()
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
257
310
|
async connect(handshake: {
|
|
258
311
|
peer: BrokerPeer
|
|
259
312
|
versions: ContractVersions
|
|
@@ -274,9 +327,18 @@ export class LocalBroker {
|
|
|
274
327
|
const versions = negotiateVersionHandshake(handshake.versions)
|
|
275
328
|
if (!versions.ok) throw versions.error
|
|
276
329
|
await this.#supervisor.start()
|
|
330
|
+
this.#emit({ kind: 'helper.start', ...this.diagnosticEnvelope() })
|
|
277
331
|
return new BrokerClient(this)
|
|
278
332
|
}
|
|
279
333
|
|
|
334
|
+
async close(reason = 'closed'): Promise<void> {
|
|
335
|
+
if (this.#closed) return
|
|
336
|
+
this.#closed = true
|
|
337
|
+
await this.#supervisor.close()
|
|
338
|
+
this.#emit({ kind: 'broker.stop', reason, ...this.diagnosticEnvelope() })
|
|
339
|
+
await this.#diagnostics?.close()
|
|
340
|
+
}
|
|
341
|
+
|
|
280
342
|
issueContext(bindings: ReferenceBindings): InteractionContext {
|
|
281
343
|
return this.#contexts.issue({
|
|
282
344
|
...bindings,
|
|
@@ -292,59 +354,78 @@ export class LocalBroker {
|
|
|
292
354
|
}
|
|
293
355
|
|
|
294
356
|
request(request: BrokerRequest): Promise<BrokerResponse> {
|
|
295
|
-
|
|
357
|
+
const enqueuedAt = this.#now()
|
|
358
|
+
return this.#queue.run(() => this.#requestLocked(request, enqueuedAt))
|
|
296
359
|
}
|
|
297
360
|
|
|
298
|
-
async #requestLocked(request: BrokerRequest): Promise<BrokerResponse> {
|
|
361
|
+
async #requestLocked(request: BrokerRequest, enqueuedAt: number): Promise<BrokerResponse> {
|
|
362
|
+
const startedAt = this.#now()
|
|
299
363
|
const requestId = `broker-${++this.#requestSequence}`
|
|
300
364
|
const deadlineAt = this.#now() + (request.deadlineMs ?? 30_000)
|
|
301
|
-
const mutation =
|
|
302
|
-
|
|
303
|
-
let
|
|
304
|
-
let
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
)
|
|
312
|
-
}
|
|
313
|
-
context = this.#contexts.resolve(token)
|
|
314
|
-
// Context-window and index shorthand do not carry an identity until the
|
|
315
|
-
// broker binds them. Inspect the bound form so native providers can
|
|
316
|
-
// re-resolve the exact process/window before dispatch.
|
|
317
|
-
inspectionInput = normalizeMutationInput(request.input, context, context)
|
|
365
|
+
const mutation = COMPUTER_OPERATIONS[request.operation].mutation
|
|
366
|
+
let inspectMs = 0
|
|
367
|
+
let dispatchMs = 0
|
|
368
|
+
let bindings: ReferenceBindings | undefined
|
|
369
|
+
let dispatched: boolean | undefined
|
|
370
|
+
let result: unknown
|
|
371
|
+
let cause: unknown
|
|
372
|
+
let input = request.input
|
|
373
|
+
if (request.operation === 'getAppState') {
|
|
374
|
+
input = bindGetAppStateInput(input, (token) => this.#contexts.resolve(token))
|
|
318
375
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
376
|
+
assertedInputApp(input)
|
|
377
|
+
let context: InteractionContext | undefined
|
|
378
|
+
let inspectionInput = input
|
|
379
|
+
try {
|
|
380
|
+
if (mutation) {
|
|
381
|
+
const token = contextToken(request.input)
|
|
382
|
+
if (token === undefined) {
|
|
383
|
+
throw createComputerError(
|
|
384
|
+
'invalid_argument',
|
|
385
|
+
'Mutation requires a context token and target'
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
context = this.#contexts.resolve(token)
|
|
389
|
+
// Context-window and index shorthand do not carry an identity until the
|
|
390
|
+
// broker binds them. Inspect the bound form so native providers can
|
|
391
|
+
// re-resolve the exact process/window before dispatch.
|
|
392
|
+
inspectionInput = normalizeMutationInput(request.input, context, context)
|
|
326
393
|
}
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
394
|
+
const inspectStarted = this.#now()
|
|
395
|
+
const inspection = await this.#inspectTarget?.(request.operation, inspectionInput)
|
|
396
|
+
inspectMs = this.#now() - inspectStarted
|
|
397
|
+
if (inspection !== undefined && inspection !== null) {
|
|
398
|
+
assertAppAllowed(inspection.appIdentity)
|
|
399
|
+
bindings = inspection.bindings
|
|
331
400
|
}
|
|
332
|
-
|
|
333
|
-
|
|
401
|
+
|
|
402
|
+
let providerInput = input
|
|
403
|
+
if (mutation) {
|
|
404
|
+
if (inspection === undefined || inspection === null) {
|
|
405
|
+
throw createComputerError('stale_target', 'Target identity could not be re-resolved')
|
|
406
|
+
}
|
|
407
|
+
providerInput = normalizeMutationInput(request.input, context!, inspection.bindings)
|
|
408
|
+
const references = mutationReferences(providerInput)
|
|
409
|
+
if (references.length === 0) {
|
|
410
|
+
throw createComputerError('invalid_argument', 'Mutation requires a target selector')
|
|
411
|
+
}
|
|
412
|
+
for (const reference of references) {
|
|
413
|
+
assertFreshReference(reference, context!, inspection.bindings, this.#now())
|
|
414
|
+
}
|
|
334
415
|
}
|
|
335
|
-
}
|
|
336
416
|
|
|
337
|
-
|
|
417
|
+
const dispatchStarted = this.#now()
|
|
338
418
|
const providerResponse = await this.#supervisor.dispatch({
|
|
339
419
|
requestId,
|
|
340
420
|
operation: request.operation,
|
|
341
421
|
input: providerInput,
|
|
342
422
|
deadlineAt
|
|
343
423
|
})
|
|
424
|
+
dispatchMs = this.#now() - dispatchStarted
|
|
425
|
+
dispatched = providerResponse.dispatched
|
|
344
426
|
if (mutation) {
|
|
345
|
-
const dispatched = providerResponse.dispatched
|
|
346
427
|
if (dispatched) this.#desktopEpoch += 1
|
|
347
|
-
const
|
|
428
|
+
const mutationResult =
|
|
348
429
|
'error' in providerResponse
|
|
349
430
|
? {
|
|
350
431
|
outcome: dispatched
|
|
@@ -354,43 +435,108 @@ export class LocalBroker {
|
|
|
354
435
|
: providerResponse.result
|
|
355
436
|
const response = {
|
|
356
437
|
requestId,
|
|
357
|
-
result: this.#publicResult(request.operation,
|
|
438
|
+
result: this.#publicResult(request.operation, mutationResult),
|
|
358
439
|
desktopEpoch: this.#desktopEpoch,
|
|
359
440
|
providerGeneration: this.#supervisor.generation ?? 'unknown'
|
|
360
441
|
}
|
|
442
|
+
result = response.result
|
|
361
443
|
await this.#publish?.(response)
|
|
362
444
|
return response
|
|
363
445
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
446
|
+
if ('error' in providerResponse) {
|
|
447
|
+
// Why: observations have no outcome envelope, so surface the provider's
|
|
448
|
+
// coded error directly. Validating the absent result instead would mask
|
|
449
|
+
// app_not_found and friends as an opaque schema failure.
|
|
450
|
+
throw createComputerError(
|
|
451
|
+
providerResponse.error.code,
|
|
452
|
+
providerResponse.error.message,
|
|
453
|
+
...(providerResponse.error.details === undefined ? [] : [providerResponse.error.details])
|
|
454
|
+
)
|
|
455
|
+
}
|
|
456
|
+
const response = this.#observationResponse(
|
|
457
|
+
requestId,
|
|
458
|
+
request.operation,
|
|
459
|
+
providerResponse.result
|
|
460
|
+
)
|
|
461
|
+
result = response.result
|
|
462
|
+
return response
|
|
463
|
+
} catch (error) {
|
|
464
|
+
cause = error
|
|
465
|
+
const failed = computerError(error)
|
|
466
|
+
if (!mutation && failed.code === 'provider_crashed') {
|
|
467
|
+
this.#emit({
|
|
468
|
+
kind: 'helper.crash',
|
|
469
|
+
...this.diagnosticEnvelope(),
|
|
470
|
+
requestId,
|
|
471
|
+
operation: request.operation,
|
|
472
|
+
error: diagnosticError(error)
|
|
473
|
+
})
|
|
474
|
+
const previousGeneration = this.#supervisor.generation
|
|
368
475
|
await this.#supervisor.restart()
|
|
369
476
|
this.#contexts.invalidateAll()
|
|
477
|
+
this.#emit({
|
|
478
|
+
kind: 'helper.restart',
|
|
479
|
+
...this.diagnosticEnvelope(),
|
|
480
|
+
...(previousGeneration === undefined ? {} : { previousGeneration })
|
|
481
|
+
})
|
|
482
|
+
const retryStarted = this.#now()
|
|
370
483
|
const retry = await this.#supervisor.dispatch({
|
|
371
484
|
requestId: `${requestId}-retry`,
|
|
372
485
|
operation: request.operation,
|
|
373
486
|
input: request.input,
|
|
374
487
|
deadlineAt
|
|
375
488
|
})
|
|
376
|
-
|
|
377
|
-
|
|
489
|
+
dispatchMs += this.#now() - retryStarted
|
|
490
|
+
if ('error' in retry) {
|
|
491
|
+
cause = createComputerError(retry.error.code, retry.error.message, retry.error.details)
|
|
492
|
+
throw cause
|
|
493
|
+
}
|
|
494
|
+
dispatched = retry.dispatched
|
|
495
|
+
const response = this.#observationResponse(requestId, request.operation, retry.result)
|
|
496
|
+
cause = undefined
|
|
497
|
+
result = response.result
|
|
498
|
+
return response
|
|
378
499
|
}
|
|
379
|
-
if (mutation && (
|
|
500
|
+
if (mutation && (failed.code === 'provider_crashed' || failed.code === 'timeout')) {
|
|
501
|
+
if (failed.code === 'provider_crashed') {
|
|
502
|
+
this.#emit({
|
|
503
|
+
kind: 'helper.crash',
|
|
504
|
+
...this.diagnosticEnvelope(),
|
|
505
|
+
requestId,
|
|
506
|
+
operation: request.operation,
|
|
507
|
+
error: diagnosticError(error)
|
|
508
|
+
})
|
|
509
|
+
}
|
|
380
510
|
this.#desktopEpoch += 1
|
|
381
511
|
this.#contexts.invalidateAll()
|
|
512
|
+
dispatched = true
|
|
382
513
|
const response = {
|
|
383
514
|
requestId,
|
|
384
515
|
result: this.#publicResult(request.operation, {
|
|
385
|
-
outcome: { state: 'indeterminate', reason:
|
|
516
|
+
outcome: { state: 'indeterminate', reason: failed.message }
|
|
386
517
|
}),
|
|
387
518
|
desktopEpoch: this.#desktopEpoch,
|
|
388
519
|
providerGeneration: this.#supervisor.generation ?? 'unknown'
|
|
389
520
|
}
|
|
521
|
+
cause = undefined
|
|
522
|
+
result = response.result
|
|
390
523
|
await this.#publish?.(response)
|
|
391
524
|
return response
|
|
392
525
|
}
|
|
393
|
-
throw
|
|
526
|
+
throw error
|
|
527
|
+
} finally {
|
|
528
|
+
this.#emitRequest({
|
|
529
|
+
request,
|
|
530
|
+
requestId,
|
|
531
|
+
enqueuedAt,
|
|
532
|
+
startedAt,
|
|
533
|
+
inspectMs,
|
|
534
|
+
dispatchMs,
|
|
535
|
+
bindings,
|
|
536
|
+
dispatched,
|
|
537
|
+
result,
|
|
538
|
+
cause
|
|
539
|
+
})
|
|
394
540
|
}
|
|
395
541
|
}
|
|
396
542
|
|
|
@@ -436,4 +582,41 @@ export class LocalBroker {
|
|
|
436
582
|
}
|
|
437
583
|
return parseOperationOutput(operation, publicResult)
|
|
438
584
|
}
|
|
585
|
+
|
|
586
|
+
#emit(record: DiagnosticRecord): void {
|
|
587
|
+
emitDiagnostic(this.#diagnostics, record)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
#emitRequest(entry: {
|
|
591
|
+
request: BrokerRequest
|
|
592
|
+
requestId: string
|
|
593
|
+
enqueuedAt: number
|
|
594
|
+
startedAt: number
|
|
595
|
+
inspectMs: number
|
|
596
|
+
dispatchMs: number
|
|
597
|
+
bindings: ReferenceBindings | undefined
|
|
598
|
+
dispatched: boolean | undefined
|
|
599
|
+
result: unknown
|
|
600
|
+
cause: unknown
|
|
601
|
+
}): void {
|
|
602
|
+
const target = diagnosticTarget(entry.request.input, entry.bindings)
|
|
603
|
+
this.#emit({
|
|
604
|
+
kind: 'request',
|
|
605
|
+
...this.diagnosticEnvelope(),
|
|
606
|
+
requestId: entry.requestId,
|
|
607
|
+
operation: entry.request.operation,
|
|
608
|
+
mutation: COMPUTER_OPERATIONS[entry.request.operation].mutation,
|
|
609
|
+
ms: {
|
|
610
|
+
queue: Math.max(0, entry.startedAt - entry.enqueuedAt),
|
|
611
|
+
inspect: entry.inspectMs,
|
|
612
|
+
dispatch: entry.dispatchMs,
|
|
613
|
+
total: Math.max(0, this.#now() - entry.enqueuedAt)
|
|
614
|
+
},
|
|
615
|
+
...(target === undefined ? {} : { target }),
|
|
616
|
+
result:
|
|
617
|
+
entry.cause !== undefined
|
|
618
|
+
? { type: 'error', ...diagnosticError(entry.cause) }
|
|
619
|
+
: diagnosticRequestResult(entry.request.operation, entry.result, entry.dispatched)
|
|
620
|
+
})
|
|
621
|
+
}
|
|
439
622
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { homedir } from 'node:os'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { graphicalSessionKey } from '../ipc/endpoint.js'
|
|
5
|
+
|
|
6
|
+
export function diagnosticsDirectory(
|
|
7
|
+
graphicalSessionId: string,
|
|
8
|
+
options: { platform?: NodeJS.Platform; env?: NodeJS.ProcessEnv } = {}
|
|
9
|
+
): string {
|
|
10
|
+
const env = options.env ?? process.env
|
|
11
|
+
const override = env.CROSSHANDS_DIAGNOSTICS_DIR
|
|
12
|
+
if (typeof override === 'string' && override.length > 0) return override
|
|
13
|
+
const sessionKey = graphicalSessionKey(graphicalSessionId)
|
|
14
|
+
const platform = options.platform ?? process.platform
|
|
15
|
+
if (platform === 'darwin') {
|
|
16
|
+
return join(homedir(), 'Library', 'Logs', 'CrossHands', sessionKey)
|
|
17
|
+
}
|
|
18
|
+
if (platform === 'linux') {
|
|
19
|
+
const stateHome =
|
|
20
|
+
typeof env.XDG_STATE_HOME === 'string' && env.XDG_STATE_HOME.length > 0
|
|
21
|
+
? env.XDG_STATE_HOME
|
|
22
|
+
: join(homedir(), '.local', 'state')
|
|
23
|
+
return join(stateHome, 'crosshands', sessionKey)
|
|
24
|
+
}
|
|
25
|
+
const localAppData =
|
|
26
|
+
typeof env.LOCALAPPDATA === 'string' && env.LOCALAPPDATA.length > 0
|
|
27
|
+
? env.LOCALAPPDATA
|
|
28
|
+
: join(homedir(), 'AppData', 'Local')
|
|
29
|
+
return join(localAppData, 'CrossHands', 'logs', sessionKey)
|
|
30
|
+
}
|