@narumitw/pi-sync 0.52.1 → 0.53.1
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/README.md +3 -4
- package/dist/chunks/{chunk-WKPX2SIC.ts → chunk-FJGNDYGC.ts} +2 -2
- package/dist/chunks/{chunk-NLS2NV3K.ts → chunk-QDPED3XJ.ts} +67 -8
- package/dist/chunks/{chunk-NLS2NV3K.ts.map → chunk-QDPED3XJ.ts.map} +3 -3
- package/dist/chunks/{chunk-QHSQCK7L.ts → chunk-SDZCTCYQ.ts} +4 -4
- package/dist/chunks/{chunk-2CBF6SMT.ts → chunk-TRCSEIAQ.ts} +2 -2
- package/dist/chunks/{chunk-7D6RDCVC.ts → chunk-WFKUKVCP.ts} +2 -2
- package/dist/chunks/{chunk-UETBKTDI.ts → chunk-YDVQYUHE.ts} +1 -1
- package/dist/chunks/{chunk-UETBKTDI.ts.map → chunk-YDVQYUHE.ts.map} +2 -2
- package/dist/chunks/{conflict-review-4WPGCXFB.ts → conflict-review-JANNPKKS.ts} +4 -4
- package/dist/chunks/{git-backend-HLGKUQJT.ts → git-backend-FD7AVSK6.ts} +2 -2
- package/dist/chunks/{manager-result-dispatcher-VIDT3QBU.ts → manager-result-dispatcher-AAEAYX57.ts} +4 -4
- package/dist/chunks/{manager-ui-HSMHS3OK.ts → manager-ui-MHAHAKG3.ts} +7 -7
- package/dist/chunks/{remote-selection-ui-SMF5CLFO.ts → remote-selection-ui-Q2HUIBTL.ts} +3 -3
- package/dist/chunks/{s3-backend-DJ2NT3SZ.ts → s3-backend-AQGV6LFT.ts} +116 -12
- package/dist/chunks/s3-backend-AQGV6LFT.ts.map +7 -0
- package/dist/chunks/{sync-inspection-VBJF2FWC.ts → sync-inspection-K45QVLHB.ts} +3 -3
- package/dist/chunks/{sync-operations-RHNECOOG.ts → sync-operations-Y27F5T7W.ts} +5 -5
- package/dist/chunks/{webdav-backend-ETQGSI6I.ts → webdav-backend-TBILVEQK.ts} +2 -2
- package/dist/index.ts +5 -5
- package/package.json +1 -1
- package/src/backends/s3/s3-backend.ts +45 -6
- package/src/backends/s3/s3-client.ts +36 -7
- package/src/backends/s3/s3-conditions.ts +67 -0
- package/src/backends/sync-backend.ts +2 -0
- package/src/sync/merge-transfer-error.ts +72 -0
- package/src/sync/merged-sync.ts +8 -5
- package/dist/chunks/s3-backend-DJ2NT3SZ.ts.map +0 -7
- /package/dist/chunks/{chunk-WKPX2SIC.ts.map → chunk-FJGNDYGC.ts.map} +0 -0
- /package/dist/chunks/{chunk-QHSQCK7L.ts.map → chunk-SDZCTCYQ.ts.map} +0 -0
- /package/dist/chunks/{chunk-2CBF6SMT.ts.map → chunk-TRCSEIAQ.ts.map} +0 -0
- /package/dist/chunks/{chunk-7D6RDCVC.ts.map → chunk-WFKUKVCP.ts.map} +0 -0
- /package/dist/chunks/{conflict-review-4WPGCXFB.ts.map → conflict-review-JANNPKKS.ts.map} +0 -0
- /package/dist/chunks/{git-backend-HLGKUQJT.ts.map → git-backend-FD7AVSK6.ts.map} +0 -0
- /package/dist/chunks/{manager-result-dispatcher-VIDT3QBU.ts.map → manager-result-dispatcher-AAEAYX57.ts.map} +0 -0
- /package/dist/chunks/{manager-ui-HSMHS3OK.ts.map → manager-ui-MHAHAKG3.ts.map} +0 -0
- /package/dist/chunks/{remote-selection-ui-SMF5CLFO.ts.map → remote-selection-ui-Q2HUIBTL.ts.map} +0 -0
- /package/dist/chunks/{sync-inspection-VBJF2FWC.ts.map → sync-inspection-K45QVLHB.ts.map} +0 -0
- /package/dist/chunks/{sync-operations-RHNECOOG.ts.map → sync-operations-Y27F5T7W.ts.map} +0 -0
- /package/dist/chunks/{webdav-backend-ETQGSI6I.ts.map → webdav-backend-TBILVEQK.ts.map} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
3
|
import {
|
|
4
4
|
inspectSync
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-WFKUKVCP.ts";
|
|
6
6
|
import {
|
|
7
7
|
applySnapshot,
|
|
8
8
|
countPreservedRemoteFiles,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
formatSnapshotOnlyDiff,
|
|
15
15
|
mergeSync,
|
|
16
16
|
publicationCapabilityDescription
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-QDPED3XJ.ts";
|
|
18
18
|
import {
|
|
19
19
|
readMergeJournal,
|
|
20
20
|
requireNoMergeJournal,
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
} from "./chunk-V6VZ5TLG.ts";
|
|
42
42
|
import {
|
|
43
43
|
createSyncBackend
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-SDZCTCYQ.ts";
|
|
45
45
|
import "./chunk-ANWNQIR7.ts";
|
|
46
46
|
import {
|
|
47
47
|
backupLocal,
|
|
@@ -79,7 +79,7 @@ import "./chunk-2JZFHNLM.ts";
|
|
|
79
79
|
import "./chunk-T2SJD3N7.ts";
|
|
80
80
|
import {
|
|
81
81
|
expectedRemoteHead
|
|
82
|
-
} from "./chunk-
|
|
82
|
+
} from "./chunk-YDVQYUHE.ts";
|
|
83
83
|
import "./chunk-ZX2Q6THB.ts";
|
|
84
84
|
import {
|
|
85
85
|
errorMessage
|
|
@@ -876,4 +876,4 @@ export {
|
|
|
876
876
|
status,
|
|
877
877
|
syncBoth
|
|
878
878
|
};
|
|
879
|
-
//# sourceMappingURL=sync-operations-
|
|
879
|
+
//# sourceMappingURL=sync-operations-Y27F5T7W.ts.map
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
SyncBackendConflictError,
|
|
5
5
|
SyncBackendPublicationOutcomeUnknownError
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YDVQYUHE.ts";
|
|
7
7
|
import {
|
|
8
8
|
decodeSnapshot,
|
|
9
9
|
encodeSnapshot
|
|
@@ -854,4 +854,4 @@ export {
|
|
|
854
854
|
snapshotsPath,
|
|
855
855
|
webDavBackendIdentity
|
|
856
856
|
};
|
|
857
|
-
//# sourceMappingURL=webdav-backend-
|
|
857
|
+
//# sourceMappingURL=webdav-backend-TBILVEQK.ts.map
|
package/dist/index.ts
CHANGED
|
@@ -176,7 +176,7 @@ async function executeCommand(rawArgs, ctx, signal, loaders, onCommit, setup) {
|
|
|
176
176
|
return { kind: "completed" };
|
|
177
177
|
}
|
|
178
178
|
case "conflicts": {
|
|
179
|
-
const { showConflicts } = await import("./chunks/conflict-review-
|
|
179
|
+
const { showConflicts } = await import("./chunks/conflict-review-JANNPKKS.ts");
|
|
180
180
|
throwIfAborted(options.signal);
|
|
181
181
|
await withLock("sync", () => showConflicts(ctx, options));
|
|
182
182
|
return { kind: "completed" };
|
|
@@ -376,7 +376,7 @@ async function handleCommand(rawArgs, ctx, sessionSignal, loaders, attention) {
|
|
|
376
376
|
);
|
|
377
377
|
if (!rawArgs.trim()) {
|
|
378
378
|
try {
|
|
379
|
-
const { showSyncManager } = await import("./chunks/manager-ui-
|
|
379
|
+
const { showSyncManager } = await import("./chunks/manager-ui-MHAHAKG3.ts");
|
|
380
380
|
if (sessionSignal.aborted) return;
|
|
381
381
|
await showSyncManager(ctx, run, sessionSignal, {
|
|
382
382
|
getAttention: () => attention.current(),
|
|
@@ -476,7 +476,7 @@ function directSelectionOrigin(rawArgs) {
|
|
|
476
476
|
async function resolveSelectionAttention(ctx, attention, signal, loaders, options = {}) {
|
|
477
477
|
const current = attention.current();
|
|
478
478
|
if (!current || signal.aborted) return;
|
|
479
|
-
const { dispatchManagerResult } = await import("./chunks/manager-result-dispatcher-
|
|
479
|
+
const { dispatchManagerResult } = await import("./chunks/manager-result-dispatcher-AAEAYX57.ts");
|
|
480
480
|
if (signal.aborted || attention.current() !== current) return;
|
|
481
481
|
await dispatchManagerResult(
|
|
482
482
|
ctx,
|
|
@@ -755,11 +755,11 @@ function createStartupCheck(loaders, attention, timeoutMs = 3e4) {
|
|
|
755
755
|
// src/sync/sync-loaders.ts
|
|
756
756
|
function createSyncLoaders(dependencies) {
|
|
757
757
|
const loaders = {
|
|
758
|
-
inspection: cachedModuleLoader(dependencies.loadSyncInspection ?? (() => import("./chunks/sync-inspection-
|
|
758
|
+
inspection: cachedModuleLoader(dependencies.loadSyncInspection ?? (() => import("./chunks/sync-inspection-K45QVLHB.ts"))),
|
|
759
759
|
setupSwitch: cachedModuleLoader(dependencies.loadSetupSwitch ?? (() => import("./chunks/setup-switch-KTUWL6DE.ts"))),
|
|
760
760
|
snapshot: cachedModuleLoader(dependencies.loadSnapshot ?? (() => import("./chunks/snapshot-THS5EY2L.ts"))),
|
|
761
761
|
syncState: cachedModuleLoader(dependencies.loadSyncState ?? (() => import("./chunks/sync-state-CFL4RRRB.ts"))),
|
|
762
|
-
operations: cachedModuleLoader(dependencies.loadSyncOperations ?? (() => import("./chunks/sync-operations-
|
|
762
|
+
operations: cachedModuleLoader(dependencies.loadSyncOperations ?? (() => import("./chunks/sync-operations-Y27F5T7W.ts")))
|
|
763
763
|
};
|
|
764
764
|
return loaders;
|
|
765
765
|
}
|
package/package.json
CHANGED
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
SyncBackendConflictError,
|
|
19
19
|
SyncBackendPublicationOutcomeUnknownError,
|
|
20
20
|
} from "../sync-backend.js";
|
|
21
|
-
import { S3Client, S3ObjectAlreadyExistsError } from "./s3-client.js";
|
|
21
|
+
import { S3Client, S3HttpError, S3ObjectAlreadyExistsError } from "./s3-client.js";
|
|
22
|
+
import { requireS3Etag, verifyS3Conditions } from "./s3-conditions.js";
|
|
22
23
|
|
|
23
24
|
const VERSION = 1;
|
|
24
25
|
const POST_COMMIT_TIMEOUT_MS = 30_000;
|
|
@@ -26,8 +27,9 @@ const POST_COMMIT_TIMEOUT_MS = 30_000;
|
|
|
26
27
|
export class S3SyncBackend implements SyncBackend {
|
|
27
28
|
readonly identity: string;
|
|
28
29
|
readonly destination: string;
|
|
29
|
-
readonly capability = "
|
|
30
|
+
readonly capability = "conditional-required" as const;
|
|
30
31
|
private readonly checksums = new Map<string, string>();
|
|
32
|
+
private recoveryHead?: { current: string; legacy: string };
|
|
31
33
|
|
|
32
34
|
constructor(
|
|
33
35
|
private readonly config: ResolvedS3Backend,
|
|
@@ -42,7 +44,18 @@ export class S3SyncBackend implements SyncBackend {
|
|
|
42
44
|
return left === right;
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
matchesUncommittedRecoveryHead(current: RemoteHead, recorded: RemoteHead) {
|
|
48
|
+
return (
|
|
49
|
+
this.sameRevision(current.revision, recorded.revision) ||
|
|
50
|
+
(current.snapshotId === recorded.snapshotId &&
|
|
51
|
+
current.snapshotRef === recorded.snapshotRef &&
|
|
52
|
+
current.revision === this.recoveryHead?.current &&
|
|
53
|
+
recorded.revision === this.recoveryHead?.legacy)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
45
57
|
async readHead(signal?: AbortSignal): Promise<RemoteHead | undefined> {
|
|
58
|
+
this.recoveryHead = undefined;
|
|
46
59
|
const object = await new S3Client(this.config, signal).getJson<LatestPointer>(latestKey(this.config));
|
|
47
60
|
throwIfAborted(signal);
|
|
48
61
|
if (object.missing) return undefined;
|
|
@@ -52,7 +65,16 @@ export class S3SyncBackend implements SyncBackend {
|
|
|
52
65
|
this.config.destination.namespace,
|
|
53
66
|
);
|
|
54
67
|
this.registerChecksum(pointer.snapshot, pointer.sha256);
|
|
55
|
-
|
|
68
|
+
const head = remoteHead(pointer, this.identity, object.etag);
|
|
69
|
+
if (object.etag && /^"[^"\r\n]+"$/u.test(object.etag)) {
|
|
70
|
+
// Old clients hashed R2's compression-weakened ETag. Bind the alias to the
|
|
71
|
+
// exact current pointer and strong ETag, solely for inactive-journal retirement.
|
|
72
|
+
this.recoveryHead = {
|
|
73
|
+
current: head.revision,
|
|
74
|
+
legacy: remoteHead(pointer, this.identity, `W/${object.etag}`).revision,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return head;
|
|
56
78
|
}
|
|
57
79
|
|
|
58
80
|
async readSnapshot(reference: string, signal?: AbortSignal): Promise<Snapshot> {
|
|
@@ -78,6 +100,8 @@ export class S3SyncBackend implements SyncBackend {
|
|
|
78
100
|
): Promise<PublishSnapshotResult> {
|
|
79
101
|
throwIfAborted(options.signal);
|
|
80
102
|
assertSnapshotIdentity(snapshot, this.config.destination.namespace);
|
|
103
|
+
await verifyS3Conditions(this.config, options.signal);
|
|
104
|
+
throwIfAborted(options.signal);
|
|
81
105
|
const stagedKey = snapshotKey(this.config, snapshot.id);
|
|
82
106
|
const encoded = await encodeSnapshot(snapshot);
|
|
83
107
|
throwIfAborted(options.signal);
|
|
@@ -113,6 +137,7 @@ export class S3SyncBackend implements SyncBackend {
|
|
|
113
137
|
currentHead: current,
|
|
114
138
|
});
|
|
115
139
|
}
|
|
140
|
+
const condition = currentObject.missing ? { ifAbsent: true } : { ifMatch: requireS3Etag(currentObject.etag) };
|
|
116
141
|
throwIfAborted(options.signal);
|
|
117
142
|
options.onCommit?.();
|
|
118
143
|
|
|
@@ -120,8 +145,16 @@ export class S3SyncBackend implements SyncBackend {
|
|
|
120
145
|
// verification to a user-cancellation signal after the boundary begins.
|
|
121
146
|
const commitClient = new S3Client(this.config, AbortSignal.timeout(this.postCommitTimeoutMs));
|
|
122
147
|
try {
|
|
123
|
-
await commitClient.putJson(latestKey(this.config), pointer);
|
|
148
|
+
await commitClient.putJson(latestKey(this.config), pointer, condition);
|
|
124
149
|
} catch (error) {
|
|
150
|
+
if (
|
|
151
|
+
error instanceof S3ObjectAlreadyExistsError ||
|
|
152
|
+
(error instanceof S3HttpError && (error.status === 412 || error.status === 409))
|
|
153
|
+
) {
|
|
154
|
+
throw new SyncBackendConflictError("Remote conditional publication was rejected; reread and retry.", {
|
|
155
|
+
cause: error,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
125
158
|
throw new SyncBackendPublicationOutcomeUnknownError(
|
|
126
159
|
`Remote publication outcome is unknown: ${errorMessage(error)}`,
|
|
127
160
|
{ cause: error },
|
|
@@ -256,8 +289,14 @@ export class S3SyncBackend implements SyncBackend {
|
|
|
256
289
|
private async updateHistory(client: S3Client, pointer: LatestPointer) {
|
|
257
290
|
const object = await client.getJson<{ version: number; snapshots: LatestPointer[] }>(historyKey(this.config));
|
|
258
291
|
const snapshots = object.missing ? [] : requireHistory(object.value, this.config.destination.namespace);
|
|
259
|
-
const
|
|
260
|
-
|
|
292
|
+
const existing = snapshots.find((snapshot) => snapshot.snapshot === pointer.snapshot);
|
|
293
|
+
if (existing) {
|
|
294
|
+
if (!samePointer(existing, pointer)) throw new Error("Remote history rebound an immutable snapshot reference.");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const next = [...snapshots, pointer].slice(-100);
|
|
298
|
+
const condition = object.missing ? { ifAbsent: true } : { ifMatch: requireS3Etag(object.etag) };
|
|
299
|
+
await client.putJson(historyKey(this.config), { version: VERSION, snapshots: next }, condition);
|
|
261
300
|
}
|
|
262
301
|
}
|
|
263
302
|
|
|
@@ -132,17 +132,27 @@ export class S3Client {
|
|
|
132
132
|
throw lastError;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
async putJson(key: string, value: unknown) {
|
|
135
|
+
async putJson(key: string, value: unknown, options: { ifAbsent?: boolean; ifMatch?: string } = {}) {
|
|
136
136
|
const body = Buffer.from(JSON.stringify(value, null, "\t"), "utf8");
|
|
137
|
-
await this.putBuffer(key, body, "application/json");
|
|
137
|
+
await this.putBuffer(key, body, "application/json", options);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
async putBuffer(
|
|
140
|
+
async putBuffer(
|
|
141
|
+
key: string,
|
|
142
|
+
body: Buffer,
|
|
143
|
+
contentType: string,
|
|
144
|
+
options: { ifAbsent?: boolean; ifMatch?: string } = {},
|
|
145
|
+
) {
|
|
141
146
|
const headers: Record<string, string> = { "content-type": contentType };
|
|
142
147
|
if (options.ifAbsent) headers["if-none-match"] = "*";
|
|
148
|
+
if (options.ifMatch) headers["if-match"] = options.ifMatch;
|
|
143
149
|
const response = await this.request("PUT", key, body, headers);
|
|
144
|
-
if (options.ifAbsent && response.status === 412) {
|
|
145
|
-
|
|
150
|
+
if ((options.ifAbsent || options.ifMatch) && (response.status === 412 || response.status === 409)) {
|
|
151
|
+
await response.body?.cancel();
|
|
152
|
+
if (options.ifAbsent && response.status === 412) throw new S3ObjectAlreadyExistsError(key);
|
|
153
|
+
if (options.ifAbsent || options.ifMatch) {
|
|
154
|
+
throw new S3HttpError(`S3 conditional PUT rejected (${response.status}).`, response.status);
|
|
155
|
+
}
|
|
146
156
|
}
|
|
147
157
|
if (!response.ok) {
|
|
148
158
|
throw new S3HttpError(
|
|
@@ -150,9 +160,26 @@ export class S3Client {
|
|
|
150
160
|
response.status,
|
|
151
161
|
);
|
|
152
162
|
}
|
|
163
|
+
await response.body?.cancel();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async delete(key: string) {
|
|
167
|
+
const response = await this.request("DELETE", key);
|
|
168
|
+
if (!response.ok && response.status !== 404) {
|
|
169
|
+
throw new S3HttpError(
|
|
170
|
+
`S3 DELETE failed (${response.status}): ${await this.readErrorText(response)}`,
|
|
171
|
+
response.status,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
await response.body?.cancel();
|
|
153
175
|
}
|
|
154
176
|
|
|
155
|
-
private async request(
|
|
177
|
+
private async request(
|
|
178
|
+
method: "GET" | "PUT" | "DELETE",
|
|
179
|
+
key: string,
|
|
180
|
+
body?: Buffer,
|
|
181
|
+
extraHeaders: Record<string, string> = {},
|
|
182
|
+
) {
|
|
156
183
|
const url = new URL(this.endpoint.toString());
|
|
157
184
|
url.pathname = posixJoin(url.pathname, this.config.destination.bucket, encodeKey(key));
|
|
158
185
|
const send = async (sessionToken: string | undefined) => {
|
|
@@ -161,7 +188,9 @@ export class S3Client {
|
|
|
161
188
|
method,
|
|
162
189
|
url,
|
|
163
190
|
body,
|
|
164
|
-
|
|
191
|
+
// Transfer compression can weaken an object's ETag (R2 JSON responses).
|
|
192
|
+
// Read the original representation for exact checksums and strong If-Match tokens.
|
|
193
|
+
extraHeaders: method === "GET" ? { ...extraHeaders, "accept-encoding": "identity" } : extraHeaders,
|
|
165
194
|
accessKeyId: this.config.profile.accessKeyId,
|
|
166
195
|
secretAccessKey: this.config.profile.secretAccessKey,
|
|
167
196
|
sessionToken,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { posixJoin } from "../../paths.js";
|
|
3
|
+
import type { ResolvedS3Backend } from "../backend-types.js";
|
|
4
|
+
import { S3Client, S3HttpError, S3ObjectAlreadyExistsError } from "./s3-client.js";
|
|
5
|
+
|
|
6
|
+
export function requireS3Etag(value: string | undefined) {
|
|
7
|
+
if (!value || !/^"[^"\r\n]+"$/u.test(value)) throw new Error("S3 conditional publication requires a strong ETag.");
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Never probe the active pointer or immutable snapshots; ignored conditions must not corrupt either. */
|
|
12
|
+
export async function verifyS3Conditions(config: ResolvedS3Backend, signal?: AbortSignal) {
|
|
13
|
+
const key = posixJoin(
|
|
14
|
+
config.destination.prefix === "./" ? "" : config.destination.prefix,
|
|
15
|
+
".pi-sync-probes",
|
|
16
|
+
randomUUID(),
|
|
17
|
+
);
|
|
18
|
+
const client = new S3Client(config, signal);
|
|
19
|
+
let failure: unknown;
|
|
20
|
+
let attempted = false;
|
|
21
|
+
try {
|
|
22
|
+
signal?.throwIfAborted();
|
|
23
|
+
attempted = true;
|
|
24
|
+
await client.putBuffer(key, Buffer.from("first"), "text/plain", { ifAbsent: true });
|
|
25
|
+
const first = await client.getBuffer(key);
|
|
26
|
+
const etag = requireS3Etag(first.etag);
|
|
27
|
+
if (!first.value?.equals(Buffer.from("first"))) throw new Error("S3 conditional probe read did not match.");
|
|
28
|
+
await expectRejected(client.putBuffer(key, Buffer.from("ignored"), "text/plain", { ifAbsent: true }));
|
|
29
|
+
await expectRejected(client.putBuffer(key, Buffer.from("ignored"), "text/plain", { ifMatch: '"pi-sync-stale"' }));
|
|
30
|
+
const unchanged = await client.getBuffer(key);
|
|
31
|
+
if (!unchanged.value?.equals(first.value) || unchanged.etag !== etag)
|
|
32
|
+
throw new Error("S3 failed precondition changed the probe.");
|
|
33
|
+
await client.putBuffer(key, Buffer.from("second"), "text/plain", { ifMatch: etag });
|
|
34
|
+
const second = await client.getBuffer(key);
|
|
35
|
+
if (!second.value?.equals(Buffer.from("second")) || requireS3Etag(second.etag) === etag) {
|
|
36
|
+
throw new Error("S3 conditional write did not rotate the probe ETag.");
|
|
37
|
+
}
|
|
38
|
+
await expectRejected(client.putBuffer(key, Buffer.from("ignored"), "text/plain", { ifMatch: etag }));
|
|
39
|
+
const final = await client.getBuffer(key);
|
|
40
|
+
if (!final.value?.equals(second.value) || final.etag !== second.etag)
|
|
41
|
+
throw new Error("S3 stale ETag changed the probe.");
|
|
42
|
+
signal?.throwIfAborted();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
failure = error;
|
|
45
|
+
}
|
|
46
|
+
// Cleanup has its own bounded deadline even when the owning session was cancelled.
|
|
47
|
+
if (attempted) {
|
|
48
|
+
try {
|
|
49
|
+
await new S3Client(config, AbortSignal.timeout(10_000)).delete(key);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new Error(`S3 probe cleanup failed; remove ${key}.`, { cause: failure ?? error });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (failure) throw failure;
|
|
55
|
+
signal?.throwIfAborted();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function expectRejected(operation: Promise<void>) {
|
|
59
|
+
try {
|
|
60
|
+
await operation;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
// 409 is retryable contention, not proof that a failed condition was enforced.
|
|
63
|
+
if (error instanceof S3ObjectAlreadyExistsError || (error instanceof S3HttpError && error.status === 412)) return;
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
throw new Error("S3 server ignored a write precondition; publication stopped for safety.");
|
|
67
|
+
}
|
|
@@ -52,6 +52,8 @@ export interface SyncBackend {
|
|
|
52
52
|
readonly capability: PublicationCapability;
|
|
53
53
|
/** Compare opaque revisions produced by this backend identity. */
|
|
54
54
|
sameRevision(left: string, right: string): boolean;
|
|
55
|
+
/** Recovery-only equivalence for retiring an inactive candidate; never authorizes publication or apply. */
|
|
56
|
+
matchesUncommittedRecoveryHead?(current: RemoteHead, recorded: RemoteHead): boolean;
|
|
55
57
|
readHead(signal?: AbortSignal): Promise<RemoteHead | undefined>;
|
|
56
58
|
readSnapshot(reference: string, signal?: AbortSignal): Promise<Snapshot>;
|
|
57
59
|
publishSnapshot(
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ResolvedSyncBackend } from "../backends/backend-types.js";
|
|
2
|
+
import { safeTerminalText } from "../ui/terminal-text.js";
|
|
3
|
+
|
|
4
|
+
const MAX_CAUSES = 4;
|
|
5
|
+
const MAX_DETAIL_LENGTH = 600;
|
|
6
|
+
|
|
7
|
+
/** Only the sanitized message is displayed; retain the original cause for outcome handling. */
|
|
8
|
+
export function mergeTransferError(error: unknown, backend: ResolvedSyncBackend): Error {
|
|
9
|
+
const messages: string[] = [];
|
|
10
|
+
const seen = new Set<Error>();
|
|
11
|
+
let current = error;
|
|
12
|
+
while (current instanceof Error && !seen.has(current) && seen.size < MAX_CAUSES) {
|
|
13
|
+
seen.add(current);
|
|
14
|
+
const code = (current as NodeJS.ErrnoException).code;
|
|
15
|
+
const status = (current as Error & { status?: unknown }).status;
|
|
16
|
+
const metadata = [
|
|
17
|
+
typeof code === "string" && /^[A-Z][A-Z0-9_]{1,63}$/u.test(code) ? code : undefined,
|
|
18
|
+
typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599
|
|
19
|
+
? `HTTP ${status}`
|
|
20
|
+
: undefined,
|
|
21
|
+
].filter(Boolean);
|
|
22
|
+
const message = `${current.message}${metadata.length ? ` (${metadata.join(", ")})` : ""}`;
|
|
23
|
+
if (message && !messages.includes(message)) messages.push(message);
|
|
24
|
+
current = current.cause;
|
|
25
|
+
}
|
|
26
|
+
let detail = messages.join(" → ") || "No error details available.";
|
|
27
|
+
// Redact before truncation or terminal escaping, including overlapping and encoded credentials.
|
|
28
|
+
for (const secret of credentialVariants(backend)) detail = detail.replaceAll(secret, "[REDACTED]");
|
|
29
|
+
detail = detail.replace(/\b(Bearer|Basic)\s+[^\s<>"']+/giu, "$1 [REDACTED]");
|
|
30
|
+
detail = safeTerminalText(detail);
|
|
31
|
+
if (detail.length > MAX_DETAIL_LENGTH) detail = `${detail.slice(0, MAX_DETAIL_LENGTH)}… (details truncated)`;
|
|
32
|
+
return new Error(
|
|
33
|
+
`Merged transfer interrupted; journal and backup retained. Run /sync sync to reconcile before further mutations. Transfer failure: ${detail}`,
|
|
34
|
+
{ cause: error },
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function credentialVariants(backend: ResolvedSyncBackend): string[] {
|
|
39
|
+
const secrets: string[] = [];
|
|
40
|
+
let address: string;
|
|
41
|
+
switch (backend.type) {
|
|
42
|
+
case "s3":
|
|
43
|
+
secrets.push(backend.profile.accessKeyId, backend.profile.secretAccessKey, backend.profile.sessionToken ?? "");
|
|
44
|
+
address = backend.profile.endpoint;
|
|
45
|
+
break;
|
|
46
|
+
case "webdav":
|
|
47
|
+
secrets.push(
|
|
48
|
+
backend.profile.username,
|
|
49
|
+
backend.profile.password,
|
|
50
|
+
Buffer.from(`${backend.profile.username}:${backend.profile.password}`).toString("base64"),
|
|
51
|
+
);
|
|
52
|
+
address = backend.profile.url;
|
|
53
|
+
break;
|
|
54
|
+
case "git":
|
|
55
|
+
address = backend.profile.remote;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const url = new URL(address);
|
|
60
|
+
secrets.push(url.username, url.password, ...url.searchParams.values());
|
|
61
|
+
for (const value of [url.username, url.password]) secrets.push(decodeURIComponent(value));
|
|
62
|
+
} catch {
|
|
63
|
+
// Git SSH shorthand is not a URL; configured Git remotes cannot contain credentials.
|
|
64
|
+
}
|
|
65
|
+
return [
|
|
66
|
+
...new Set(
|
|
67
|
+
secrets
|
|
68
|
+
.filter(Boolean)
|
|
69
|
+
.flatMap((value) => [value, encodeURIComponent(value), JSON.stringify(value).slice(1, -1)]),
|
|
70
|
+
),
|
|
71
|
+
].sort((left, right) => right.length - left.length);
|
|
72
|
+
}
|
package/src/sync/merged-sync.ts
CHANGED
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
readMergeJournal,
|
|
53
53
|
writeMergeJournal,
|
|
54
54
|
} from "./merge-journal.js";
|
|
55
|
+
import { mergeTransferError } from "./merge-transfer-error.js";
|
|
55
56
|
import { acceptedMergeFiles, type PartialProgress, progressState } from "./partial-progress.js";
|
|
56
57
|
import {
|
|
57
58
|
formatRemoteSelectionStatus,
|
|
@@ -497,10 +498,7 @@ export async function mergeSync(
|
|
|
497
498
|
}
|
|
498
499
|
// Cancellation before publication is safe only if no backend attempt began.
|
|
499
500
|
// Every uncertain/after-commit outcome retains evidence and is never blindly retried.
|
|
500
|
-
throw
|
|
501
|
-
"Merged transfer interrupted; journal and backup retained. Run /sync sync to reconcile before further mutations.",
|
|
502
|
-
{ cause: error },
|
|
503
|
-
);
|
|
501
|
+
throw mergeTransferError(error, config.backend);
|
|
504
502
|
}
|
|
505
503
|
const completed = await completeJournal(ctx, config, backend, journal, validate, options.signal, options.auto);
|
|
506
504
|
if (!completed) {
|
|
@@ -557,7 +555,12 @@ async function completeJournal(
|
|
|
557
555
|
}
|
|
558
556
|
const snapshotOptions = { ...snapshotOptionsForContext(ctx, config), sessionDir: sessionRoot, signal };
|
|
559
557
|
const head = await backend.readHead(signal);
|
|
560
|
-
if (
|
|
558
|
+
if (
|
|
559
|
+
!journal.committedHead &&
|
|
560
|
+
head &&
|
|
561
|
+
(backend.sameRevision(head.revision, journal.expectedHead.revision) ||
|
|
562
|
+
backend.matchesUncommittedRecoveryHead?.(head, journal.expectedHead))
|
|
563
|
+
) {
|
|
561
564
|
await validate();
|
|
562
565
|
const local = await createSnapshot(config.snapshotIdentity, snapshotOptions);
|
|
563
566
|
if (
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/backends/s3/s3-backend.ts", "../../src/backends/s3/s3-client.ts"],
|
|
4
|
-
"sourcesContent": ["import { createHash } from \"node:crypto\";\nimport { encodeKey, posixJoin } from \"../../paths.js\";\nimport { sessionTokenWarnings } from \"../../settings/config.js\";\nimport { decodeSnapshot, encodeSnapshot } from \"../../snapshot/snapshot-codec.js\";\nimport type { Snapshot } from \"../../snapshot/snapshot-types.js\";\nimport { validatePortableSnapshot } from \"../../sync/local-fields.js\";\nimport { syncErrorGuidance } from \"../../sync/sync-error-guidance.js\";\nimport { portableSnapshotSelection } from \"../../sync/sync-policy.js\";\nimport type { LatestPointer, RemoteObject, ResolvedS3Backend } from \"../backend-types.js\";\nimport {\n type BackendDiagnostic,\n type ExpectedRemoteHead,\n type PublishSnapshotOptions,\n type PublishSnapshotResult,\n type RemoteHead,\n type RemoteHistoryEntry,\n type SyncBackend,\n SyncBackendConflictError,\n SyncBackendPublicationOutcomeUnknownError,\n} from \"../sync-backend.js\";\nimport { S3Client, S3ObjectAlreadyExistsError } from \"./s3-client.js\";\n\nconst VERSION = 1;\nconst POST_COMMIT_TIMEOUT_MS = 30_000;\n\nexport class S3SyncBackend implements SyncBackend {\n readonly identity: string;\n readonly destination: string;\n readonly capability = \"read-check-write-verify\" as const;\n private readonly checksums = new Map<string, string>();\n\n constructor(\n private readonly config: ResolvedS3Backend,\n private readonly postCommitTimeoutMs = POST_COMMIT_TIMEOUT_MS,\n ) {\n assertSafeDestination(config);\n this.identity = s3BackendIdentity(config);\n this.destination = s3Destination(config);\n }\n\n sameRevision(left: string, right: string) {\n return left === right;\n }\n\n async readHead(signal?: AbortSignal): Promise<RemoteHead | undefined> {\n const object = await new S3Client(this.config, signal).getJson<LatestPointer>(latestKey(this.config));\n throwIfAborted(signal);\n if (object.missing) return undefined;\n const pointer = requirePointer(\n object.value,\n \"Remote latest pointer is malformed.\",\n this.config.destination.namespace,\n );\n this.registerChecksum(pointer.snapshot, pointer.sha256);\n return remoteHead(pointer, this.identity, object.etag);\n }\n\n async readSnapshot(reference: string, signal?: AbortSignal): Promise<Snapshot> {\n const expectedChecksum = this.checksums.get(reference) ?? (await this.resolveChecksum(reference, signal));\n const object = await new S3Client(this.config, signal).getBuffer(snapshotKey(this.config, reference));\n throwIfAborted(signal);\n if (!object.value) throw new Error(`Snapshot not found: ${reference}`);\n if (expectedChecksum && sha256(object.value) !== expectedChecksum) {\n throw new Error(\"Remote snapshot checksum mismatch.\");\n }\n const snapshot = await decodeSnapshot(object.value, { signal });\n if (snapshot.id !== reference || snapshot.profile !== this.config.destination.namespace) {\n throw new Error(\"Remote snapshot identity mismatch.\");\n }\n validateSnapshotBundle(snapshot);\n return snapshot;\n }\n\n async publishSnapshot(\n snapshot: Snapshot,\n expected: ExpectedRemoteHead,\n options: PublishSnapshotOptions = {},\n ): Promise<PublishSnapshotResult> {\n throwIfAborted(options.signal);\n assertSnapshotIdentity(snapshot, this.config.destination.namespace);\n const stagedKey = snapshotKey(this.config, snapshot.id);\n const encoded = await encodeSnapshot(snapshot);\n throwIfAborted(options.signal);\n const pointer = pointerFor(this.config, snapshot, sha256(encoded));\n const cancellableClient = new S3Client(this.config, options.signal);\n try {\n await cancellableClient.putBuffer(stagedKey, encoded, \"application/gzip\", {\n ifAbsent: true,\n });\n } catch (error) {\n if (!(error instanceof S3ObjectAlreadyExistsError)) throw error;\n const existing = await cancellableClient.getBuffer(stagedKey);\n if (!existing.value || sha256(existing.value) !== pointer.sha256) {\n throw new SyncBackendConflictError(\n `Immutable snapshot id already exists with different content: ${snapshot.id}`,\n );\n }\n }\n const currentObject = await cancellableClient.getJson<LatestPointer>(latestKey(this.config));\n throwIfAborted(options.signal);\n let current: RemoteHead | undefined;\n if (!currentObject.missing) {\n const currentPointer = requirePointer(\n currentObject.value,\n \"Remote latest pointer is malformed.\",\n this.config.destination.namespace,\n );\n this.registerChecksum(currentPointer.snapshot, currentPointer.sha256);\n current = remoteHead(currentPointer, this.identity, currentObject.etag);\n }\n if (!matchesExpected(current, expected)) {\n throw new SyncBackendConflictError(\"Remote changed while pushing. Run /sync pull first, then retry.\", {\n currentHead: current,\n });\n }\n throwIfAborted(options.signal);\n options.onCommit?.();\n\n // The active-head PUT is the publication boundary. Do not bind it or its\n // verification to a user-cancellation signal after the boundary begins.\n const commitClient = new S3Client(this.config, AbortSignal.timeout(this.postCommitTimeoutMs));\n try {\n await commitClient.putJson(latestKey(this.config), pointer);\n } catch (error) {\n throw new SyncBackendPublicationOutcomeUnknownError(\n `Remote publication outcome is unknown: ${errorMessage(error)}`,\n { cause: error },\n );\n }\n\n let verifiedObject: RemoteObject<LatestPointer>;\n try {\n verifiedObject = await commitClient.getJson<LatestPointer>(latestKey(this.config));\n } catch (error) {\n throw new SyncBackendPublicationOutcomeUnknownError(\n `Remote snapshot may be active, but publication could not be verified: ${errorMessage(error)}`,\n { cause: error },\n );\n }\n let verifiedPointer: LatestPointer;\n try {\n verifiedPointer = requirePointer(\n verifiedObject.value,\n \"Remote latest pointer is malformed after publication.\",\n this.config.destination.namespace,\n );\n } catch (error) {\n throw new SyncBackendPublicationOutcomeUnknownError(\n `Remote snapshot may be active, but publication verification was malformed: ${errorMessage(error)}`,\n { cause: error },\n );\n }\n if (!samePointer(verifiedPointer, pointer)) {\n throw new SyncBackendConflictError(\n \"Remote latest changed immediately after push. Run /sync status before continuing.\",\n {\n phase: \"after-commit\",\n currentHead: remoteHead(verifiedPointer, this.identity, verifiedObject.etag),\n candidateMayHaveBeenActive: true,\n },\n );\n }\n this.registerChecksum(verifiedPointer.snapshot, verifiedPointer.sha256);\n const head = remoteHead(verifiedPointer, this.identity, verifiedObject.etag);\n const warning = await this.updateHistorySafely(commitClient, pointer);\n return { head, warnings: warning ? [warning] : [] };\n }\n\n async listHistory(signal?: AbortSignal): Promise<RemoteHistoryEntry[]> {\n const object = await new S3Client(this.config, signal).getJson<{\n version: number;\n snapshots: LatestPointer[];\n }>(historyKey(this.config));\n throwIfAborted(signal);\n if (object.missing) return [];\n const pointers = requireHistory(object.value, this.config.destination.namespace);\n for (const pointer of pointers) {\n const known = this.checksums.get(pointer.snapshot);\n if (known && known !== pointer.sha256) {\n throw new Error(\"Remote history conflicts with an already observed snapshot checksum.\");\n }\n }\n for (const pointer of pointers) this.registerChecksum(pointer.snapshot, pointer.sha256);\n return pointers.map(remoteHistoryEntry);\n }\n\n async diagnose(signal?: AbortSignal): Promise<BackendDiagnostic[]> {\n throwIfAborted(signal);\n const diagnostics: BackendDiagnostic[] = [\n {\n key: \"s3-config\",\n level: \"info\",\n message: `s3 config: ok (${this.config.destination.bucket}/${storageRoot(this.config)})`,\n },\n ...sessionTokenWarnings(this.config.profile).map((message) => ({\n key: \"s3-session-token\",\n level: \"warning\" as const,\n message,\n })),\n ];\n const deadline = AbortSignal.timeout(10_000);\n const probeSignal = signal ? AbortSignal.any([signal, deadline]) : deadline;\n try {\n const result = await new S3Client(this.config, probeSignal).diagnoseRead(latestKey(this.config));\n throwIfAborted(probeSignal);\n const messages = {\n readable: \"s3 remote read: HTTP success. Snapshot contents and write access are not tested.\",\n \"missing-key\":\n \"s3 remote read: no snapshot at this path (NoSuchKey). Write access is not tested; verify the bucket and path before first sync.\",\n \"missing-bucket\":\n \"s3 remote read: bucket not found (NoSuchBucket). Create the bucket with your provider or edit the setup's bucket name.\",\n \"unknown-404\":\n \"s3 remote read: HTTP 404; cannot confirm whether the bucket or snapshot is missing. Verify the endpoint, bucket, and path with your provider. Write access is not tested.\",\n };\n diagnostics.push({\n key: \"s3-read\",\n level: result === \"readable\" ? \"info\" : \"warning\",\n message: messages[result],\n });\n } catch (error) {\n throwIfAborted(signal);\n diagnostics.push({\n key: \"s3-read\",\n level: \"warning\",\n message: syncErrorGuidance(deadline.aborted ? new Error(\"S3 diagnostic request timed out.\") : error),\n });\n }\n return diagnostics;\n }\n\n private async resolveChecksum(reference: string, signal?: AbortSignal) {\n await this.readHead(signal);\n const headChecksum = this.checksums.get(reference);\n if (headChecksum) return headChecksum;\n await this.listHistory(signal);\n return this.checksums.get(reference);\n }\n\n private registerChecksum(reference: string, checksum: string) {\n const known = this.checksums.get(reference);\n if (known && known !== checksum) {\n throw new Error(\"Remote snapshot reference was rebound to a different checksum.\");\n }\n this.checksums.set(reference, checksum);\n }\n\n private async updateHistorySafely(client: S3Client, pointer: LatestPointer) {\n try {\n await this.updateHistory(client, pointer);\n return undefined;\n } catch (error) {\n return `Remote snapshot is active, but history could not be updated: ${errorMessage(error)}. Run /sync doctor before relying on history.`;\n }\n }\n\n private async updateHistory(client: S3Client, pointer: LatestPointer) {\n const object = await client.getJson<{ version: number; snapshots: LatestPointer[] }>(historyKey(this.config));\n const snapshots = object.missing ? [] : requireHistory(object.value, this.config.destination.namespace);\n const next = [...snapshots.filter((snapshot) => snapshot.snapshot !== pointer.snapshot), pointer].slice(-100);\n await client.putJson(historyKey(this.config), { version: VERSION, snapshots: next });\n }\n}\n\nexport function latestKey(config: ResolvedS3Backend) {\n return posixJoin(storageRoot(config), \"latest.json\");\n}\n\nexport function historyKey(config: ResolvedS3Backend) {\n return posixJoin(storageRoot(config), \"history.json\");\n}\n\nexport function snapshotKey(config: ResolvedS3Backend, id: string) {\n requireSnapshotReference(id);\n return posixJoin(storageRoot(config), \"snapshots\", `${id}.json.gz`);\n}\n\nexport function storageRoot(config: ResolvedS3Backend) {\n return config.destination.prefix === \"./\" ? \"\" : config.destination.prefix;\n}\n\nexport function pointerFor(config: ResolvedS3Backend, snapshot: Snapshot, checksum: string): LatestPointer {\n return {\n version: VERSION,\n profile: config.destination.namespace,\n snapshot: snapshot.id,\n sha256: checksum,\n createdAt: snapshot.createdAt,\n machine: snapshot.machine,\n syncSessions: snapshot.syncSessions === true || snapshot.files.some((file) => file.path.startsWith(\"sessions/\")),\n ...(snapshot.selection === undefined ? {} : { selection: snapshot.selection }),\n };\n}\n\nexport function s3BackendIdentity(config: ResolvedS3Backend) {\n const destination = JSON.stringify([\n secretFreeEndpoint(config.profile.endpoint),\n trimSlashes(config.destination.bucket),\n trimSlashes(config.destination.prefix),\n ]);\n return `s3:${sha256(Buffer.from(destination))}`;\n}\n\nfunction s3Destination(config: ResolvedS3Backend) {\n let host = secretFreeEndpoint(config.profile.endpoint);\n try {\n host = new URL(host).hostname;\n } catch {\n host = \"invalid S3 endpoint\";\n }\n return `${host} \u00B7 ${config.destination.bucket}/${storageRoot(config)}`;\n}\n\nfunction secretFreeEndpoint(value: string) {\n const normalized = value.trim();\n try {\n const url = new URL(normalized);\n url.username = \"\";\n url.password = \"\";\n return url.toString();\n } catch {\n return normalized.replace(/\\/\\/[^/@\\s]+@/u, \"//\");\n }\n}\n\nfunction trimSlashes(value: string) {\n return value.replace(/^\\/+|\\/+$/g, \"\");\n}\n\nfunction remoteHistoryEntry(pointer: LatestPointer): RemoteHistoryEntry {\n return {\n snapshotRef: pointer.snapshot,\n snapshotId: pointer.snapshot,\n createdAt: pointer.createdAt,\n machine: pointer.machine,\n syncSessions: pointer.syncSessions === true,\n };\n}\n\nfunction remoteHead(pointer: LatestPointer, identity: string, etag?: string): RemoteHead {\n return {\n snapshotRef: pointer.snapshot,\n snapshotId: pointer.snapshot,\n revision: `s3:${sha256(Buffer.from(canonicalJson([identity, etag ?? null, pointer])))}`,\n createdAt: pointer.createdAt,\n machine: pointer.machine,\n syncSessions: pointer.syncSessions === true,\n ...(pointer.selection === undefined ? {} : { selection: pointer.selection }),\n };\n}\n\nfunction samePointer(left: LatestPointer, right: LatestPointer) {\n return canonicalJson(left) === canonicalJson(right);\n}\n\nfunction canonicalJson(value: unknown) {\n return JSON.stringify(canonicalValue(value));\n}\n\nfunction canonicalValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalValue);\n if (!value || typeof value !== \"object\") return value;\n return Object.fromEntries(\n Object.keys(value)\n .sort()\n .map((key) => [key, canonicalValue((value as Record<string, unknown>)[key])]),\n );\n}\n\nfunction matchesExpected(head: RemoteHead | undefined, expected: ExpectedRemoteHead) {\n if (expected.kind === \"missing\") return head === undefined;\n return head?.revision === expected.revision;\n}\n\nfunction requirePointer(value: LatestPointer | undefined, message: string, expectedProfile: string) {\n if (\n !value ||\n value.version !== VERSION ||\n value.profile !== expectedProfile ||\n typeof value.snapshot !== \"string\" ||\n !isSafeSnapshotReference(value.snapshot) ||\n typeof value.sha256 !== \"string\" ||\n !/^[0-9a-f]{64}$/.test(value.sha256) ||\n typeof value.createdAt !== \"string\" ||\n !isSafeMetadata(value.createdAt, 64) ||\n typeof value.machine !== \"string\" ||\n !isSafeMetadata(value.machine, 256) ||\n (value.syncSessions !== undefined && typeof value.syncSessions !== \"boolean\")\n ) {\n throw new Error(message);\n }\n if (value.selection !== undefined) portableSnapshotSelection(value.selection);\n return value;\n}\n\nfunction requireHistory(value: { version: number; snapshots: LatestPointer[] } | undefined, expectedProfile: string) {\n if (!value || value.version !== VERSION || !Array.isArray(value.snapshots)) {\n throw new Error(\"Remote history is malformed.\");\n }\n const pointers = value.snapshots.map((pointer) =>\n requirePointer(pointer, \"Remote history entry is malformed.\", expectedProfile),\n );\n const references = new Set<string>();\n for (const pointer of pointers) {\n if (references.has(pointer.snapshot)) {\n throw new Error(\"Remote history contains duplicate snapshot references.\");\n }\n references.add(pointer.snapshot);\n }\n return pointers;\n}\n\nfunction assertSnapshotIdentity(snapshot: Snapshot, expectedProfile: string) {\n validatePortableSnapshot(snapshot);\n if (\n snapshot.profile !== expectedProfile ||\n !isSafeSnapshotReference(snapshot.id) ||\n !isSafeMetadata(snapshot.createdAt, 64) ||\n !isSafeMetadata(snapshot.machine, 256) ||\n !Array.isArray(snapshot.files)\n ) {\n throw new Error(\"Invalid snapshot identity for S3 publication.\");\n }\n}\n\nfunction validateSnapshotBundle(snapshot: Snapshot) {\n const paths = new Set<string>();\n for (const file of snapshot.files) {\n if (\n !file ||\n typeof file.path !== \"string\" ||\n typeof file.contentBase64 !== \"string\" ||\n typeof file.sha256 !== \"string\" ||\n !/^[0-9a-f]{64}$/.test(file.sha256) ||\n paths.has(file.path)\n ) {\n throw new Error(\"Remote snapshot bundle is malformed.\");\n }\n const content = Buffer.from(file.contentBase64, \"base64\");\n if (content.toString(\"base64\") !== file.contentBase64 || sha256(content) !== file.sha256) {\n throw new Error(\"Remote snapshot file checksum mismatch.\");\n }\n paths.add(file.path);\n }\n}\n\nfunction assertSafeDestination(config: ResolvedS3Backend) {\n for (const [label, value, allowEmpty] of [\n [\"bucket\", config.destination.bucket, false],\n [\"prefix\", config.destination.prefix, true],\n [\"namespace\", config.destination.namespace, false],\n ] as const) {\n if (label === \"prefix\" && value === \"./\") continue;\n if (\n (!allowEmpty && value.length === 0) ||\n value.includes(\"\\\\\") ||\n hasControlCharacter(value) ||\n value.split(\"/\").some((segment) => segment === \".\" || segment === \"..\")\n ) {\n throw new Error(`Invalid S3 storage location ${label}.`);\n }\n }\n}\n\nfunction requireSnapshotReference(reference: string) {\n if (!isSafeSnapshotReference(reference)) {\n throw new Error(\"Invalid S3 snapshot reference.\");\n }\n}\n\nfunction isSafeSnapshotReference(reference: string) {\n return (\n reference.length > 0 &&\n reference.length <= 512 &&\n reference !== \".\" &&\n reference !== \"..\" &&\n !reference.includes(\"/\") &&\n !reference.includes(\"\\\\\") &&\n !hasControlCharacter(reference)\n );\n}\n\nfunction isSafeMetadata(value: string, maxLength: number) {\n return value.length > 0 && value.length <= maxLength && !hasControlCharacter(value);\n}\n\nfunction hasControlCharacter(value: string) {\n return [...value].some((character) => {\n const code = character.codePointAt(0) ?? 0;\n return code < 0x20 || code === 0x7f;\n });\n}\n\nfunction throwIfAborted(signal?: AbortSignal) {\n if (!signal?.aborted) return;\n throw signal.reason instanceof Error ? signal.reason : new DOMException(\"The operation was aborted\", \"AbortError\");\n}\n\nfunction sha256(value: Buffer) {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nfunction errorMessage(error: unknown) {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport { encodeKey };\n", "import { createHash, createHmac } from \"node:crypto\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\nimport { encodeKey, posixJoin } from \"../../paths.js\";\nimport type { RemoteObject, ResolvedS3Backend } from \"../backend-types.js\";\n\nconst MAX_JSON_RESPONSE_BYTES = 1024 * 1024;\nconst MAX_SNAPSHOT_RESPONSE_BYTES = 256 * 1024 * 1024;\nconst MAX_ERROR_RESPONSE_BYTES = 64 * 1024;\nconst DEFAULT_REQUEST_TIMEOUT_MS = 30_000;\n\nfunction iso8601Basic(date: Date) {\n return date.toISOString().replace(/[:-]|\\.\\d{3}/g, \"\");\n}\n\nfunction isCloudflareR2Endpoint(endpoint: string | undefined) {\n const value = endpoint?.trim();\n if (!value) return false;\n try {\n const hostname = new URL(value).hostname.toLowerCase();\n return hostname === \"r2.cloudflarestorage.com\" || hostname.endsWith(\".r2.cloudflarestorage.com\");\n } catch {\n return false;\n }\n}\n\nexport class S3ObjectAlreadyExistsError extends Error {\n constructor(readonly key: string) {\n super(`S3 object already exists: ${key}`);\n this.name = \"S3ObjectAlreadyExistsError\";\n }\n}\n\nexport class S3HttpError extends Error {\n constructor(\n message: string,\n readonly status: number,\n ) {\n super(message);\n this.name = \"S3HttpError\";\n }\n}\n\nexport class S3Client {\n private config: ResolvedS3Backend;\n private endpoint: URL;\n private signal?: AbortSignal;\n private requestTimeoutMs: number;\n private omitSessionTokenAfterRejection = false;\n\n constructor(config: ResolvedS3Backend, signal?: AbortSignal, requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {\n this.config = config;\n this.endpoint = new URL(config.profile.endpoint);\n this.signal = signal;\n this.requestTimeoutMs = requestTimeoutMs;\n }\n\n /** Read-only access probe. A bare 404 cannot distinguish a missing key from a missing bucket. */\n async diagnoseRead(key: string): Promise<\"readable\" | \"missing-key\" | \"missing-bucket\" | \"unknown-404\"> {\n this.signal?.throwIfAborted();\n const response = await this.request(\"GET\", key);\n if (response.ok) {\n await response.body?.cancel();\n this.signal?.throwIfAborted();\n return \"readable\";\n }\n const detail = await this.readErrorText(response);\n this.signal?.throwIfAborted();\n if (response.status === 404) {\n const code = /<Code>\\s*([A-Za-z]+)\\s*<\\/Code>/u.exec(detail)?.[1];\n if (code === \"NoSuchBucket\") return \"missing-bucket\";\n if (code === \"NoSuchKey\") return \"missing-key\";\n return \"unknown-404\";\n }\n throw new S3HttpError(`S3 remote read failed (HTTP ${response.status}).`, response.status);\n }\n\n async getJson<T>(key: string): Promise<RemoteObject<T>> {\n const maxAttempts = 3;\n let lastError: unknown;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const object = await this.request(\"GET\", key);\n if (object.status === 404) return { missing: true };\n if (!object.ok) {\n throw new S3HttpError(`S3 GET failed (${object.status}): ${await this.readErrorText(object)}`, object.status);\n }\n const body = await readBoundedText(object, MAX_JSON_RESPONSE_BYTES, \"S3 JSON response\");\n // R2 can intermittently return an empty 200 body (read-after-write\n // inconsistency on a long-lived keep-alive connection), which makes\n // response.json() throw \"JSON Parse error: Unexpected EOF\" under Bun.\n // Retry so the transient blip is absorbed instead of surfacing as a\n // \"pi-sync auto sync skipped\" warning on every session start.\n if (body.length > 0) {\n return {\n value: JSON.parse(body) as T,\n etag: normalizeEtag(object.headers.get(\"etag\")),\n missing: false,\n };\n }\n lastError = new Error(`S3 GET returned an empty body for ${key}`);\n if (attempt < maxAttempts) {\n await sleep(250 * attempt, undefined, { signal: this.signal });\n }\n }\n throw lastError;\n }\n\n async getBuffer(key: string): Promise<RemoteObject<Buffer>> {\n const maxAttempts = 3;\n let lastError: unknown;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const object = await this.request(\"GET\", key);\n if (object.status === 404) return { missing: true };\n if (!object.ok) {\n throw new S3HttpError(`S3 GET failed (${object.status}): ${await this.readErrorText(object)}`, object.status);\n }\n const buffer = await readBoundedBuffer(object, MAX_SNAPSHOT_RESPONSE_BYTES, \"S3 snapshot response\");\n // R2 can intermittently return an empty 200 body on a long-lived\n // keep-alive connection (same root cause as the getJson retry\n // above). getBuffer is only used for snapshot .json.gz payloads,\n // which are always non-empty, so an empty body is a transient\n // blip, not a legitimate response. Retry so the checksum guard\n // downstream doesn't surface it as \"Remote snapshot checksum\n // mismatch\".\n if (buffer.length > 0) {\n return { value: buffer, etag: normalizeEtag(object.headers.get(\"etag\")), missing: false };\n }\n lastError = new Error(`S3 GET returned an empty body for ${key}`);\n if (attempt < maxAttempts) {\n await sleep(250 * attempt, undefined, { signal: this.signal });\n }\n }\n throw lastError;\n }\n\n async putJson(key: string, value: unknown) {\n const body = Buffer.from(JSON.stringify(value, null, \"\\t\"), \"utf8\");\n await this.putBuffer(key, body, \"application/json\");\n }\n\n async putBuffer(key: string, body: Buffer, contentType: string, options: { ifAbsent?: boolean } = {}) {\n const headers: Record<string, string> = { \"content-type\": contentType };\n if (options.ifAbsent) headers[\"if-none-match\"] = \"*\";\n const response = await this.request(\"PUT\", key, body, headers);\n if (options.ifAbsent && response.status === 412) {\n throw new S3ObjectAlreadyExistsError(key);\n }\n if (!response.ok) {\n throw new S3HttpError(\n `S3 PUT failed (${response.status}): ${await this.readErrorText(response)}`,\n response.status,\n );\n }\n }\n\n private async request(method: \"GET\" | \"PUT\", key: string, body?: Buffer, extraHeaders: Record<string, string> = {}) {\n const url = new URL(this.endpoint.toString());\n url.pathname = posixJoin(url.pathname, this.config.destination.bucket, encodeKey(key));\n const send = async (sessionToken: string | undefined) => {\n const transportSignal = this.transportSignal();\n const headers = await signedHeaders({\n method,\n url,\n body,\n extraHeaders,\n accessKeyId: this.config.profile.accessKeyId,\n secretAccessKey: this.config.profile.secretAccessKey,\n sessionToken,\n region: this.config.profile.region,\n });\n try {\n return await fetch(url, {\n method,\n headers,\n body: body ? new Uint8Array(body) : undefined,\n signal: transportSignal,\n });\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") throw error;\n if (error instanceof Error && error.name === \"TimeoutError\") {\n throw new Error(\"S3 request timed out.\", { cause: error });\n }\n throw new Error(`S3 request failed: ${this.redact(errorMessage(error))}`, { cause: error });\n }\n };\n const sessionToken = this.omitSessionTokenAfterRejection ? undefined : this.config.profile.sessionToken;\n const response = await send(sessionToken);\n try {\n if (!(await this.shouldRetryWithoutSessionToken(response, sessionToken))) return response;\n } catch (error) {\n await response.body?.cancel().catch(() => undefined);\n throw error;\n }\n await response.body?.cancel();\n const retry = await send(undefined);\n if (retry.ok || retry.status === 404) this.omitSessionTokenAfterRejection = true;\n return retry;\n }\n\n private transportSignal() {\n const timeout = AbortSignal.timeout(this.requestTimeoutMs);\n return this.signal ? AbortSignal.any([this.signal, timeout]) : timeout;\n }\n\n private async shouldRetryWithoutSessionToken(response: Response, sessionToken: string | undefined) {\n if (\n !sessionToken ||\n !isCloudflareR2Endpoint(this.config.profile.endpoint) ||\n response.ok ||\n response.status !== 400\n ) {\n return false;\n }\n return isSecurityTokenInvalidArgument(\n await readBoundedText(response.clone(), MAX_ERROR_RESPONSE_BYTES, \"S3 error response\"),\n );\n }\n\n private async readErrorText(response: Response) {\n try {\n return this.redact(await readBoundedText(response, MAX_ERROR_RESPONSE_BYTES, \"S3 error response\"));\n } catch (error) {\n return this.redact(errorMessage(error));\n }\n }\n\n private redact(value: string) {\n let redacted = value;\n let endpointUsername: string | undefined;\n let endpointPassword: string | undefined;\n let endpointQueryValues: string[] = [];\n try {\n const endpoint = new URL(this.config.profile.endpoint);\n endpointUsername = endpoint.username;\n endpointPassword = endpoint.password;\n endpointQueryValues = [...endpoint.searchParams.values()];\n } catch {\n // The constructor already validates normal runtime endpoints.\n }\n for (const secret of [\n this.config.profile.accessKeyId,\n this.config.profile.secretAccessKey,\n this.config.profile.sessionToken,\n endpointUsername,\n endpointPassword,\n ...endpointQueryValues,\n ]) {\n if (secret) redacted = redacted.replaceAll(secret, \"[REDACTED]\");\n }\n return redacted;\n }\n}\n\nasync function readBoundedText(response: Response, limit: number, label: string) {\n return (await readBoundedBuffer(response, limit, label)).toString(\"utf8\");\n}\n\nasync function readBoundedBuffer(response: Response, limit: number, label: string) {\n const contentLength = Number(response.headers.get(\"content-length\"));\n if (Number.isFinite(contentLength) && contentLength > limit) {\n // Do not await cancellation of a cloned stream before its sibling can be released.\n void response.body?.cancel().catch(() => undefined);\n throw new Error(`${label} exceeds the ${limit}-byte limit.`);\n }\n if (!response.body) return Buffer.alloc(0);\n const reader = response.body.getReader();\n const chunks: Buffer[] = [];\n let total = 0;\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > limit) {\n void reader.cancel().catch(() => undefined);\n throw new Error(`${label} exceeds the ${limit}-byte limit.`);\n }\n chunks.push(Buffer.from(value));\n }\n } finally {\n reader.releaseLock();\n }\n return Buffer.concat(chunks, total);\n}\n\nfunction errorMessage(error: unknown) {\n return error instanceof Error ? error.message : String(error);\n}\n\nasync function signedHeaders(input: {\n method: string;\n url: URL;\n body?: Buffer;\n extraHeaders: Record<string, string>;\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n region: string;\n}) {\n const now = new Date();\n const amzDate = iso8601Basic(now);\n const dateStamp = amzDate.slice(0, 8);\n const payloadHash = sha256(input.body ?? Buffer.alloc(0));\n const headers: Record<string, string> = {\n ...lowercaseKeys(input.extraHeaders),\n host: input.url.host,\n \"x-amz-content-sha256\": payloadHash,\n \"x-amz-date\": amzDate,\n };\n if (input.sessionToken) headers[\"x-amz-security-token\"] = input.sessionToken;\n const signedHeaderNames = Object.keys(headers).sort();\n const canonicalHeaders = signedHeaderNames.map((name) => `${name}:${headers[name]?.trim()}\\n`).join(\"\");\n const canonicalRequest = [\n input.method,\n input.url.pathname,\n input.url.searchParams.toString(),\n canonicalHeaders,\n signedHeaderNames.join(\";\"),\n payloadHash,\n ].join(\"\\n\");\n const scope = `${dateStamp}/${input.region}/s3/aws4_request`;\n const stringToSign = [\"AWS4-HMAC-SHA256\", amzDate, scope, sha256(Buffer.from(canonicalRequest))].join(\"\\n\");\n const signingKey = hmac(\n hmac(hmac(hmac(Buffer.from(`AWS4${input.secretAccessKey}`), dateStamp), input.region), \"s3\"),\n \"aws4_request\",\n );\n const signature = createHmac(\"sha256\", signingKey).update(stringToSign).digest(\"hex\");\n return {\n ...headers,\n authorization: `AWS4-HMAC-SHA256 Credential=${input.accessKeyId}/${scope}, SignedHeaders=${signedHeaderNames.join(\";\")}, Signature=${signature}`,\n };\n}\n\nfunction hmac(key: Buffer, value: string) {\n return createHmac(\"sha256\", key).update(value).digest();\n}\n\nfunction sha256(value: Buffer) {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nfunction lowercaseKeys(value: Record<string, string>) {\n return Object.fromEntries(Object.entries(value).map(([key, item]) => [key.toLowerCase(), item]));\n}\n\nfunction normalizeEtag(value: string | null) {\n return value ?? undefined;\n}\n\nfunction isSecurityTokenInvalidArgument(text: string) {\n return text.includes(\"<Code>InvalidArgument</Code>\") && text.includes(\"<Message>X-Amz-Security-Token</Message>\");\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["createHash", "sessionToken", "sha256", "errorMessage", "createHash"]
|
|
7
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/dist/chunks/{remote-selection-ui-SMF5CLFO.ts.map → remote-selection-ui-Q2HUIBTL.ts.map}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|