@sparkelf/dsh-plugin-backup 0.1.0-rc.10

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/lib/client.js ADDED
@@ -0,0 +1,594 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@sparkelf/dsh-plugin-backup",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react_jsx_runtime = require("react/jsx-runtime");
8
+ let react = require("react");
9
+ let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
10
+ //#region \0dsh-css:/home/runner/work/deepseek-harness-plus/deepseek-harness-plus/packages/plus/backup/src/client/BackupSection.module.css.mjs
11
+ const css = ".cY-W_a_section{flex-direction:column;gap:16px;display:flex}.cY-W_a_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:600}.cY-W_a_desc{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px}.cY-W_a_actions{flex-wrap:wrap;align-items:center;gap:12px;display:flex}.cY-W_a_button{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:8px;padding:8px 14px;font-size:13px}.cY-W_a_button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.cY-W_a_button:disabled{opacity:.6;cursor:default}.cY-W_a_fileInput{display:none}.cY-W_a_progress{flex-direction:column;gap:8px;width:min(100%,480px);display:flex}.cY-W_a_progressLabel,.cY-W_a_progressDetail{margin:0;font-size:13px}.cY-W_a_progressLabel{color:var(--dsw-alias-label-primary);font-weight:500}.cY-W_a_progressDetail{color:var(--dsw-alias-label-secondary)}.cY-W_a_progressTrack{width:100%;height:8px;accent-color:var(--dsw-alias-state-business-primary);border:0}.cY-W_a_progressTrack::-webkit-progress-bar{background:var(--dsw-alias-border-l2);border-radius:4px}.cY-W_a_progressTrack::-webkit-progress-value{background:var(--dsw-alias-state-business-primary);border-radius:4px}.cY-W_a_progressTrack::-moz-progress-bar{background:var(--dsw-alias-state-business-primary);border-radius:4px}.cY-W_a_status{color:var(--dsw-alias-state-success-primary);margin:0;font-size:13px}.cY-W_a_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:13px}.cY-W_a_warning{color:var(--dsw-alias-state-warn-primary);margin:0;font-size:12px}";
12
+ const tagId = "@sparkelf/dsh-plugin-backup/BackupSection.module.css";
13
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
14
+ const tag = document.createElement("style");
15
+ tag.dataset.plugin = "@sparkelf/dsh-plugin-backup";
16
+ tag.dataset.pluginCss = tagId;
17
+ tag.textContent = css;
18
+ document.head.appendChild(tag);
19
+ }
20
+ var BackupSection_module_css_default = {
21
+ "actions": "cY-W_a_actions",
22
+ "button": "cY-W_a_button",
23
+ "desc": "cY-W_a_desc",
24
+ "error": "cY-W_a_error",
25
+ "fileInput": "cY-W_a_fileInput",
26
+ "progress": "cY-W_a_progress",
27
+ "progressDetail": "cY-W_a_progressDetail",
28
+ "progressLabel": "cY-W_a_progressLabel",
29
+ "progressTrack": "cY-W_a_progressTrack",
30
+ "section": "cY-W_a_section",
31
+ "status": "cY-W_a_status",
32
+ "title": "cY-W_a_title",
33
+ "warning": "cY-W_a_warning"
34
+ };
35
+ //#endregion
36
+ //#region lib/types/client/BackupSection.js
37
+ /**
38
+ * Backup settings section: exact Host routes stream archive bytes and progress;
39
+ * the slot store survives settings-driven component remounts.
40
+ */
41
+ /** Prove exhaustive progress phase handling. */
42
+ function assertNever(value) {
43
+ throw new Error("unknown backup progress phase: " + String(value));
44
+ }
45
+ /** Return the locale key naming the current phase. */
46
+ function progressTextKey(progress) {
47
+ switch (progress.phase) {
48
+ case "scan": return "exportScan";
49
+ case "compress": return "exportCompress";
50
+ case "upload": return "importUpload";
51
+ case "validate": return "importValidate";
52
+ case "restore": return "importRestore";
53
+ case "reload": return "importReload";
54
+ default: return assertNever(progress);
55
+ }
56
+ }
57
+ /** Derive a displayed integer percentage only for phases with stable byte totals. */
58
+ function progressPercent(progress) {
59
+ switch (progress.phase) {
60
+ case "scan":
61
+ case "validate":
62
+ case "reload": return;
63
+ case "compress":
64
+ case "upload":
65
+ case "restore": return progress.totalBytes === 0 ? 100 : Math.floor(progress.completedBytes * 100 / progress.totalBytes);
66
+ default: return assertNever(progress);
67
+ }
68
+ }
69
+ /** Return whether the current phase can still stop without interrupting committed restore mutation. */
70
+ function canCancel(progress) {
71
+ switch (progress.phase) {
72
+ case "scan":
73
+ case "compress":
74
+ case "upload":
75
+ case "validate": return true;
76
+ case "restore":
77
+ case "reload": return false;
78
+ default: return assertNever(progress);
79
+ }
80
+ }
81
+ /** Recognize the browser cancellation error returned by fetch and XHR. */
82
+ function isCancellation(caught) {
83
+ return caught instanceof Error && caught.name === "AbortError";
84
+ }
85
+ /** Map one transport or archive rejection to product copy. */
86
+ function localize(caught, kind) {
87
+ const message = caught instanceof Error ? caught.message : String(caught);
88
+ if (message.includes("missing backup-manifest.json")) return "notBackup";
89
+ if (message.includes("unsafe path")) return "unsafe";
90
+ return kind === "export" ? "exportFailed" : "importFailed";
91
+ }
92
+ /**
93
+ * The Backup section body.
94
+ * @param props - runtime owner props, locale, store seat, and streamed operation callbacks.
95
+ * @returns the section tree.
96
+ */
97
+ function BackupSection(props) {
98
+ const { t, actions } = props;
99
+ const operation = props.useStore((state) => state.operation);
100
+ const status = props.useStore((state) => state.status);
101
+ const error = props.useStore((state) => state.error);
102
+ const cancelling = props.useStore((state) => state.cancelling);
103
+ const reloadRequired = status === "imported";
104
+ const fileRef = (0, react.useRef)(null);
105
+ const runExport = async () => {
106
+ actions.begin({
107
+ kind: "export",
108
+ progress: { phase: "scan" }
109
+ });
110
+ try {
111
+ const { downloadUrl } = await props.exportArchive((progress) => {
112
+ actions.progress({
113
+ kind: "export",
114
+ progress
115
+ });
116
+ });
117
+ const anchor = document.createElement("a");
118
+ anchor.href = downloadUrl;
119
+ anchor.download = "deepseek-harness-backup.zip";
120
+ anchor.click();
121
+ actions.complete("exported");
122
+ } catch (caught) {
123
+ if (isCancellation(caught)) {
124
+ actions.cancelled();
125
+ return;
126
+ }
127
+ actions.fail(localize(caught, "export"));
128
+ }
129
+ };
130
+ const onPick = (event) => {
131
+ const file = event.target.files?.[0];
132
+ event.target.value = "";
133
+ if (file === void 0) return;
134
+ actions.begin({
135
+ kind: "import",
136
+ progress: {
137
+ phase: "upload",
138
+ completedBytes: 0,
139
+ totalBytes: file.size
140
+ }
141
+ });
142
+ (async () => {
143
+ try {
144
+ await props.importArchive(file, (progress) => {
145
+ actions.progress({
146
+ kind: "import",
147
+ progress
148
+ });
149
+ });
150
+ actions.complete("imported");
151
+ } catch (caught) {
152
+ if (isCancellation(caught)) {
153
+ actions.cancelled();
154
+ return;
155
+ }
156
+ actions.fail(localize(caught, "import"));
157
+ }
158
+ })();
159
+ };
160
+ const percent = operation === null ? void 0 : progressPercent(operation.progress);
161
+ const progressText = operation === null ? "" : t(progressTextKey(operation.progress)) + (percent === void 0 ? "" : " " + String(percent) + "%");
162
+ return (0, react_jsx_runtime.jsxs)("section", {
163
+ className: BackupSection_module_css_default.section,
164
+ children: [
165
+ (0, react_jsx_runtime.jsx)("h2", {
166
+ className: BackupSection_module_css_default.title,
167
+ children: t("title")
168
+ }),
169
+ (0, react_jsx_runtime.jsx)("p", {
170
+ className: BackupSection_module_css_default.desc,
171
+ children: t("desc")
172
+ }),
173
+ (0, react_jsx_runtime.jsxs)("div", {
174
+ className: BackupSection_module_css_default.actions,
175
+ children: [
176
+ (0, react_jsx_runtime.jsx)("button", {
177
+ type: "button",
178
+ className: BackupSection_module_css_default.button,
179
+ disabled: operation !== null || reloadRequired,
180
+ onClick: () => {
181
+ runExport();
182
+ },
183
+ children: operation?.kind === "export" ? t("busyExport") : t("exportButton")
184
+ }),
185
+ (0, react_jsx_runtime.jsx)("button", {
186
+ type: "button",
187
+ className: BackupSection_module_css_default.button,
188
+ disabled: operation !== null || reloadRequired,
189
+ onClick: () => {
190
+ fileRef.current?.click();
191
+ },
192
+ children: operation?.kind === "import" ? t("busyImport") : t("importButton")
193
+ }),
194
+ operation !== null && canCancel(operation.progress) ? (0, react_jsx_runtime.jsx)("button", {
195
+ type: "button",
196
+ className: BackupSection_module_css_default.button,
197
+ disabled: cancelling,
198
+ onClick: () => {
199
+ actions.requestCancel();
200
+ props.cancelOperation();
201
+ },
202
+ children: t(cancelling ? "cancellingButton" : "cancelButton")
203
+ }) : null,
204
+ (0, react_jsx_runtime.jsx)("input", {
205
+ ref: fileRef,
206
+ type: "file",
207
+ accept: ".zip,application/zip",
208
+ className: BackupSection_module_css_default.fileInput,
209
+ onChange: onPick
210
+ })
211
+ ]
212
+ }),
213
+ operation !== null ? (0, react_jsx_runtime.jsxs)("div", {
214
+ className: BackupSection_module_css_default.progress,
215
+ role: "status",
216
+ "aria-live": "polite",
217
+ children: [
218
+ (0, react_jsx_runtime.jsx)("p", {
219
+ className: BackupSection_module_css_default.progressLabel,
220
+ children: progressText
221
+ }),
222
+ percent === void 0 ? (0, react_jsx_runtime.jsx)("progress", {
223
+ className: BackupSection_module_css_default.progressTrack,
224
+ "aria-label": t(operation.kind === "export" ? "exportProgressLabel" : "importProgressLabel"),
225
+ "aria-valuetext": progressText
226
+ }) : (0, react_jsx_runtime.jsx)("progress", {
227
+ className: BackupSection_module_css_default.progressTrack,
228
+ max: 100,
229
+ value: percent,
230
+ "aria-label": t(operation.kind === "export" ? "exportProgressLabel" : "importProgressLabel"),
231
+ "aria-valuetext": progressText
232
+ }),
233
+ (0, react_jsx_runtime.jsx)("p", {
234
+ className: BackupSection_module_css_default.progressDetail,
235
+ children: t("progressDetail")
236
+ })
237
+ ]
238
+ }) : null,
239
+ status !== null ? (0, react_jsx_runtime.jsx)("p", {
240
+ className: BackupSection_module_css_default.status,
241
+ role: "status",
242
+ children: t(status)
243
+ }) : null,
244
+ reloadRequired ? (0, react_jsx_runtime.jsx)("button", {
245
+ type: "button",
246
+ className: BackupSection_module_css_default.button,
247
+ onClick: () => {
248
+ props.reloadPage();
249
+ },
250
+ children: t("reloadButton")
251
+ }) : null,
252
+ error !== null ? (0, react_jsx_runtime.jsx)("p", {
253
+ className: BackupSection_module_css_default.error,
254
+ role: "alert",
255
+ children: t(error)
256
+ }) : null,
257
+ (0, react_jsx_runtime.jsx)("p", {
258
+ className: BackupSection_module_css_default.warning,
259
+ children: t("warning")
260
+ })
261
+ ]
262
+ });
263
+ }
264
+ //#endregion
265
+ //#region lib/types/client/store.js
266
+ /** Remount-surviving state for the Settings Backup slot registration. */
267
+ /**
268
+ * Declare the Backup section operation and result state.
269
+ * @returns the root-scoped store handle.
270
+ */
271
+ function createBackupSectionStore() {
272
+ return (0, _deepseek_ai_dsh_client_store.defineStore)({
273
+ init: () => ({
274
+ operation: null,
275
+ status: null,
276
+ error: null,
277
+ cancelling: false
278
+ }),
279
+ actions: {
280
+ begin: (draft, operation) => {
281
+ draft.operation = operation;
282
+ draft.status = null;
283
+ draft.error = null;
284
+ draft.cancelling = false;
285
+ },
286
+ progress: (draft, operation) => {
287
+ draft.operation = operation;
288
+ },
289
+ requestCancel: (draft) => {
290
+ draft.cancelling = true;
291
+ },
292
+ cancelled: (draft) => {
293
+ draft.operation = null;
294
+ draft.cancelling = false;
295
+ },
296
+ complete: (draft, status) => {
297
+ draft.operation = null;
298
+ draft.status = status;
299
+ draft.error = null;
300
+ draft.cancelling = false;
301
+ },
302
+ fail: (draft, error) => {
303
+ draft.operation = null;
304
+ draft.status = null;
305
+ draft.error = error;
306
+ draft.cancelling = false;
307
+ }
308
+ }
309
+ });
310
+ }
311
+ //#endregion
312
+ //#region lib/types/client/locales.js
313
+ /** Settings Backup section dictionaries (zh product copy, en twin). */
314
+ const zh = {
315
+ nav: "备份",
316
+ title: "备份与恢复",
317
+ desc: "把用户设置和数据导出为一个 zip 压缩包,或从备份压缩包恢复。",
318
+ exportButton: "导出备份压缩包",
319
+ importButton: "选择压缩包并导入",
320
+ busyExport: "正在生成备份…",
321
+ busyImport: "正在导入备份…",
322
+ cancelButton: "取消",
323
+ cancellingButton: "正在取消…",
324
+ exportScan: "正在计算备份数据量…",
325
+ exportCompress: "正在生成备份",
326
+ exportProgressLabel: "备份生成进度",
327
+ importUpload: "正在上传备份",
328
+ importValidate: "正在校验备份完整性…",
329
+ importRestore: "正在恢复备份",
330
+ importReload: "正在加载会话与工作区…",
331
+ importProgressLabel: "备份恢复进度",
332
+ progressDetail: "数据较多时可能需要几分钟,请保持此页面打开。",
333
+ exported: "备份已生成,浏览器已开始下载。",
334
+ imported: "备份已导入。重新加载页面后,会话和工作区将使用恢复后的数据。",
335
+ reloadButton: "重新加载页面",
336
+ notBackup: "所选压缩包不是 DeepSeek Harness 备份文件。",
337
+ unsafe: "备份压缩包包含不安全的条目路径。",
338
+ exportFailed: "备份导出失败,请重试;若持续失败,请检查可用磁盘空间。",
339
+ importFailed: "备份导入失败,请检查所选压缩包后重试。",
340
+ warning: "备份压缩包包含 API 密钥等敏感信息,请妥善保管。"
341
+ };
342
+ /** English twin of the `zh` dictionary. */
343
+ const en = {
344
+ nav: "Backup",
345
+ title: "Backup and restore",
346
+ desc: "Export user settings and data as one zip archive, or restore them from a backup archive.",
347
+ exportButton: "Export backup archive",
348
+ importButton: "Choose archive and import",
349
+ busyExport: "Generating backup…",
350
+ busyImport: "Importing backup…",
351
+ cancelButton: "Cancel",
352
+ cancellingButton: "Cancelling…",
353
+ exportScan: "Calculating backup size…",
354
+ exportCompress: "Generating backup",
355
+ exportProgressLabel: "Backup generation progress",
356
+ importUpload: "Uploading backup",
357
+ importValidate: "Validating backup integrity…",
358
+ importRestore: "Restoring backup",
359
+ importReload: "Loading sessions and workspaces…",
360
+ importProgressLabel: "Backup restore progress",
361
+ progressDetail: "Large data sets may take several minutes; keep this page open.",
362
+ exported: "Backup generated; the browser download has started.",
363
+ imported: "Backup imported. Reload to apply the restored session and workspace data.",
364
+ reloadButton: "Reload page",
365
+ notBackup: "The selected archive is not a DeepSeek Harness backup file.",
366
+ unsafe: "The backup archive contains unsafe entry paths.",
367
+ exportFailed: "Backup export failed. Retry; if it keeps failing, check available disk space.",
368
+ importFailed: "Backup import failed. Check the selected archive and retry.",
369
+ warning: "The backup archive contains sensitive data such as API keys; store it carefully."
370
+ };
371
+ //#endregion
372
+ //#region lib/types/client/index.js
373
+ /**
374
+ * Settings Backup section plugin, browser half. Archive bytes use exact Host
375
+ * routes; preparation and restore state arrive as validated NDJSON progress
376
+ * lines, outside the unary RPC carrier.
377
+ */
378
+ const NS = "settingsBackup";
379
+ const inject = ["slots", "locale"];
380
+ /** Resolve a Host logical path beneath the runtime-injected document base. */
381
+ function browserHostUrl(path) {
382
+ return new URL(path.replace(/^[/]/u, ""), document.baseURI).toString();
383
+ }
384
+ /** Parse one unknown JSON object at the backup progress HTTP boundary. */
385
+ function parseRecord(value) {
386
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("invalid backup progress message");
387
+ return value;
388
+ }
389
+ /** Parse one finite non-negative byte or count field from the progress wire. */
390
+ function parseCount(value) {
391
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error("invalid backup progress count");
392
+ return value;
393
+ }
394
+ /** Parse one Host-authored phase update from the exact-route stream. */
395
+ function parseHostProgress(value) {
396
+ const record = parseRecord(value);
397
+ if (record.phase === "scan" || record.phase === "validate" || record.phase === "reload") return { phase: record.phase };
398
+ if (record.phase === "compress" || record.phase === "restore") {
399
+ const completedBytes = parseCount(record.completedBytes);
400
+ const totalBytes = parseCount(record.totalBytes);
401
+ if (completedBytes > totalBytes) throw new Error("invalid backup progress byte range");
402
+ return {
403
+ phase: record.phase,
404
+ completedBytes,
405
+ totalBytes
406
+ };
407
+ }
408
+ throw new Error("unknown backup progress phase");
409
+ }
410
+ /** Parse one complete NDJSON line; this is the progress protocol's sole browser parser. */
411
+ function parseProgressLine(line) {
412
+ const record = parseRecord(JSON.parse(line));
413
+ if (record.type === "progress") return {
414
+ type: "progress",
415
+ progress: parseHostProgress(record.progress)
416
+ };
417
+ if (record.type === "export-ready") {
418
+ if (typeof record.downloadUrl !== "string" || record.downloadUrl === "") throw new Error("invalid backup export download URL");
419
+ return {
420
+ type: "export-ready",
421
+ downloadUrl: record.downloadUrl,
422
+ entries: parseCount(record.entries)
423
+ };
424
+ }
425
+ if (record.type === "import-complete") return {
426
+ type: "import-complete",
427
+ entries: parseCount(record.entries)
428
+ };
429
+ if (record.type === "error" && typeof record.message === "string") return {
430
+ type: "error",
431
+ message: record.message
432
+ };
433
+ throw new Error("unknown backup progress message");
434
+ }
435
+ /** Read and validate one exact-route NDJSON response until its terminal line. */
436
+ async function readProgressResponse(response, report) {
437
+ if (!response.ok) throw new Error("backup operation failed: HTTP " + String(response.status));
438
+ if (response.body === null) throw new Error("backup progress response has no body");
439
+ const reader = response.body.getReader();
440
+ const decoder = new TextDecoder();
441
+ let pending = "";
442
+ let terminal;
443
+ let terminalError;
444
+ try {
445
+ while (true) {
446
+ const { done, value } = await reader.read();
447
+ pending += decoder.decode(value, { stream: !done });
448
+ const lines = pending.split(String.fromCharCode(10));
449
+ if (done) pending = "";
450
+ else pending = lines.pop();
451
+ for (const line of lines) {
452
+ if (line === "") continue;
453
+ const message = parseProgressLine(line);
454
+ if (message.type === "progress") report(message.progress);
455
+ else if (message.type === "error") terminalError = message.message;
456
+ else terminal = message;
457
+ }
458
+ if (done) break;
459
+ }
460
+ } finally {
461
+ reader.releaseLock();
462
+ }
463
+ if (terminalError !== void 0) throw new Error(terminalError);
464
+ if (terminal === void 0) throw new Error("backup progress response ended without a result");
465
+ return terminal;
466
+ }
467
+ /** Parse the upload route's single-use token response. */
468
+ function parseUploadToken(text) {
469
+ const record = parseRecord(JSON.parse(text));
470
+ if (typeof record.token !== "string" || record.token === "") throw new Error("invalid backup upload token");
471
+ return record.token;
472
+ }
473
+ /** Upload a File with browser-native byte progress and caller cancellation. */
474
+ function uploadArchive(file, report, signal) {
475
+ report({
476
+ phase: "upload",
477
+ completedBytes: 0,
478
+ totalBytes: file.size
479
+ });
480
+ return new Promise((resolve, reject) => {
481
+ const request = new XMLHttpRequest();
482
+ const onSignalAbort = () => {
483
+ request.abort();
484
+ };
485
+ const settle = (complete) => {
486
+ signal.removeEventListener("abort", onSignalAbort);
487
+ complete();
488
+ };
489
+ request.open("POST", browserHostUrl("/api/backup.upload"));
490
+ request.upload.onprogress = (event) => {
491
+ report({
492
+ phase: "upload",
493
+ completedBytes: event.loaded,
494
+ totalBytes: file.size
495
+ });
496
+ };
497
+ request.onload = () => {
498
+ if (request.status < 200 || request.status >= 300) {
499
+ settle(() => {
500
+ reject(/* @__PURE__ */ new Error("backup upload failed: HTTP " + String(request.status)));
501
+ });
502
+ return;
503
+ }
504
+ try {
505
+ const token = parseUploadToken(request.responseText);
506
+ settle(() => {
507
+ resolve(token);
508
+ });
509
+ } catch (error) {
510
+ settle(() => {
511
+ reject(error instanceof Error ? error : new Error(String(error)));
512
+ });
513
+ }
514
+ };
515
+ request.onerror = () => {
516
+ settle(() => {
517
+ reject(/* @__PURE__ */ new Error("backup upload failed"));
518
+ });
519
+ };
520
+ request.onabort = () => {
521
+ settle(() => {
522
+ reject(new DOMException("Backup upload aborted", "AbortError"));
523
+ });
524
+ };
525
+ signal.addEventListener("abort", onSignalAbort, { once: true });
526
+ request.send(file);
527
+ });
528
+ }
529
+ /** Register the Backup settings section and its exact-route transport callbacks. */
530
+ function apply(ctx) {
531
+ ctx.effect(() => ctx.locale.register(NS, {
532
+ zh,
533
+ en
534
+ }), "plus-backup: dictionaries");
535
+ const store = createBackupSectionStore();
536
+ let activeController = null;
537
+ ctx.effect(() => () => {
538
+ activeController?.abort();
539
+ }, "plus-backup: cancel active operation");
540
+ const runAbortable = async (run) => {
541
+ const controller = new AbortController();
542
+ activeController = controller;
543
+ try {
544
+ return await run(controller.signal);
545
+ } finally {
546
+ if (activeController === controller) activeController = null;
547
+ }
548
+ };
549
+ const injected = () => ({
550
+ exportArchive: (report) => runAbortable(async (signal) => {
551
+ const terminal = await readProgressResponse(await fetch(browserHostUrl("/api/backup.export.prepare"), {
552
+ method: "POST",
553
+ signal
554
+ }), report);
555
+ if (terminal.type !== "export-ready") throw new Error("backup export returned an import result");
556
+ return {
557
+ downloadUrl: browserHostUrl(terminal.downloadUrl),
558
+ entries: terminal.entries
559
+ };
560
+ }),
561
+ importArchive: (file, report) => runAbortable(async (signal) => {
562
+ const token = await uploadArchive(file, report, signal);
563
+ const terminal = await readProgressResponse(await fetch(browserHostUrl("/api/backup.import?token=" + encodeURIComponent(token)), {
564
+ method: "POST",
565
+ signal
566
+ }), report);
567
+ if (terminal.type !== "import-complete") throw new Error("backup import returned an export result");
568
+ return { entries: terminal.entries };
569
+ }),
570
+ cancelOperation: () => {
571
+ activeController?.abort();
572
+ },
573
+ reloadPage: () => {
574
+ window.location.reload();
575
+ }
576
+ });
577
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
578
+ name: "settings.section",
579
+ id: "backup",
580
+ order: 30,
581
+ label: () => ctx.locale.bind(NS)("nav"),
582
+ locale: NS,
583
+ store,
584
+ inject: injected
585
+ }, BackupSection));
586
+ }
587
+ //#endregion
588
+ exports.apply = apply;
589
+ exports.inject = inject;
590
+ return module.exports;
591
+ }
592
+ });
593
+
594
+ //# sourceMappingURL=client.js.map