Release 0.9.10 — fix zero-byte download crash, simplify Password setDesc

This commit is contained in:
Andrew Kopylev 2026-05-03 21:07:39 +03:00
parent bbe554fdaa
commit e5c5d23dea
6 changed files with 19 additions and 9 deletions

View file

@ -1,7 +1,7 @@
{
"id": "vault-bridge-sftp",
"name": "Vault Bridge SFTP",
"version": "0.9.9",
"version": "0.9.10",
"minAppVersion": "1.5.0",
"description": "Bridge your vault across devices through your own SSH/SFTP server. Bidirectional sync with conflict resolution, multi-device safety, and full self-hosting.",
"author": "Andrew Kopylev",

View file

@ -1,6 +1,6 @@
{
"name": "obsidian-vault-bridge-sftp",
"version": "0.9.9",
"version": "0.9.10",
"description": "Vault Bridge SFTP — bidirectional Obsidian vault sync over SSH/SFTP",
"main": "main.js",
"scripts": {

View file

@ -137,7 +137,7 @@ export class SftpSyncSettingTab extends PluginSettingTab {
if (this.plugin.settings.authMethod === "password") {
new Setting(containerEl)
.setName("Password")
.setDesc("Encrypted at rest using a per-device key. The key never syncs to the server, so password disclosure requires local filesystem access. SSH keys are still preferred on shared machines.")
.setDesc("Encrypted on this device. Use SSH keys on shared machines.")
.addText((t) => {
t.inputEl.type = "password";
t.setValue(this.plugin.settings.password).onChange((v) => {

View file

@ -85,15 +85,22 @@ export async function readLocalAsBuffer(adapter: DataAdapter, vaultPath: string)
return Buffer.from(ab);
}
/** Read a remote file as a Buffer (full content in memory). */
/** Read a remote file as a Buffer (full content in memory).
* Note: ssh2-sftp-client's `get()` (via the underlying concat-stream) returns an empty
* array `[]` instead of an empty Buffer when the remote file is zero bytes, so we
* defensively coerce to a real Buffer here. Callers downstream rely on `.buffer`,
* `.byteOffset`, and `Buffer.isBuffer` semantics that the empty-array sentinel
* doesn't satisfy. */
export async function downloadToBuffer(
client: SftpClient,
remoteRoot: string,
vaultPath: string,
): Promise<Buffer> {
const remotePath = remotePathOf(remoteRoot, vaultPath);
const buf = (await client.raw.get(remotePath)) as Buffer;
return buf;
const result = await client.raw.get(remotePath);
if (Buffer.isBuffer(result)) return result;
// 0-byte file → concat-stream gave us [] — return a real empty Buffer instead.
return Buffer.alloc(0);
}
/** Write a Buffer into the vault at `vaultPath`, atomically via the given tmp directory.

View file

@ -174,10 +174,12 @@ export class PullEngine {
): Promise<void> {
const remotePath = remotePathOf(this.settings.remoteRoot, vaultPath);
// Get full file content. ssh2-sftp-client.get returns a Buffer when no stream destination is given.
// Get full file content. ssh2-sftp-client.get returns a Buffer for non-empty files,
// but an empty array `[]` (via concat-stream) for zero-byte files. Coerce to Buffer.
let buf: Buffer;
try {
buf = (await client.raw.get(remotePath)) as Buffer;
const result = await client.raw.get(remotePath);
buf = Buffer.isBuffer(result) ? result : Buffer.alloc(0);
} catch (err) {
throw new Error(`Cannot fetch remote ${remotePath}: ${(err as Error).message}`);
}

View file

@ -2,5 +2,6 @@
"0.9.6": "1.5.0",
"0.9.7": "1.5.0",
"0.9.8": "1.5.0",
"0.9.9": "1.5.0"
"0.9.9": "1.5.0",
"0.9.10": "1.5.0"
}