( onPrompt: (prompt: string) => Promise<string> )
| 81 | * must return the response string (e.g. "yes" or "no"). |
| 82 | */ |
| 83 | export async function createAskpassSession( |
| 84 | onPrompt: (prompt: string) => Promise<string> |
| 85 | ): Promise<AskpassSession> { |
| 86 | // Resolve script path before allocating temp resources. |
| 87 | const scriptPath = await ensureAskpassScript(); |
| 88 | |
| 89 | const dir = fs.mkdtempSync(path.join(os.tmpdir(), "mux-askpass-")); |
| 90 | const processed = new Set<string>(); |
| 91 | let closed = false; |
| 92 | |
| 93 | async function handlePrompt(requestId: string): Promise<void> { |
| 94 | const promptFile = path.join(dir, `prompt.${requestId}.txt`); |
| 95 | const responseFile = path.join(dir, `response.${requestId}.txt`); |
| 96 | |
| 97 | try { |
| 98 | await fs.promises.access(promptFile); |
| 99 | } catch { |
| 100 | processed.delete(requestId); |
| 101 | return; |
| 102 | } |
| 103 | |
| 104 | if (closed) return; |
| 105 | |
| 106 | try { |
| 107 | const promptText = await fs.promises.readFile(promptFile, "utf-8"); |
| 108 | const response = await onPrompt(promptText); |
| 109 | await fs.promises.writeFile(responseFile, response + "\n"); |
| 110 | } catch (err) { |
| 111 | log.debug("Askpass prompt handling failed:", err); |
| 112 | // Write rejection to unblock askpass (best-effort) |
| 113 | try { |
| 114 | await fs.promises.writeFile(responseFile, "no\n"); |
| 115 | } catch { |
| 116 | /* askpass may already be gone */ |
| 117 | } |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | // Watch for askpass to write prompt files. |
| 122 | // fs.watch is set up BEFORE SSH is spawned, so we cannot miss events. |
| 123 | let watcher: fs.FSWatcher; |
| 124 | try { |
| 125 | watcher = fs.watch(dir, (_, filename) => { |
| 126 | if (closed) return; |
| 127 | |
| 128 | void (async () => { |
| 129 | let candidateFilenames: string[]; |
| 130 | if (typeof filename === "string") { |
| 131 | candidateFilenames = [filename]; |
| 132 | } else { |
| 133 | try { |
| 134 | candidateFilenames = await fs.promises.readdir(dir); |
| 135 | } catch { |
| 136 | return; |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | for (const candidate of candidateFilenames) { |
no test coverage detected