DSH-Arch-Setup/plugins/dsh-touched-git/lib/editor-file-bridge.js
Шурупов Илья Викторович cf9443753e Add one-command Arch DSH setup
Package the portable DSH profiles, local plugins, desktop integration, Firefox wrapper, and ownership-aware installer lifecycle. Bundle checksum-pinned OpenVSCode, editor extensions, translation assets, and cloudflared so unreliable upstream artifact downloads cannot break a clean installation.
2026-09-12 19:13:36 +03:00

92 lines
3.8 KiB
JavaScript

import net from 'node:net'
import { randomUUID } from 'node:crypto'
import { readdir, realpath } from 'node:fs/promises'
import { isAbsolute, join } from 'node:path'
import bridgeProtocol from '../editor-file-bridge/extension.js'
const { workspaceSocketPrefix, validateBridgeDirectory, MAX_MESSAGE_BYTES, REQUEST_TIMEOUT_MILLISECONDS } = bridgeProtocol
function requestFileOpen(socketPath, request, signal) {
return new Promise((resolve, reject) => {
if (signal.aborted) {
reject(signal.reason)
return
}
const socket = net.createConnection(socketPath)
let connected = false
let settled = false
let buffered = Buffer.alloc(0)
const finish = (error, opened) => {
if (settled) return
settled = true
signal.removeEventListener('abort', abort)
socket.destroy()
if (error) reject(error)
else resolve(opened)
}
const abort = () => finish(signal.reason || new Error('The editor request was cancelled.'))
signal.addEventListener('abort', abort, { once: true })
socket.once('connect', () => {
connected = true
socket.write(`${JSON.stringify(request)}\n`)
})
socket.on('data', chunk => {
buffered = Buffer.concat([buffered, chunk])
if (buffered.length > MAX_MESSAGE_BYTES) {
finish(new Error('The editor acknowledgement is too large.'))
return
}
const newline = buffered.indexOf(10)
if (newline === -1) return
let response
try {
response = JSON.parse(buffered.subarray(0, newline).toString('utf8'))
} catch {
finish(new Error('The editor returned an invalid acknowledgement.'))
return
}
if (response?.requestId !== request.requestId || typeof response.ok !== 'boolean') {
finish(new Error('The editor returned an unexpected acknowledgement.'))
} else if (!response.ok) {
finish(new Error(typeof response.error === 'string' ? response.error : 'The existing editor could not open the file.'))
} else {
finish(null, true)
}
})
socket.once('error', error => {
if (!connected && ['ECONNREFUSED', 'ENOENT'].includes(error.code)) finish(null, false)
else finish(error)
})
socket.once('close', () => {
if (!settled) finish(new Error('The existing editor disconnected before acknowledging the file.'))
})
})
}
export async function openFileInExistingWorkspace({ bridgeDirectory, workspaceDirectory, filePath, signal }) {
signal?.throwIfAborted()
if (!bridgeDirectory) return { opened: false }
if (!isAbsolute(workspaceDirectory) || !isAbsolute(filePath)) throw new Error('Editor workspace and file paths must be absolute.')
const canonicalWorkspace = await realpath(workspaceDirectory)
let directory
let entries
try {
directory = await validateBridgeDirectory(bridgeDirectory)
entries = await readdir(directory, { withFileTypes: true })
} catch (error) {
if (error.code === 'ENOENT') return { opened: false }
throw error
}
const prefix = workspaceSocketPrefix(canonicalWorkspace)
const sockets = entries.filter(entry => entry.isSocket() && entry.name.startsWith(prefix) && entry.name.endsWith('.sock')).map(entry => entry.name).sort()
const request = { kind: 'open-file', workspaceDirectory: canonicalWorkspace, filePath, requestId: randomUUID() }
if (Buffer.byteLength(JSON.stringify(request)) + 1 > MAX_MESSAGE_BYTES) throw new Error('The editor request is too large.')
const deadline = AbortSignal.timeout(REQUEST_TIMEOUT_MILLISECONDS)
const requestSignal = signal ? AbortSignal.any([signal, deadline]) : deadline
for (const name of sockets) {
requestSignal.throwIfAborted()
if (await requestFileOpen(join(directory, name), request, requestSignal)) return { opened: true }
}
requestSignal.throwIfAborted()
return { opened: false }
}