@awiki/dsh-plugin 0.3.7-dsh-test.20260831.2 → 0.3.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/README.md +66 -26
- package/README.zh.md +57 -23
- package/cordis.patch.yml +7 -5
- package/lib/builtin-tenants.json +22 -0
- package/lib/builtin-tenants.sha256 +1 -0
- package/lib/client.js +7123 -1635
- package/lib/client.js.map +1 -1
- package/lib/index.js +2534 -474
- package/lib/model-proxy-contract.js +11 -3
- package/lib/provider.js +6 -3
- package/lib/{sdk-adapter-FGaOi8It.mjs → sdk-adapter-CYcJH7sM.mjs} +210 -21
- package/lib/typert.host.js +1305 -106
- package/lib/typert.remote-client.d.ts +25 -1
- package/lib/typert.remote-client.d.ts.map +1 -1
- package/lib/typert.remote-client.js +1305 -106
- package/lib/types/builtin-tenant-config.d.ts +19 -0
- package/lib/types/builtin-tenant-config.d.ts.map +1 -0
- package/lib/types/builtin-tenant-config.js +57 -0
- package/lib/types/builtin-tenant-config.js.map +1 -0
- package/lib/types/client/AwikiDeviceJoinReminder.d.ts +15 -0
- package/lib/types/client/AwikiDeviceJoinReminder.d.ts.map +1 -0
- package/lib/types/client/AwikiDeviceJoinReminder.js +74 -0
- package/lib/types/client/AwikiDeviceJoinReminder.js.map +1 -0
- package/lib/types/client/AwikiDevices.d.ts +8 -3
- package/lib/types/client/AwikiDevices.d.ts.map +1 -1
- package/lib/types/client/AwikiDevices.js +64 -8
- package/lib/types/client/AwikiDevices.js.map +1 -1
- package/lib/types/client/AwikiGroupDetails.d.ts.map +1 -1
- package/lib/types/client/AwikiGroupDetails.js +16 -13
- package/lib/types/client/AwikiGroupDetails.js.map +1 -1
- package/lib/types/client/AwikiIdentityAccess.d.ts +10 -4
- package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
- package/lib/types/client/AwikiIdentityAccess.js +139 -36
- package/lib/types/client/AwikiIdentityAccess.js.map +1 -1
- package/lib/types/client/AwikiIntegrationSettings.d.ts +21 -0
- package/lib/types/client/AwikiIntegrationSettings.d.ts.map +1 -0
- package/lib/types/client/AwikiIntegrationSettings.js +105 -0
- package/lib/types/client/AwikiIntegrationSettings.js.map +1 -0
- package/lib/types/client/AwikiMail.d.ts.map +1 -1
- package/lib/types/client/AwikiMail.js +12 -8
- package/lib/types/client/AwikiMail.js.map +1 -1
- package/lib/types/client/AwikiOverlay.d.ts +0 -1
- package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
- package/lib/types/client/AwikiOverlay.js +26 -21
- package/lib/types/client/AwikiOverlay.js.map +1 -1
- package/lib/types/client/AwikiProfileCard.d.ts.map +1 -1
- package/lib/types/client/AwikiProfileCard.js +17 -18
- package/lib/types/client/AwikiProfileCard.js.map +1 -1
- package/lib/types/client/AwikiRecoveryForm.d.ts +5 -1
- package/lib/types/client/AwikiRecoveryForm.d.ts.map +1 -1
- package/lib/types/client/AwikiRecoveryForm.js +73 -30
- package/lib/types/client/AwikiRecoveryForm.js.map +1 -1
- package/lib/types/client/AwikiSettingsSection.d.ts +28 -13
- package/lib/types/client/AwikiSettingsSection.d.ts.map +1 -1
- package/lib/types/client/AwikiSettingsSection.js +93 -61
- package/lib/types/client/AwikiSettingsSection.js.map +1 -1
- package/lib/types/client/AwikiUpdates.d.ts +10 -0
- package/lib/types/client/AwikiUpdates.d.ts.map +1 -0
- package/lib/types/client/AwikiUpdates.js +57 -0
- package/lib/types/client/AwikiUpdates.js.map +1 -0
- package/lib/types/client/awiki-client-bridge.d.ts +1 -1
- package/lib/types/client/awiki-client-bridge.d.ts.map +1 -1
- package/lib/types/client/awiki-client-bridge.js +4 -1
- package/lib/types/client/awiki-client-bridge.js.map +1 -1
- package/lib/types/client/controller.d.ts +65 -11
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +480 -127
- package/lib/types/client/controller.js.map +1 -1
- package/lib/types/client/drafts.d.ts +31 -0
- package/lib/types/client/drafts.d.ts.map +1 -0
- package/lib/types/client/drafts.js +94 -0
- package/lib/types/client/drafts.js.map +1 -0
- package/lib/types/client/image-cache.d.ts +2 -2
- package/lib/types/client/image-cache.d.ts.map +1 -1
- package/lib/types/client/image-cache.js +15 -2
- package/lib/types/client/image-cache.js.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +55 -15
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/integration-operation.d.ts +9 -0
- package/lib/types/client/integration-operation.d.ts.map +1 -0
- package/lib/types/client/integration-operation.js +54 -0
- package/lib/types/client/integration-operation.js.map +1 -0
- package/lib/types/client/mail-list-cache.d.ts +2 -0
- package/lib/types/client/mail-list-cache.d.ts.map +1 -1
- package/lib/types/client/mail-list-cache.js +18 -0
- package/lib/types/client/mail-list-cache.js.map +1 -1
- package/lib/types/client/otp-cooldown.d.ts +6 -0
- package/lib/types/client/otp-cooldown.d.ts.map +1 -0
- package/lib/types/client/otp-cooldown.js +18 -0
- package/lib/types/client/otp-cooldown.js.map +1 -0
- package/lib/types/client/settings-controller.d.ts +34 -0
- package/lib/types/client/settings-controller.d.ts.map +1 -1
- package/lib/types/client/settings-controller.js +182 -2
- package/lib/types/client/settings-controller.js.map +1 -1
- package/lib/types/client/settings-locales.d.ts +1 -1
- package/lib/types/client/settings-locales.d.ts.map +1 -1
- package/lib/types/client/settings-locales.js +184 -6
- package/lib/types/client/settings-locales.js.map +1 -1
- package/lib/types/client/slots.d.ts +11 -1
- package/lib/types/client/slots.d.ts.map +1 -1
- package/lib/types/client/slots.js +0 -1
- package/lib/types/client/slots.js.map +1 -1
- package/lib/types/desktop-distribution.d.ts +25 -0
- package/lib/types/desktop-distribution.d.ts.map +1 -0
- package/lib/types/desktop-distribution.js +57 -0
- package/lib/types/desktop-distribution.js.map +1 -0
- package/lib/types/domain.d.ts +1 -1
- package/lib/types/domain.d.ts.map +1 -1
- package/lib/types/domain.js +2 -1
- package/lib/types/domain.js.map +1 -1
- package/lib/types/index.d.ts +112 -18
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +897 -205
- package/lib/types/index.js.map +1 -1
- package/lib/types/integration-client.d.ts +17 -0
- package/lib/types/integration-client.d.ts.map +1 -0
- package/lib/types/integration-client.js +233 -0
- package/lib/types/integration-client.js.map +1 -0
- package/lib/types/legacy-sent-mail-cache.d.ts +4 -0
- package/lib/types/legacy-sent-mail-cache.d.ts.map +1 -0
- package/lib/types/legacy-sent-mail-cache.js +29 -0
- package/lib/types/legacy-sent-mail-cache.js.map +1 -0
- package/lib/types/mail-list-client.d.ts +12 -0
- package/lib/types/mail-list-client.d.ts.map +1 -0
- package/lib/types/mail-list-client.js +254 -0
- package/lib/types/mail-list-client.js.map +1 -0
- package/lib/types/mail-recovery-observability.d.ts +8 -0
- package/lib/types/mail-recovery-observability.d.ts.map +1 -0
- package/lib/types/mail-recovery-observability.js +119 -0
- package/lib/types/mail-recovery-observability.js.map +1 -0
- package/lib/types/model-proxy-contract.d.ts.map +1 -1
- package/lib/types/model-proxy-contract.js +7 -0
- package/lib/types/model-proxy-contract.js.map +1 -1
- package/lib/types/package-version.generated.d.ts +4 -0
- package/lib/types/package-version.generated.d.ts.map +1 -0
- package/lib/types/package-version.generated.js +4 -0
- package/lib/types/package-version.generated.js.map +1 -0
- package/lib/types/profile-state.d.ts +6 -0
- package/lib/types/profile-state.d.ts.map +1 -0
- package/lib/types/profile-state.js +41 -0
- package/lib/types/profile-state.js.map +1 -0
- package/lib/types/provider-api.d.ts +36 -9
- package/lib/types/provider-api.d.ts.map +1 -1
- package/lib/types/provider.d.ts.map +1 -1
- package/lib/types/provider.js +5 -2
- package/lib/types/provider.js.map +1 -1
- package/lib/types/realtime-supervisor.d.ts +6 -0
- package/lib/types/realtime-supervisor.d.ts.map +1 -1
- package/lib/types/realtime-supervisor.js +14 -1
- package/lib/types/realtime-supervisor.js.map +1 -1
- package/lib/types/sdk-adapter.d.ts +32 -6
- package/lib/types/sdk-adapter.d.ts.map +1 -1
- package/lib/types/sdk-adapter.js +127 -24
- package/lib/types/sdk-adapter.js.map +1 -1
- package/lib/types/settings-rpc-contract.d.ts +58 -0
- package/lib/types/settings-rpc-contract.d.ts.map +1 -1
- package/lib/types/settings-rpc-contract.js +103 -0
- package/lib/types/settings-rpc-contract.js.map +1 -1
- package/lib/types/settings-rpc.d.ts +14 -1
- package/lib/types/settings-rpc.d.ts.map +1 -1
- package/lib/types/settings-rpc.js +130 -1
- package/lib/types/settings-rpc.js.map +1 -1
- package/lib/types/tenant-registry.d.ts +74 -0
- package/lib/types/tenant-registry.d.ts.map +1 -0
- package/lib/types/tenant-registry.js +440 -0
- package/lib/types/tenant-registry.js.map +1 -0
- package/lib/types/types.d.ts +173 -1
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js.map +1 -1
- package/lib/types/update-policy.d.ts +51 -0
- package/lib/types/update-policy.d.ts.map +1 -0
- package/lib/types/update-policy.js +379 -0
- package/lib/types/update-policy.js.map +1 -0
- package/lib/types/version.d.ts +4 -0
- package/lib/types/version.d.ts.map +1 -0
- package/lib/types/version.js +44 -0
- package/lib/types/version.js.map +1 -0
- package/package.json +34 -21
- package/lib/types/sent-mail-store.d.ts +0 -22
- package/lib/types/sent-mail-store.d.ts.map +0 -1
- package/lib/types/sent-mail-store.js +0 -246
- package/lib/types/sent-mail-store.js.map +0 -1
package/lib/index.js
CHANGED
|
@@ -1,16 +1,107 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as fencedMailRecoveryObservability, i as failedMailRecoveryObservability, o as successfulMailRecoveryObservability, r as downloadedAttachment, t as AwikiSdkError } from "./sdk-adapter-CYcJH7sM.mjs";
|
|
2
2
|
import "@deepseek-ai/cordis";
|
|
3
3
|
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import { isAbsolute, join } from "node:path";
|
|
5
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
6
|
import z from "@deepseek-ai/schemastery";
|
|
7
7
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
8
8
|
import { SettingsConflictError, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
9
|
-
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
10
9
|
import { chmod, lstat, mkdir, readFile, readdir, rename, rm, unlink, utimes, writeFile } from "node:fs/promises";
|
|
10
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
11
11
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
12
12
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
13
13
|
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { satisfies } from "semver";
|
|
17
|
+
//#region lib/types/version.js
|
|
18
|
+
/** Shared strict version comparison for update presentation and Host gates. */
|
|
19
|
+
function assertVersion(value) {
|
|
20
|
+
if (value.length > 256 || !/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u.test(value)) throw new Error("invalid semantic version");
|
|
21
|
+
}
|
|
22
|
+
function compareVersions(left, right) {
|
|
23
|
+
assertVersion(left);
|
|
24
|
+
assertVersion(right);
|
|
25
|
+
const parse = (value) => {
|
|
26
|
+
const withoutBuild = value.split("+", 1)[0];
|
|
27
|
+
const separator = withoutBuild.indexOf("-");
|
|
28
|
+
const core = (separator < 0 ? withoutBuild : withoutBuild.slice(0, separator)).split(".").map(BigInt);
|
|
29
|
+
return separator < 0 ? { core } : {
|
|
30
|
+
core,
|
|
31
|
+
prerelease: withoutBuild.slice(separator + 1).split(".")
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
const a = parse(left);
|
|
35
|
+
const b = parse(right);
|
|
36
|
+
for (let index = 0; index < 3; index += 1) {
|
|
37
|
+
const difference = a.core[index] - b.core[index];
|
|
38
|
+
if (difference !== 0n) return difference < 0n ? -1 : 1;
|
|
39
|
+
}
|
|
40
|
+
if (a.prerelease === void 0 || b.prerelease === void 0) return a.prerelease === b.prerelease ? 0 : a.prerelease === void 0 ? 1 : -1;
|
|
41
|
+
for (let index = 0; index < Math.min(a.prerelease.length, b.prerelease.length); index += 1) {
|
|
42
|
+
const leftPart = a.prerelease[index];
|
|
43
|
+
const rightPart = b.prerelease[index];
|
|
44
|
+
if (leftPart === rightPart) continue;
|
|
45
|
+
const leftNumeric = /^\d+$/u.test(leftPart);
|
|
46
|
+
if (leftNumeric !== /^\d+$/u.test(rightPart)) return leftNumeric ? -1 : 1;
|
|
47
|
+
if (leftNumeric) return BigInt(leftPart) < BigInt(rightPart) ? -1 : 1;
|
|
48
|
+
return leftPart < rightPart ? -1 : 1;
|
|
49
|
+
}
|
|
50
|
+
if (a.prerelease.length !== b.prerelease.length) return a.prerelease.length < b.prerelease.length ? -1 : 1;
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region lib/types/desktop-distribution.js
|
|
55
|
+
function decodeDesktopDistribution(value) {
|
|
56
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
57
|
+
const view = value;
|
|
58
|
+
if (view.schemaVersion !== 1 || view.distributionId !== "awiki-dsh-desktop" || typeof view.currentVersion !== "string" || view.channel !== "stable" && view.channel !== "prerelease" || typeof view.downloadPageUrl !== "string" || ![
|
|
59
|
+
"unchecked",
|
|
60
|
+
"checking",
|
|
61
|
+
"ready",
|
|
62
|
+
"failed"
|
|
63
|
+
].includes(String(view.state)) || typeof view.updateAvailable !== "boolean" || typeof view.usedCache !== "boolean" || view.latestVersion !== void 0 && typeof view.latestVersion !== "string" || view.checkedAt !== void 0 && typeof view.checkedAt !== "string" || view.noRelease !== void 0 && typeof view.noRelease !== "boolean") return void 0;
|
|
64
|
+
try {
|
|
65
|
+
assertVersion(view.currentVersion);
|
|
66
|
+
if (view.latestVersion !== void 0) assertVersion(view.latestVersion);
|
|
67
|
+
if (view.checkedAt !== void 0 && !Number.isFinite(Date.parse(view.checkedAt))) return void 0;
|
|
68
|
+
const url = new URL(view.downloadPageUrl);
|
|
69
|
+
if (url.protocol !== "https:" || url.username !== "" || url.password !== "") return void 0;
|
|
70
|
+
} catch {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let bundledVersions;
|
|
74
|
+
if (view.bundledVersions !== void 0) {
|
|
75
|
+
if (typeof view.bundledVersions !== "object" || view.bundledVersions === null) return void 0;
|
|
76
|
+
const bundled = view.bundledVersions;
|
|
77
|
+
if (typeof bundled.plugin !== "string" || bundled.modelProxy !== void 0 && typeof bundled.modelProxy !== "string") return void 0;
|
|
78
|
+
try {
|
|
79
|
+
assertVersion(bundled.plugin);
|
|
80
|
+
if (bundled.modelProxy !== void 0) assertVersion(bundled.modelProxy);
|
|
81
|
+
} catch {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
bundledVersions = {
|
|
85
|
+
plugin: bundled.plugin,
|
|
86
|
+
...typeof bundled.modelProxy === "string" ? { modelProxy: bundled.modelProxy } : {}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
schemaVersion: 1,
|
|
91
|
+
distributionId: "awiki-dsh-desktop",
|
|
92
|
+
currentVersion: view.currentVersion,
|
|
93
|
+
channel: view.channel,
|
|
94
|
+
downloadPageUrl: view.downloadPageUrl,
|
|
95
|
+
state: view.state,
|
|
96
|
+
updateAvailable: view.updateAvailable,
|
|
97
|
+
usedCache: view.usedCache,
|
|
98
|
+
...typeof view.latestVersion === "string" ? { latestVersion: view.latestVersion } : {},
|
|
99
|
+
...typeof view.checkedAt === "string" ? { checkedAt: view.checkedAt } : {},
|
|
100
|
+
...typeof view.noRelease === "boolean" ? { noRelease: view.noRelease } : {},
|
|
101
|
+
...bundledVersions === void 0 ? {} : { bundledVersions }
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
14
105
|
//#region lib/types/types.js
|
|
15
106
|
/** Client-safe AWiki service and Remote data types. */
|
|
16
107
|
/** Exact browser acknowledgement required before locally signing out. */
|
|
@@ -190,6 +281,511 @@ function responseMetadata(response) {
|
|
|
190
281
|
};
|
|
191
282
|
}
|
|
192
283
|
//#endregion
|
|
284
|
+
//#region lib/types/integration-client.js
|
|
285
|
+
/** Host-only fixed-scope client for Guest Integration management. */
|
|
286
|
+
const MAX_REQUEST_BYTES = 65536;
|
|
287
|
+
const MAX_RESPONSE_BYTES$1 = 1048576;
|
|
288
|
+
const REQUEST_TIMEOUT_MS = 1e4;
|
|
289
|
+
const INTEGRATION_STATUSES = ["active", "closed"];
|
|
290
|
+
const TARGET_AVAILABILITIES = [
|
|
291
|
+
"eligible",
|
|
292
|
+
"group_not_found",
|
|
293
|
+
"owner_not_active",
|
|
294
|
+
"owner_mismatch",
|
|
295
|
+
"not_open_join",
|
|
296
|
+
"unsupported_security_profile",
|
|
297
|
+
"member_send_disabled",
|
|
298
|
+
"attachments_disabled",
|
|
299
|
+
"group_full",
|
|
300
|
+
"validation_unavailable"
|
|
301
|
+
];
|
|
302
|
+
const OPERATIONS = {
|
|
303
|
+
read: {
|
|
304
|
+
method: "GET",
|
|
305
|
+
path: "/guest/api/v1/management/integration"
|
|
306
|
+
},
|
|
307
|
+
create: {
|
|
308
|
+
method: "POST",
|
|
309
|
+
path: "/guest/api/v1/management/integration"
|
|
310
|
+
},
|
|
311
|
+
update: {
|
|
312
|
+
method: "PATCH",
|
|
313
|
+
path: "/guest/api/v1/management/integration"
|
|
314
|
+
},
|
|
315
|
+
rotate: {
|
|
316
|
+
method: "POST",
|
|
317
|
+
path: "/guest/api/v1/management/integration/rotate-id"
|
|
318
|
+
},
|
|
319
|
+
close: {
|
|
320
|
+
method: "POST",
|
|
321
|
+
path: "/guest/api/v1/management/integration/close"
|
|
322
|
+
},
|
|
323
|
+
reopen: {
|
|
324
|
+
method: "POST",
|
|
325
|
+
path: "/guest/api/v1/management/integration/reopen"
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
/** Browser-safe management input translated to the Gateway's snake-case contract. */
|
|
329
|
+
function fields(request) {
|
|
330
|
+
return {
|
|
331
|
+
product_name: request.productName,
|
|
332
|
+
description: request.description,
|
|
333
|
+
contact_enabled: request.contactEnabled,
|
|
334
|
+
contact_description: request.contactDescription,
|
|
335
|
+
group_targets: request.groupTargets.map((target) => ({
|
|
336
|
+
...target.id === void 0 ? {} : { id: target.id },
|
|
337
|
+
group_did: target.groupDid,
|
|
338
|
+
description: target.description
|
|
339
|
+
}))
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function view$1(raw) {
|
|
343
|
+
if (typeof raw !== "object" || raw === null) throw new TypeError("invalid Integration response");
|
|
344
|
+
const value = raw;
|
|
345
|
+
const owner = value.owner;
|
|
346
|
+
const groups = value.group_targets;
|
|
347
|
+
if (typeof owner !== "object" || owner === null || !Array.isArray(groups)) throw new TypeError("invalid Integration response");
|
|
348
|
+
return {
|
|
349
|
+
id: requiredString$1(value.id),
|
|
350
|
+
publicId: nullableString(value.public_id),
|
|
351
|
+
integrationUrl: nullableString(value.integration_url),
|
|
352
|
+
owner: {
|
|
353
|
+
tenantId: requiredString$1(owner.tenant_id),
|
|
354
|
+
handle: requiredString$1(owner.handle),
|
|
355
|
+
currentDid: requiredString$1(owner.current_did),
|
|
356
|
+
displayName: requiredString$1(owner.display_name)
|
|
357
|
+
},
|
|
358
|
+
productName: requiredString$1(value.product_name),
|
|
359
|
+
description: requiredString$1(value.description),
|
|
360
|
+
contactEnabled: requiredBoolean(value.contact_enabled),
|
|
361
|
+
contactDescription: requiredString$1(value.contact_description),
|
|
362
|
+
groupTargets: groups.map((group) => {
|
|
363
|
+
if (typeof group !== "object" || group === null) throw new TypeError("invalid Integration group");
|
|
364
|
+
const target = group;
|
|
365
|
+
return {
|
|
366
|
+
id: requiredString$1(target.id),
|
|
367
|
+
groupDid: requiredString$1(target.group_did),
|
|
368
|
+
displayName: requiredString$1(target.display_name),
|
|
369
|
+
avatarUrl: nullableString(target.avatar_url),
|
|
370
|
+
description: requiredString$1(target.description),
|
|
371
|
+
availability: requiredEnum(target.availability, TARGET_AVAILABILITIES)
|
|
372
|
+
};
|
|
373
|
+
}),
|
|
374
|
+
status: requiredEnum(value.status, INTEGRATION_STATUSES),
|
|
375
|
+
revision: requiredInteger(value.revision)
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function requiredString$1(value) {
|
|
379
|
+
if (typeof value !== "string") throw new TypeError("invalid Integration response");
|
|
380
|
+
return value;
|
|
381
|
+
}
|
|
382
|
+
function nullableString(value) {
|
|
383
|
+
if (value === null) return null;
|
|
384
|
+
return requiredString$1(value);
|
|
385
|
+
}
|
|
386
|
+
function requiredBoolean(value) {
|
|
387
|
+
if (typeof value !== "boolean") throw new TypeError("invalid Integration response");
|
|
388
|
+
return value;
|
|
389
|
+
}
|
|
390
|
+
function requiredInteger(value) {
|
|
391
|
+
if (!Number.isSafeInteger(value)) throw new TypeError("invalid Integration response");
|
|
392
|
+
return value;
|
|
393
|
+
}
|
|
394
|
+
function requiredEnum(value, allowed) {
|
|
395
|
+
if (typeof value !== "string" || !allowed.includes(value)) throw new TypeError("invalid Integration response");
|
|
396
|
+
return value;
|
|
397
|
+
}
|
|
398
|
+
function failure$1(status, raw) {
|
|
399
|
+
const body = typeof raw === "object" && raw !== null ? raw : {};
|
|
400
|
+
const nested = typeof body.error === "object" && body.error !== null ? body.error : body;
|
|
401
|
+
const candidate = typeof nested.code === "string" ? nested.code : "";
|
|
402
|
+
const code = status === 404 ? "not-found" : status === 409 ? "conflict" : status === 400 ? "invalid-request" : status === 401 ? "unauthorized" : status === 403 ? "forbidden" : status === 429 ? "rate-limited" : status >= 500 ? "unavailable" : [
|
|
403
|
+
"not-found",
|
|
404
|
+
"conflict",
|
|
405
|
+
"invalid-request",
|
|
406
|
+
"forbidden",
|
|
407
|
+
"unauthorized",
|
|
408
|
+
"rate-limited",
|
|
409
|
+
"unavailable"
|
|
410
|
+
].includes(candidate) ? candidate : "remote";
|
|
411
|
+
return {
|
|
412
|
+
code,
|
|
413
|
+
message: integrationFailureMessage(code)
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function integrationFailureMessage(code) {
|
|
417
|
+
switch (code) {
|
|
418
|
+
case "not-found": return "尚未创建 Integration。";
|
|
419
|
+
case "conflict": return "Integration 已在其他位置更新,请重新加载后再试。";
|
|
420
|
+
case "invalid-request": return "Integration 信息不完整或格式不正确。";
|
|
421
|
+
case "forbidden": return "当前 AWiki 身份无权管理这个 Integration。";
|
|
422
|
+
case "unauthorized": return "请先在 AWiki 正式版中登录。";
|
|
423
|
+
case "rate-limited": return "操作过于频繁,请稍后重试。";
|
|
424
|
+
case "unavailable": return "临时消息服务暂时不可用。";
|
|
425
|
+
case "network": return "无法连接临时消息服务。";
|
|
426
|
+
default: return "临时消息服务返回了无法识别的结果。";
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
async function readBounded$1(response) {
|
|
430
|
+
const reader = response.body?.getReader();
|
|
431
|
+
if (reader === void 0) return null;
|
|
432
|
+
const chunks = [];
|
|
433
|
+
let size = 0;
|
|
434
|
+
try {
|
|
435
|
+
while (true) {
|
|
436
|
+
const result = await reader.read();
|
|
437
|
+
if (result.done) break;
|
|
438
|
+
size += result.value.byteLength;
|
|
439
|
+
if (size > MAX_RESPONSE_BYTES$1) throw new TypeError("Integration response too large");
|
|
440
|
+
chunks.push(result.value);
|
|
441
|
+
}
|
|
442
|
+
} finally {
|
|
443
|
+
reader.releaseLock();
|
|
444
|
+
}
|
|
445
|
+
const bytes = new Uint8Array(size);
|
|
446
|
+
let offset = 0;
|
|
447
|
+
for (const chunk of chunks) {
|
|
448
|
+
bytes.set(chunk, offset);
|
|
449
|
+
offset += chunk.byteLength;
|
|
450
|
+
}
|
|
451
|
+
if (bytes.length === 0) return null;
|
|
452
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
453
|
+
}
|
|
454
|
+
/** Fixed-origin client; callers cannot control path, method, headers, or transport. */
|
|
455
|
+
var AwikiIntegrationClient = class {
|
|
456
|
+
origin;
|
|
457
|
+
auth;
|
|
458
|
+
constructor(origin, auth) {
|
|
459
|
+
this.origin = origin;
|
|
460
|
+
this.auth = auth;
|
|
461
|
+
}
|
|
462
|
+
read() {
|
|
463
|
+
return this.execute("read");
|
|
464
|
+
}
|
|
465
|
+
create(request) {
|
|
466
|
+
return this.execute("create", fields(request), request.idempotencyKey);
|
|
467
|
+
}
|
|
468
|
+
update(request) {
|
|
469
|
+
return this.execute("update", {
|
|
470
|
+
expected_revision: request.expectedRevision,
|
|
471
|
+
...fields(request)
|
|
472
|
+
}, request.idempotencyKey);
|
|
473
|
+
}
|
|
474
|
+
rotate(request) {
|
|
475
|
+
return this.execute("rotate", { expected_revision: request.expectedRevision }, request.idempotencyKey);
|
|
476
|
+
}
|
|
477
|
+
close(request) {
|
|
478
|
+
return this.execute("close", { expected_revision: request.expectedRevision }, request.idempotencyKey);
|
|
479
|
+
}
|
|
480
|
+
reopen(request) {
|
|
481
|
+
return this.execute("reopen", {
|
|
482
|
+
expected_revision: request.expectedRevision,
|
|
483
|
+
...fields(request)
|
|
484
|
+
}, request.idempotencyKey);
|
|
485
|
+
}
|
|
486
|
+
async execute(operation, body, idempotencyKey) {
|
|
487
|
+
const spec = OPERATIONS[operation];
|
|
488
|
+
const encoded = body === void 0 ? void 0 : JSON.stringify(body);
|
|
489
|
+
if (encoded !== void 0 && Buffer.byteLength(encoded, "utf8") > MAX_REQUEST_BYTES) return {
|
|
490
|
+
ok: false,
|
|
491
|
+
error: {
|
|
492
|
+
code: "invalid-request",
|
|
493
|
+
message: integrationFailureMessage("invalid-request")
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
const headers = new Headers({ accept: "application/json" });
|
|
497
|
+
if (encoded !== void 0) headers.set("content-type", "application/json");
|
|
498
|
+
if (idempotencyKey !== void 0) headers.set("idempotency-key", idempotencyKey);
|
|
499
|
+
const abort = new AbortController();
|
|
500
|
+
const timeout = setTimeout(() => {
|
|
501
|
+
abort.abort();
|
|
502
|
+
}, REQUEST_TIMEOUT_MS);
|
|
503
|
+
try {
|
|
504
|
+
const request = new Request(new URL(spec.path, this.origin), {
|
|
505
|
+
method: spec.method,
|
|
506
|
+
headers,
|
|
507
|
+
...encoded === void 0 ? {} : { body: encoded },
|
|
508
|
+
redirect: "error",
|
|
509
|
+
signal: abort.signal
|
|
510
|
+
});
|
|
511
|
+
const response = await this.auth.dispatch(request, (signed) => fetch(signed));
|
|
512
|
+
const raw = await readBounded$1(response);
|
|
513
|
+
if (!response.ok) return {
|
|
514
|
+
ok: false,
|
|
515
|
+
error: failure$1(response.status, raw)
|
|
516
|
+
};
|
|
517
|
+
return {
|
|
518
|
+
ok: true,
|
|
519
|
+
value: view$1(raw)
|
|
520
|
+
};
|
|
521
|
+
} catch (error) {
|
|
522
|
+
if (error instanceof AwikiExternalHttpAuthError && (error.code === "not-registered" || error.code === "signed-out")) return {
|
|
523
|
+
ok: false,
|
|
524
|
+
error: {
|
|
525
|
+
code: "unauthorized",
|
|
526
|
+
message: integrationFailureMessage("unauthorized")
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
return {
|
|
530
|
+
ok: false,
|
|
531
|
+
error: {
|
|
532
|
+
code: "network",
|
|
533
|
+
message: integrationFailureMessage("network")
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
} finally {
|
|
537
|
+
clearTimeout(timeout);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
//#endregion
|
|
542
|
+
//#region lib/types/legacy-sent-mail-cache.js
|
|
543
|
+
/** Exact cleanup for the removed Host-local sent-mail authority. */
|
|
544
|
+
function isMissing$4(error) {
|
|
545
|
+
return typeof error === "object" && error !== null && error.code === "ENOENT";
|
|
546
|
+
}
|
|
547
|
+
/** Clear only the legacy cache directory during the existing destructive local-data flow. */
|
|
548
|
+
async function clearLegacySentMailCache(stateRoot) {
|
|
549
|
+
const hostDirectory = join(stateRoot, ".host");
|
|
550
|
+
const directory = join(hostDirectory, "sent-mail-v1");
|
|
551
|
+
try {
|
|
552
|
+
const host = await lstat(hostDirectory);
|
|
553
|
+
if (!host.isDirectory() || host.isSymbolicLink()) throw new TypeError("awiki: legacy sent-mail cache parent is invalid");
|
|
554
|
+
const target = await lstat(directory);
|
|
555
|
+
if (target.isDirectory() && !target.isSymbolicLink()) await rm(directory, {
|
|
556
|
+
recursive: true,
|
|
557
|
+
force: true
|
|
558
|
+
});
|
|
559
|
+
else await unlink(directory);
|
|
560
|
+
} catch (error) {
|
|
561
|
+
if (!isMissing$4(error)) throw error;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
//#endregion
|
|
565
|
+
//#region lib/types/mail-list-client.js
|
|
566
|
+
/** Host-only server-authoritative outbound Mail query. */
|
|
567
|
+
const MAIL_LIST_PATH = "/mail/rpc";
|
|
568
|
+
const MAIL_LIST_PAGE_SIZE = 100;
|
|
569
|
+
const MAIL_LIST_TIMEOUT_MS = 1e4;
|
|
570
|
+
const MAX_RESPONSE_BYTES = 1048576;
|
|
571
|
+
const MAX_UINT32 = 4294967295;
|
|
572
|
+
function invalidResponse() {
|
|
573
|
+
throw new AwikiSdkError("remote");
|
|
574
|
+
}
|
|
575
|
+
function uint32(value) {
|
|
576
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > MAX_UINT32) invalidResponse();
|
|
577
|
+
return value;
|
|
578
|
+
}
|
|
579
|
+
function requiredString(value, maxBytes) {
|
|
580
|
+
if (typeof value !== "string" || value.length === 0 || value.trim() !== value || /[\u0000-\u001f\u007f-\u009f]/u.test(value) || Buffer.byteLength(value, "utf8") > maxBytes) invalidResponse();
|
|
581
|
+
return value;
|
|
582
|
+
}
|
|
583
|
+
function addresses(value) {
|
|
584
|
+
if (value === null || value === void 0 || value === "") return [];
|
|
585
|
+
const raw = Array.isArray(value) ? value : typeof value === "string" ? value.split(/[;,]/u) : invalidResponse();
|
|
586
|
+
if (raw.length > 100) invalidResponse();
|
|
587
|
+
return raw.map((candidate) => {
|
|
588
|
+
const address = requiredString(typeof candidate === "string" ? candidate.trim() : candidate, 320);
|
|
589
|
+
if (!address.includes("@") || /\s/u.test(address)) invalidResponse();
|
|
590
|
+
return address;
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
function timestamp(value) {
|
|
594
|
+
const copied = requiredString(value, 64);
|
|
595
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|[+-]\d{2}:\d{2})?$/u.exec(copied);
|
|
596
|
+
if (match === null) invalidResponse();
|
|
597
|
+
const year = Number(match[1]);
|
|
598
|
+
const month = Number(match[2]);
|
|
599
|
+
const day = Number(match[3]);
|
|
600
|
+
const hour = Number(match[4]);
|
|
601
|
+
const minute = Number(match[5]);
|
|
602
|
+
const second = Number(match[6]);
|
|
603
|
+
const zone = match[8];
|
|
604
|
+
const monthDays = [
|
|
605
|
+
31,
|
|
606
|
+
year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28,
|
|
607
|
+
31,
|
|
608
|
+
30,
|
|
609
|
+
31,
|
|
610
|
+
30,
|
|
611
|
+
31,
|
|
612
|
+
31,
|
|
613
|
+
30,
|
|
614
|
+
31,
|
|
615
|
+
30,
|
|
616
|
+
31
|
|
617
|
+
];
|
|
618
|
+
if (year === 0 || month < 1 || month > 12 || day < 1 || day > monthDays[month - 1] || hour > 23 || minute > 59 || second > 59) invalidResponse();
|
|
619
|
+
if (zone !== void 0 && zone !== "Z") {
|
|
620
|
+
const offsetHour = Number(zone.slice(1, 3));
|
|
621
|
+
const offsetMinute = Number(zone.slice(4, 6));
|
|
622
|
+
if (offsetHour > 23 || offsetMinute > 59) invalidResponse();
|
|
623
|
+
}
|
|
624
|
+
return zone === void 0 ? `${copied}Z` : copied;
|
|
625
|
+
}
|
|
626
|
+
function boundedText$1(value, maxBytes, fallback) {
|
|
627
|
+
const source = typeof value === "string" && value.trim() !== "" ? value : fallback;
|
|
628
|
+
if (/\u0000/u.test(source)) invalidResponse();
|
|
629
|
+
if (Buffer.byteLength(source, "utf8") <= maxBytes) return {
|
|
630
|
+
value: source,
|
|
631
|
+
truncated: false
|
|
632
|
+
};
|
|
633
|
+
const characters = Array.from(source);
|
|
634
|
+
while (characters.length > 0 && Buffer.byteLength(characters.join(""), "utf8") > maxBytes) characters.pop();
|
|
635
|
+
return {
|
|
636
|
+
value: characters.join(""),
|
|
637
|
+
truncated: true
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function summary(raw) {
|
|
641
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) invalidResponse();
|
|
642
|
+
const value = raw;
|
|
643
|
+
if (value.direction !== "outbound" || typeof value.has_attachments !== "boolean") invalidResponse();
|
|
644
|
+
const subject = boundedText$1(value.subject, 1024, "(no subject)");
|
|
645
|
+
return {
|
|
646
|
+
id: requiredString(value.id, 2048),
|
|
647
|
+
folder: "sent",
|
|
648
|
+
from: addresses(value.from_addr),
|
|
649
|
+
to: addresses(value.to_addr),
|
|
650
|
+
cc: addresses(value.cc_addr),
|
|
651
|
+
subject: subject.value,
|
|
652
|
+
subjectTruncated: subject.truncated,
|
|
653
|
+
previewTruncated: false,
|
|
654
|
+
sentAt: timestamp(value.created_at),
|
|
655
|
+
unread: false,
|
|
656
|
+
hasAttachments: value.has_attachments
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
async function readBounded(response) {
|
|
660
|
+
const declared = response.headers.get("content-length");
|
|
661
|
+
if (declared !== null && /^\d+$/u.test(declared) && Number(declared) > MAX_RESPONSE_BYTES) invalidResponse();
|
|
662
|
+
const reader = response.body?.getReader();
|
|
663
|
+
if (reader === void 0) invalidResponse();
|
|
664
|
+
const chunks = [];
|
|
665
|
+
let size = 0;
|
|
666
|
+
try {
|
|
667
|
+
while (true) {
|
|
668
|
+
const result = await reader.read();
|
|
669
|
+
if (result.done) break;
|
|
670
|
+
size += result.value.byteLength;
|
|
671
|
+
if (size > MAX_RESPONSE_BYTES) invalidResponse();
|
|
672
|
+
chunks.push(result.value);
|
|
673
|
+
}
|
|
674
|
+
} finally {
|
|
675
|
+
reader.releaseLock();
|
|
676
|
+
}
|
|
677
|
+
const bytes = new Uint8Array(size);
|
|
678
|
+
let offset = 0;
|
|
679
|
+
for (const chunk of chunks) {
|
|
680
|
+
bytes.set(chunk, offset);
|
|
681
|
+
offset += chunk.byteLength;
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
685
|
+
} catch {
|
|
686
|
+
invalidResponse();
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function rpcFailure(code) {
|
|
690
|
+
if (code === -32e3) throw new AwikiSdkError("not-registered");
|
|
691
|
+
if (code === -32001) throw new AwikiSdkError("forbidden");
|
|
692
|
+
if (code === -32002) throw new AwikiSdkError("not-found");
|
|
693
|
+
if (code === -32003) throw new AwikiSdkError("conflict");
|
|
694
|
+
if (code === -32004 || code === -32600 || code === -32602) throw new AwikiSdkError("invalid-request");
|
|
695
|
+
if (code === -32005) throw new AwikiSdkError("rate-limited");
|
|
696
|
+
invalidResponse();
|
|
697
|
+
}
|
|
698
|
+
function responseFailure(status) {
|
|
699
|
+
if (status === 401) throw new AwikiSdkError("not-registered");
|
|
700
|
+
if (status === 403) throw new AwikiSdkError("forbidden");
|
|
701
|
+
if (status === 429) throw new AwikiSdkError("rate-limited");
|
|
702
|
+
if (status >= 500) throw new AwikiSdkError("network");
|
|
703
|
+
invalidResponse();
|
|
704
|
+
}
|
|
705
|
+
/** Fixed Mail Service client; callers cannot select method, direction, headers, or transport. */
|
|
706
|
+
var AwikiMailListClient = class {
|
|
707
|
+
origin;
|
|
708
|
+
auth;
|
|
709
|
+
constructor(origin, auth) {
|
|
710
|
+
this.origin = origin;
|
|
711
|
+
this.auth = auth;
|
|
712
|
+
}
|
|
713
|
+
async listOutbound(request) {
|
|
714
|
+
const offset = request.offset ?? 0;
|
|
715
|
+
const limit = request.limit ?? 20;
|
|
716
|
+
const firstPageNumber = Math.floor(offset / MAIL_LIST_PAGE_SIZE) + 1;
|
|
717
|
+
const first = await this.page(firstPageNumber);
|
|
718
|
+
const pageOffset = offset % MAIL_LIST_PAGE_SIZE;
|
|
719
|
+
let items = first.items.slice(pageOffset, pageOffset + limit);
|
|
720
|
+
let total = first.total;
|
|
721
|
+
if (items.length < limit && offset + items.length < total) {
|
|
722
|
+
const next = await this.page(firstPageNumber + 1);
|
|
723
|
+
total = next.total;
|
|
724
|
+
items = [...items, ...next.items.slice(0, limit - items.length)];
|
|
725
|
+
}
|
|
726
|
+
const nextOffset = offset + items.length;
|
|
727
|
+
const hasMore = nextOffset < total;
|
|
728
|
+
return {
|
|
729
|
+
items,
|
|
730
|
+
...hasMore ? { nextOffset } : {},
|
|
731
|
+
hasMore
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
async page(page) {
|
|
735
|
+
const abort = new AbortController();
|
|
736
|
+
const timeout = setTimeout(() => {
|
|
737
|
+
abort.abort();
|
|
738
|
+
}, MAIL_LIST_TIMEOUT_MS);
|
|
739
|
+
try {
|
|
740
|
+
const request = new Request(new URL(MAIL_LIST_PATH, this.origin), {
|
|
741
|
+
method: "POST",
|
|
742
|
+
headers: {
|
|
743
|
+
accept: "application/json",
|
|
744
|
+
"content-type": "application/json"
|
|
745
|
+
},
|
|
746
|
+
body: JSON.stringify({
|
|
747
|
+
jsonrpc: "2.0",
|
|
748
|
+
id: 1,
|
|
749
|
+
method: "mail.list",
|
|
750
|
+
params: {
|
|
751
|
+
direction: "outbound",
|
|
752
|
+
page,
|
|
753
|
+
page_size: MAIL_LIST_PAGE_SIZE
|
|
754
|
+
}
|
|
755
|
+
}),
|
|
756
|
+
redirect: "error",
|
|
757
|
+
signal: abort.signal
|
|
758
|
+
});
|
|
759
|
+
const response = await this.auth.dispatch(request, (signed) => fetch(signed));
|
|
760
|
+
if (!response.ok) responseFailure(response.status);
|
|
761
|
+
const raw = await readBounded(response);
|
|
762
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) invalidResponse();
|
|
763
|
+
const envelope = raw;
|
|
764
|
+
if (envelope.jsonrpc !== "2.0" || envelope.id !== 1) invalidResponse();
|
|
765
|
+
if (envelope.error !== null && envelope.error !== void 0) {
|
|
766
|
+
if (typeof envelope.error !== "object" || Array.isArray(envelope.error)) invalidResponse();
|
|
767
|
+
rpcFailure(envelope.error.code);
|
|
768
|
+
}
|
|
769
|
+
if (typeof envelope.result !== "object" || envelope.result === null || Array.isArray(envelope.result)) invalidResponse();
|
|
770
|
+
const result = envelope.result;
|
|
771
|
+
if (!Array.isArray(result.items) || result.items.length > MAIL_LIST_PAGE_SIZE) invalidResponse();
|
|
772
|
+
return {
|
|
773
|
+
total: uint32(result.total),
|
|
774
|
+
items: result.items.map(summary)
|
|
775
|
+
};
|
|
776
|
+
} catch (error) {
|
|
777
|
+
if (error instanceof AwikiSdkError) throw error;
|
|
778
|
+
if (error instanceof AwikiExternalHttpAuthError) {
|
|
779
|
+
if (error.code === "not-registered") throw new AwikiSdkError("not-registered");
|
|
780
|
+
if (error.code === "signed-out") throw new AwikiSdkError("signed-out");
|
|
781
|
+
}
|
|
782
|
+
throw new AwikiSdkError("network");
|
|
783
|
+
} finally {
|
|
784
|
+
clearTimeout(timeout);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
//#endregion
|
|
193
789
|
//#region lib/types/tools.js
|
|
194
790
|
/** Model-facing AWiki read and approved-send tools. */
|
|
195
791
|
/** Model tool that reads the public deployment identity. */
|
|
@@ -555,10 +1151,55 @@ function mailSendRequest(request) {
|
|
|
555
1151
|
};
|
|
556
1152
|
}
|
|
557
1153
|
//#endregion
|
|
1154
|
+
//#region lib/types/builtin-tenant-config.js
|
|
1155
|
+
/** Build-injected two-slot tenant catalog with a source-tree default for tests. */
|
|
1156
|
+
function record(value) {
|
|
1157
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1158
|
+
}
|
|
1159
|
+
function definition(value) {
|
|
1160
|
+
if (!record(value) || !record(value.display_name) || typeof value.display_name["zh-CN"] !== "string" || value.display_name["zh-CN"].trim() === "" || typeof value.display_name.en !== "string" || value.display_name.en.trim() === "" || typeof value.backend_origin !== "string" || typeof value.did_host !== "string") throw new Error("awiki: invalid built-in tenant definition");
|
|
1161
|
+
const origin = new URL(value.backend_origin);
|
|
1162
|
+
const loopback = origin.protocol === "http:" && [
|
|
1163
|
+
"localhost",
|
|
1164
|
+
"127.0.0.1",
|
|
1165
|
+
"[::1]"
|
|
1166
|
+
].includes(origin.hostname);
|
|
1167
|
+
if (origin.protocol !== "https:" && !loopback || origin.username !== "" || origin.password !== "" || origin.pathname !== "/" || origin.search !== "" || origin.hash !== "" || !loopback && origin.port !== "") throw new Error("awiki: built-in tenant origin must be an HTTPS origin");
|
|
1168
|
+
const didHost = value.did_host.trim().toLowerCase().replace(/\.$/u, "");
|
|
1169
|
+
if (origin.hostname !== didHost) throw new Error("awiki: built-in tenant DID host must match its origin");
|
|
1170
|
+
return {
|
|
1171
|
+
displayName: {
|
|
1172
|
+
"zh-CN": value.display_name["zh-CN"].trim(),
|
|
1173
|
+
en: value.display_name.en.trim()
|
|
1174
|
+
},
|
|
1175
|
+
backendOrigin: origin.origin,
|
|
1176
|
+
didHost
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
function decodeBuiltinTenantConfig(value) {
|
|
1180
|
+
if (!record(value) || value.schema_version !== 1 || value.default_slot !== "primary" && value.default_slot !== "secondary" || !record(value.tenants) || Object.keys(value.tenants).sort().join(",") !== "primary,secondary") throw new Error("awiki: invalid built-in tenant config");
|
|
1181
|
+
const primary = definition(value.tenants.primary);
|
|
1182
|
+
const secondary = definition(value.tenants.secondary);
|
|
1183
|
+
if (primary.backendOrigin === secondary.backendOrigin || primary.didHost === secondary.didHost) throw new Error("awiki: built-in tenant slots must use distinct endpoints");
|
|
1184
|
+
return {
|
|
1185
|
+
schemaVersion: 1,
|
|
1186
|
+
defaultSlot: value.default_slot,
|
|
1187
|
+
tenants: {
|
|
1188
|
+
primary,
|
|
1189
|
+
secondary
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
function rawConfig() {
|
|
1194
|
+
return JSON.parse("{\n \"schema_version\": 1,\n \"default_slot\": \"primary\",\n \"tenants\": {\n \"primary\": {\n \"display_name\": {\n \"zh-CN\": \"AWiki 中国(上海)\",\n \"en\": \"AWiki China (Shanghai)\"\n },\n \"backend_origin\": \"https://awiki.me\",\n \"did_host\": \"awiki.me\"\n },\n \"secondary\": {\n \"display_name\": {\n \"zh-CN\": \"AWiki 全球(硅谷)\",\n \"en\": \"AWiki Global (Silicon Valley)\"\n },\n \"backend_origin\": \"https://awiki.ai\",\n \"did_host\": \"awiki.ai\"\n }\n }\n}\n");
|
|
1195
|
+
}
|
|
1196
|
+
const AWIKI_BUILTIN_TENANT_CONFIG = decodeBuiltinTenantConfig(rawConfig());
|
|
1197
|
+
const AWIKI_DEFAULT_BUILTIN_TENANT = AWIKI_BUILTIN_TENANT_CONFIG.tenants[AWIKI_BUILTIN_TENANT_CONFIG.defaultSlot];
|
|
1198
|
+
//#endregion
|
|
558
1199
|
//#region lib/types/domain.js
|
|
559
1200
|
/** Client-safe AWiki Handle provider domain constants and validation. */
|
|
560
1201
|
/** Default Handle provider domain for new AWiki deployments. */
|
|
561
|
-
const DEFAULT_AWIKI_DOMAIN =
|
|
1202
|
+
const DEFAULT_AWIKI_DOMAIN = AWIKI_DEFAULT_BUILTIN_TENANT.didHost;
|
|
562
1203
|
/** Field carrying the Handle provider domain in the AWiki settings namespace. */
|
|
563
1204
|
const AWIKI_DOMAIN_FIELD = "domain";
|
|
564
1205
|
/** Settings namespace owned by the AWiki plugin. */
|
|
@@ -587,20 +1228,29 @@ const AWIKI_SETTINGS_RPC_CHANNEL = "/awiki-settings";
|
|
|
587
1228
|
const AWIKI_SETTINGS_RPC_ENDPOINTS = {
|
|
588
1229
|
describe: "describe",
|
|
589
1230
|
setDomain: "set-domain",
|
|
590
|
-
resetDomain: "reset-domain"
|
|
1231
|
+
resetDomain: "reset-domain",
|
|
1232
|
+
describeTenants: "describe-tenants",
|
|
1233
|
+
createTenant: "create-tenant",
|
|
1234
|
+
renameTenant: "rename-tenant",
|
|
1235
|
+
switchTenant: "switch-tenant",
|
|
1236
|
+
archiveTenant: "archive-tenant",
|
|
1237
|
+
describeUpdatePolicy: "describe-update-policy",
|
|
1238
|
+
refreshUpdatePolicy: "refresh-update-policy",
|
|
1239
|
+
describeDesktopUpdate: "describe-desktop-update",
|
|
1240
|
+
refreshDesktopUpdate: "refresh-desktop-update"
|
|
591
1241
|
};
|
|
592
1242
|
//#endregion
|
|
593
1243
|
//#region lib/types/settings-rpc.js
|
|
594
1244
|
/** Loopback-only Host transport for AWiki's durable plugin settings. */
|
|
595
|
-
function isRecord$
|
|
1245
|
+
function isRecord$4(value) {
|
|
596
1246
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
597
1247
|
}
|
|
598
1248
|
function expectedRevision(payload) {
|
|
599
|
-
if (!isRecord$
|
|
1249
|
+
if (!isRecord$4(payload) || !Number.isSafeInteger(payload.expectedRevision) || payload.expectedRevision < 0) return;
|
|
600
1250
|
return payload.expectedRevision;
|
|
601
1251
|
}
|
|
602
1252
|
function sanitizeLayer(value) {
|
|
603
|
-
if (!isRecord$
|
|
1253
|
+
if (!isRecord$4(value)) return void 0;
|
|
604
1254
|
if (!Object.hasOwn(value, "domain")) return {};
|
|
605
1255
|
if (typeof value["domain"] !== "string") return void 0;
|
|
606
1256
|
const domain = normalizeAwikiDomain(value[AWIKI_DOMAIN_FIELD]);
|
|
@@ -609,7 +1259,7 @@ function sanitizeLayer(value) {
|
|
|
609
1259
|
}
|
|
610
1260
|
function view(provider) {
|
|
611
1261
|
const descriptor = provider.describe({ redactSecrets: true }).find((candidate) => candidate.ns === AWIKI_SETTINGS_NAMESPACE);
|
|
612
|
-
if (descriptor === void 0 || !isRecord$
|
|
1262
|
+
if (descriptor === void 0 || !isRecord$4(descriptor.value) || typeof descriptor.value.domain !== "string") return;
|
|
613
1263
|
const domain = normalizeAwikiDomain(descriptor.value.domain);
|
|
614
1264
|
if (domain !== descriptor.value.domain) return void 0;
|
|
615
1265
|
const base = descriptor.base === void 0 ? void 0 : sanitizeLayer(descriptor.base);
|
|
@@ -653,14 +1303,138 @@ function cancelled() {
|
|
|
653
1303
|
}
|
|
654
1304
|
};
|
|
655
1305
|
}
|
|
1306
|
+
function publicTenantView(value) {
|
|
1307
|
+
return {
|
|
1308
|
+
schemaVersion: value.schemaVersion,
|
|
1309
|
+
officialCatalogVersion: value.officialCatalogVersion,
|
|
1310
|
+
generation: value.generation,
|
|
1311
|
+
activeTenantId: value.activeTenantId,
|
|
1312
|
+
tenants: value.tenants.map((tenant) => {
|
|
1313
|
+
const slot = tenant.tenantId === "builtin-primary" ? "primary" : tenant.tenantId === "builtin-secondary" ? "secondary" : void 0;
|
|
1314
|
+
return {
|
|
1315
|
+
tenantId: tenant.tenantId,
|
|
1316
|
+
storageScopeId: tenant.storageScopeId,
|
|
1317
|
+
kind: tenant.kind,
|
|
1318
|
+
displayName: tenant.displayName,
|
|
1319
|
+
...slot === void 0 ? {} : { displayNames: AWIKI_BUILTIN_TENANT_CONFIG.tenants[slot].displayName },
|
|
1320
|
+
backendBaseUrl: tenant.backendBaseUrl,
|
|
1321
|
+
didHost: tenant.didHost,
|
|
1322
|
+
lifecycle: tenant.lifecycle,
|
|
1323
|
+
storageLayout: tenant.storageLayout
|
|
1324
|
+
};
|
|
1325
|
+
}),
|
|
1326
|
+
switching: value.switching,
|
|
1327
|
+
...value.diagnostic === void 0 ? {} : { diagnostic: value.diagnostic }
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function tenantRejected(error) {
|
|
1331
|
+
return {
|
|
1332
|
+
ok: false,
|
|
1333
|
+
error: {
|
|
1334
|
+
code: "settings-rejected",
|
|
1335
|
+
message: error instanceof Error && error.message.startsWith("awiki:") ? error.message : "The Host rejected the AWiki tenant change.",
|
|
1336
|
+
details: { ns: AWIKI_SETTINGS_NAMESPACE }
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
656
1340
|
/** Build a handler whose provider lookup remains correct across Cordis reinjection. */
|
|
657
|
-
function createAwikiSettingsRpcHandler(getProvider) {
|
|
1341
|
+
function createAwikiSettingsRpcHandler(getProvider, tenantManagement) {
|
|
658
1342
|
return async (endpoint, payload, signal) => {
|
|
659
1343
|
if (signal.aborted) return cancelled();
|
|
1344
|
+
if (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.describeDesktopUpdate || endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.refreshDesktopUpdate) {
|
|
1345
|
+
if (!isRecord$4(payload)) return badRequest();
|
|
1346
|
+
try {
|
|
1347
|
+
return {
|
|
1348
|
+
ok: true,
|
|
1349
|
+
value: (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.refreshDesktopUpdate ? await tenantManagement?.refreshDesktopUpdate?.() : tenantManagement?.describeDesktopUpdate?.()) ?? null
|
|
1350
|
+
};
|
|
1351
|
+
} catch {
|
|
1352
|
+
return unavailable();
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
if (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.describeTenants) {
|
|
1356
|
+
if (!isRecord$4(payload) || tenantManagement === void 0) return unavailable();
|
|
1357
|
+
try {
|
|
1358
|
+
return {
|
|
1359
|
+
ok: true,
|
|
1360
|
+
value: publicTenantView(tenantManagement.describe())
|
|
1361
|
+
};
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
return tenantRejected(error);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
if (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.createTenant) {
|
|
1367
|
+
if (!isRecord$4(payload) || typeof payload.displayName !== "string" || typeof payload.domain !== "string" || tenantManagement === void 0) return badRequest();
|
|
1368
|
+
try {
|
|
1369
|
+
return {
|
|
1370
|
+
ok: true,
|
|
1371
|
+
value: publicTenantView(tenantManagement.create(payload.displayName, payload.domain))
|
|
1372
|
+
};
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
return tenantRejected(error);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
if (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.renameTenant) {
|
|
1378
|
+
if (!isRecord$4(payload) || typeof payload.tenantId !== "string" || typeof payload.displayName !== "string" || tenantManagement === void 0) return badRequest();
|
|
1379
|
+
try {
|
|
1380
|
+
return {
|
|
1381
|
+
ok: true,
|
|
1382
|
+
value: publicTenantView(tenantManagement.rename(payload.tenantId, payload.displayName))
|
|
1383
|
+
};
|
|
1384
|
+
} catch (error) {
|
|
1385
|
+
return tenantRejected(error);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
if (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.switchTenant) {
|
|
1389
|
+
if (!isRecord$4(payload) || typeof payload.tenantId !== "string" || tenantManagement === void 0) return badRequest();
|
|
1390
|
+
try {
|
|
1391
|
+
return {
|
|
1392
|
+
ok: true,
|
|
1393
|
+
value: publicTenantView(await tenantManagement.switch(payload.tenantId))
|
|
1394
|
+
};
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
return tenantRejected(error);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
if (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.archiveTenant) {
|
|
1400
|
+
if (!isRecord$4(payload) || typeof payload.tenantId !== "string" || tenantManagement === void 0) return badRequest();
|
|
1401
|
+
try {
|
|
1402
|
+
return {
|
|
1403
|
+
ok: true,
|
|
1404
|
+
value: publicTenantView(tenantManagement.archive(payload.tenantId))
|
|
1405
|
+
};
|
|
1406
|
+
} catch (error) {
|
|
1407
|
+
return tenantRejected(error);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
if (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.describeUpdatePolicy) {
|
|
1411
|
+
const describeUpdate = tenantManagement?.describeUpdate;
|
|
1412
|
+
if (!isRecord$4(payload) || describeUpdate === void 0) return unavailable();
|
|
1413
|
+
try {
|
|
1414
|
+
return {
|
|
1415
|
+
ok: true,
|
|
1416
|
+
value: describeUpdate.call(tenantManagement)
|
|
1417
|
+
};
|
|
1418
|
+
} catch (error) {
|
|
1419
|
+
return tenantRejected(error);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
if (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.refreshUpdatePolicy) {
|
|
1423
|
+
const refreshUpdate = tenantManagement?.refreshUpdate;
|
|
1424
|
+
if (!isRecord$4(payload) || refreshUpdate === void 0) return unavailable();
|
|
1425
|
+
try {
|
|
1426
|
+
return {
|
|
1427
|
+
ok: true,
|
|
1428
|
+
value: await refreshUpdate.call(tenantManagement)
|
|
1429
|
+
};
|
|
1430
|
+
} catch (error) {
|
|
1431
|
+
return tenantRejected(error);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
660
1434
|
const provider = getProvider();
|
|
661
1435
|
if (provider === void 0) return unavailable();
|
|
662
1436
|
if (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.describe) {
|
|
663
|
-
if (!isRecord$
|
|
1437
|
+
if (!isRecord$4(payload)) return badRequest();
|
|
664
1438
|
const current = view(provider);
|
|
665
1439
|
return current === void 0 ? unavailable() : {
|
|
666
1440
|
ok: true,
|
|
@@ -671,7 +1445,7 @@ function createAwikiSettingsRpcHandler(getProvider) {
|
|
|
671
1445
|
if (revision === void 0) return badRequest();
|
|
672
1446
|
let operation;
|
|
673
1447
|
if (endpoint === AWIKI_SETTINGS_RPC_ENDPOINTS.setDomain) {
|
|
674
|
-
if (!isRecord$
|
|
1448
|
+
if (!isRecord$4(payload) || typeof payload.domain !== "string") return badRequest();
|
|
675
1449
|
let domain;
|
|
676
1450
|
try {
|
|
677
1451
|
domain = normalizeAwikiDomain(payload.domain);
|
|
@@ -740,7 +1514,7 @@ var AwikiSessionStore = class {
|
|
|
740
1514
|
if (await readFile(this.markerPath, "utf8") !== SIGNED_OUT_MARKER) throw new TypeError("awiki: local session marker is invalid");
|
|
741
1515
|
return true;
|
|
742
1516
|
} catch (error) {
|
|
743
|
-
if (isMissing$
|
|
1517
|
+
if (isMissing$3(error)) return false;
|
|
744
1518
|
throw error;
|
|
745
1519
|
}
|
|
746
1520
|
}
|
|
@@ -758,7 +1532,7 @@ var AwikiSessionStore = class {
|
|
|
758
1532
|
mode: 384
|
|
759
1533
|
});
|
|
760
1534
|
} catch (error) {
|
|
761
|
-
if (!isFileExists$
|
|
1535
|
+
if (!isFileExists$2(error) || !await this.isSignedOut()) throw error;
|
|
762
1536
|
}
|
|
763
1537
|
await chmod(this.markerPath, 384);
|
|
764
1538
|
}
|
|
@@ -767,7 +1541,7 @@ var AwikiSessionStore = class {
|
|
|
767
1541
|
try {
|
|
768
1542
|
await unlink(this.markerPath);
|
|
769
1543
|
} catch (error) {
|
|
770
|
-
if (!isMissing$
|
|
1544
|
+
if (!isMissing$3(error)) throw error;
|
|
771
1545
|
}
|
|
772
1546
|
}
|
|
773
1547
|
async hasPrivateHostDirectory() {
|
|
@@ -776,15 +1550,15 @@ var AwikiSessionStore = class {
|
|
|
776
1550
|
if (!directory.isDirectory() || directory.isSymbolicLink()) throw new TypeError("awiki: local session directory is invalid");
|
|
777
1551
|
return true;
|
|
778
1552
|
} catch (error) {
|
|
779
|
-
if (isMissing$
|
|
1553
|
+
if (isMissing$3(error)) return false;
|
|
780
1554
|
throw error;
|
|
781
1555
|
}
|
|
782
1556
|
}
|
|
783
1557
|
};
|
|
784
|
-
function isMissing$
|
|
1558
|
+
function isMissing$3(error) {
|
|
785
1559
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
786
1560
|
}
|
|
787
|
-
function isFileExists$
|
|
1561
|
+
function isFileExists$2(error) {
|
|
788
1562
|
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
789
1563
|
}
|
|
790
1564
|
//#endregion
|
|
@@ -877,7 +1651,7 @@ var AwikiImageAttachmentCache = class {
|
|
|
877
1651
|
force: true
|
|
878
1652
|
});
|
|
879
1653
|
} catch (error) {
|
|
880
|
-
if (!isMissing$
|
|
1654
|
+
if (!isMissing$2(error)) throw error;
|
|
881
1655
|
}
|
|
882
1656
|
}
|
|
883
1657
|
decode(input, ownerDid, request) {
|
|
@@ -903,7 +1677,7 @@ var AwikiImageAttachmentCache = class {
|
|
|
903
1677
|
if (!await this.hasPrivateDirectory(this.hostDirectory)) throw new TypeError("awiki: local Host directory is invalid");
|
|
904
1678
|
await chmod(this.hostDirectory, 448);
|
|
905
1679
|
await mkdir(this.directory, { mode: 448 }).catch((error) => {
|
|
906
|
-
if (!isFileExists$
|
|
1680
|
+
if (!isFileExists$1(error)) throw error;
|
|
907
1681
|
});
|
|
908
1682
|
if (!await this.hasPrivateDirectory(this.directory)) throw new TypeError("awiki: local image cache directory is invalid");
|
|
909
1683
|
await chmod(this.directory, 448);
|
|
@@ -941,233 +1715,17 @@ var AwikiImageAttachmentCache = class {
|
|
|
941
1715
|
if (!metadata.isDirectory() || metadata.isSymbolicLink()) throw new TypeError("awiki: local image cache path is invalid");
|
|
942
1716
|
return true;
|
|
943
1717
|
} catch (error) {
|
|
944
|
-
if (isMissing$
|
|
1718
|
+
if (isMissing$2(error)) return false;
|
|
945
1719
|
throw error;
|
|
946
1720
|
}
|
|
947
1721
|
}
|
|
948
1722
|
};
|
|
949
|
-
function isMissing$3(error) {
|
|
950
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
951
|
-
}
|
|
952
|
-
function isFileExists$2(error) {
|
|
953
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
954
|
-
}
|
|
955
|
-
//#endregion
|
|
956
|
-
//#region lib/types/sent-mail-store.js
|
|
957
|
-
/** Private, owner-bound persistence for mail successfully sent by this installation. */
|
|
958
|
-
const STORE_VERSION$1 = 1;
|
|
959
|
-
const STORE_DIRECTORY$1 = "sent-mail-v1";
|
|
960
|
-
const MAX_RECORDS = 200;
|
|
961
|
-
const MAX_STORE_BYTES$1 = 16777216;
|
|
962
|
-
const PREVIEW_CHARACTERS = 160;
|
|
963
|
-
const LOCAL_SENT_MAIL_ID_PREFIX = "awiki-sent-v1:";
|
|
964
|
-
function invalidState$1() {
|
|
965
|
-
throw new TypeError("awiki: local sent-mail history is invalid");
|
|
966
|
-
}
|
|
967
1723
|
function isMissing$2(error) {
|
|
968
|
-
return typeof error === "object" && error !== null && error.code === "ENOENT";
|
|
1724
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
969
1725
|
}
|
|
970
1726
|
function isFileExists$1(error) {
|
|
971
|
-
return typeof error === "object" && error !== null && error.code === "EEXIST";
|
|
972
|
-
}
|
|
973
|
-
function validToken(value, maxCharacters) {
|
|
974
|
-
return typeof value === "string" && value.length > 0 && value.trim() === value && Array.from(value).length <= maxCharacters && !/[\u0000-\u001f\u007f-\u009f]/u.test(value);
|
|
975
|
-
}
|
|
976
|
-
function validAddress(value) {
|
|
977
|
-
return typeof value === "string" && Array.from(value).length >= 3 && Array.from(value).length <= 320 && value.includes("@") && !/[\s\u0000-\u001f\u007f-\u009f]/u.test(value);
|
|
978
|
-
}
|
|
979
|
-
function decodeRecord(input) {
|
|
980
|
-
if (typeof input !== "object" || input === null || Array.isArray(input)) invalidState$1();
|
|
981
|
-
const value = input;
|
|
982
|
-
if (typeof value.id !== "string" || !/^awiki-sent-v1:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(value.id) || value.serviceMessageId !== void 0 && !validToken(value.serviceMessageId, 2048) || value.from !== void 0 && !validAddress(value.from) || typeof value.sentAt !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value.sentAt) || !Number.isFinite(Date.parse(value.sentAt))) invalidState$1();
|
|
983
|
-
let request;
|
|
984
|
-
try {
|
|
985
|
-
request = mailSendRequest({
|
|
986
|
-
to: value.to,
|
|
987
|
-
cc: value.cc,
|
|
988
|
-
subject: value.subject,
|
|
989
|
-
bodyText: value.bodyText
|
|
990
|
-
});
|
|
991
|
-
} catch {
|
|
992
|
-
invalidState$1();
|
|
993
|
-
}
|
|
994
|
-
return {
|
|
995
|
-
id: value.id,
|
|
996
|
-
...value.serviceMessageId === void 0 ? {} : { serviceMessageId: value.serviceMessageId },
|
|
997
|
-
...value.from === void 0 ? {} : { from: value.from },
|
|
998
|
-
to: [...request.to],
|
|
999
|
-
cc: [...request.cc ?? []],
|
|
1000
|
-
subject: request.subject,
|
|
1001
|
-
bodyText: request.bodyText,
|
|
1002
|
-
sentAt: value.sentAt
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
function message(record) {
|
|
1006
|
-
const characters = Array.from(record.bodyText);
|
|
1007
|
-
const preview = characters.slice(0, PREVIEW_CHARACTERS).join("");
|
|
1008
|
-
return {
|
|
1009
|
-
summary: {
|
|
1010
|
-
id: record.id,
|
|
1011
|
-
folder: "sent",
|
|
1012
|
-
from: record.from === void 0 ? [] : [record.from],
|
|
1013
|
-
to: [...record.to],
|
|
1014
|
-
cc: [...record.cc],
|
|
1015
|
-
subject: record.subject,
|
|
1016
|
-
subjectTruncated: false,
|
|
1017
|
-
preview,
|
|
1018
|
-
previewTruncated: characters.length > PREVIEW_CHARACTERS,
|
|
1019
|
-
sentAt: record.sentAt,
|
|
1020
|
-
unread: false,
|
|
1021
|
-
hasAttachments: false,
|
|
1022
|
-
attachmentCount: 0
|
|
1023
|
-
},
|
|
1024
|
-
bodyText: record.bodyText,
|
|
1025
|
-
bodyTruncated: false,
|
|
1026
|
-
hasHtmlBody: false,
|
|
1027
|
-
attachments: []
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
function isLocalSentMailId(messageId) {
|
|
1031
|
-
return String(messageId).startsWith(LOCAL_SENT_MAIL_ID_PREFIX);
|
|
1727
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
1032
1728
|
}
|
|
1033
|
-
/** Atomic, bounded history of sends accepted by the Mail Service. */
|
|
1034
|
-
var AwikiSentMailStore = class {
|
|
1035
|
-
hostDirectory;
|
|
1036
|
-
directory;
|
|
1037
|
-
mutation = Promise.resolve();
|
|
1038
|
-
constructor(stateRoot) {
|
|
1039
|
-
this.hostDirectory = join(stateRoot, ".host");
|
|
1040
|
-
this.directory = join(this.hostDirectory, STORE_DIRECTORY$1);
|
|
1041
|
-
}
|
|
1042
|
-
async list(ownerDid, request) {
|
|
1043
|
-
const records = request.unreadOnly === true ? [] : await this.load(ownerDid);
|
|
1044
|
-
const offset = request.offset ?? 0;
|
|
1045
|
-
const limit = request.limit ?? 20;
|
|
1046
|
-
const page = records.slice(offset, offset + limit);
|
|
1047
|
-
const nextOffset = offset + page.length;
|
|
1048
|
-
const hasMore = nextOffset < records.length;
|
|
1049
|
-
return {
|
|
1050
|
-
items: page.map((record) => message(record).summary),
|
|
1051
|
-
...hasMore ? { nextOffset } : {},
|
|
1052
|
-
hasMore
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
async read(ownerDid, messageId) {
|
|
1056
|
-
if (!isLocalSentMailId(messageId)) return void 0;
|
|
1057
|
-
const record = (await this.load(ownerDid)).find((candidate) => candidate.id === String(messageId));
|
|
1058
|
-
return record === void 0 ? void 0 : message(record);
|
|
1059
|
-
}
|
|
1060
|
-
append(ownerDid, request, result, account) {
|
|
1061
|
-
const normalized = mailSendRequest(request);
|
|
1062
|
-
const record = {
|
|
1063
|
-
id: `${LOCAL_SENT_MAIL_ID_PREFIX}${randomUUID()}`,
|
|
1064
|
-
...result.messageId === void 0 ? {} : { serviceMessageId: String(result.messageId) },
|
|
1065
|
-
...account?.mailboxAddress === void 0 ? {} : { from: account.mailboxAddress },
|
|
1066
|
-
to: [...normalized.to],
|
|
1067
|
-
cc: [...normalized.cc ?? []],
|
|
1068
|
-
subject: normalized.subject,
|
|
1069
|
-
bodyText: normalized.bodyText,
|
|
1070
|
-
sentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1071
|
-
};
|
|
1072
|
-
const append = async () => {
|
|
1073
|
-
const records = await this.load(ownerDid);
|
|
1074
|
-
await this.write(ownerDid, [record, ...records].slice(0, MAX_RECORDS));
|
|
1075
|
-
};
|
|
1076
|
-
const pending = this.mutation.then(append, append);
|
|
1077
|
-
this.mutation = pending.catch(() => void 0);
|
|
1078
|
-
return pending;
|
|
1079
|
-
}
|
|
1080
|
-
async clear() {
|
|
1081
|
-
await this.mutation;
|
|
1082
|
-
try {
|
|
1083
|
-
if (!await this.hasDirectory(this.hostDirectory)) return;
|
|
1084
|
-
const metadata = await lstat(this.directory);
|
|
1085
|
-
if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
|
|
1086
|
-
await unlink(this.directory);
|
|
1087
|
-
return;
|
|
1088
|
-
}
|
|
1089
|
-
await rm(this.directory, {
|
|
1090
|
-
recursive: true,
|
|
1091
|
-
force: true
|
|
1092
|
-
});
|
|
1093
|
-
} catch (error) {
|
|
1094
|
-
if (!isMissing$2(error)) throw error;
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
async load(ownerDid) {
|
|
1098
|
-
if (!await this.hasDirectory(this.hostDirectory) || !await this.hasDirectory(this.directory)) return [];
|
|
1099
|
-
const path = this.path(ownerDid);
|
|
1100
|
-
let text;
|
|
1101
|
-
try {
|
|
1102
|
-
const metadata = await lstat(path);
|
|
1103
|
-
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > MAX_STORE_BYTES$1) invalidState$1();
|
|
1104
|
-
text = await readFile(path, "utf8");
|
|
1105
|
-
} catch (error) {
|
|
1106
|
-
if (isMissing$2(error)) return [];
|
|
1107
|
-
throw error;
|
|
1108
|
-
}
|
|
1109
|
-
let parsed;
|
|
1110
|
-
try {
|
|
1111
|
-
parsed = JSON.parse(text);
|
|
1112
|
-
} catch {
|
|
1113
|
-
invalidState$1();
|
|
1114
|
-
}
|
|
1115
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) invalidState$1();
|
|
1116
|
-
const value = parsed;
|
|
1117
|
-
if (value.version !== STORE_VERSION$1 || value.ownerDid !== String(ownerDid) || !Array.isArray(value.records) || value.records.length > MAX_RECORDS) invalidState$1();
|
|
1118
|
-
return value.records.map(decodeRecord);
|
|
1119
|
-
}
|
|
1120
|
-
async write(ownerDid, records) {
|
|
1121
|
-
await this.ensureDirectory();
|
|
1122
|
-
const path = this.path(ownerDid);
|
|
1123
|
-
const temporary = join(this.directory, `.${this.key(ownerDid)}.${randomUUID()}.tmp`);
|
|
1124
|
-
const text = JSON.stringify({
|
|
1125
|
-
version: STORE_VERSION$1,
|
|
1126
|
-
ownerDid: String(ownerDid),
|
|
1127
|
-
records
|
|
1128
|
-
});
|
|
1129
|
-
if (Buffer.byteLength(text, "utf8") > MAX_STORE_BYTES$1) invalidState$1();
|
|
1130
|
-
try {
|
|
1131
|
-
await writeFile(temporary, text, {
|
|
1132
|
-
flag: "wx",
|
|
1133
|
-
mode: 384
|
|
1134
|
-
});
|
|
1135
|
-
await rename(temporary, path);
|
|
1136
|
-
await chmod(path, 384);
|
|
1137
|
-
} finally {
|
|
1138
|
-
await unlink(temporary).catch(() => void 0);
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
async ensureDirectory() {
|
|
1142
|
-
await mkdir(this.hostDirectory, {
|
|
1143
|
-
recursive: true,
|
|
1144
|
-
mode: 448
|
|
1145
|
-
});
|
|
1146
|
-
if (!await this.hasDirectory(this.hostDirectory)) invalidState$1();
|
|
1147
|
-
await chmod(this.hostDirectory, 448);
|
|
1148
|
-
await mkdir(this.directory, { mode: 448 }).catch((error) => {
|
|
1149
|
-
if (!isFileExists$1(error)) throw error;
|
|
1150
|
-
});
|
|
1151
|
-
if (!await this.hasDirectory(this.directory)) invalidState$1();
|
|
1152
|
-
await chmod(this.directory, 448);
|
|
1153
|
-
}
|
|
1154
|
-
async hasDirectory(path) {
|
|
1155
|
-
try {
|
|
1156
|
-
const metadata = await lstat(path);
|
|
1157
|
-
if (!metadata.isDirectory() || metadata.isSymbolicLink()) invalidState$1();
|
|
1158
|
-
return true;
|
|
1159
|
-
} catch (error) {
|
|
1160
|
-
if (isMissing$2(error)) return false;
|
|
1161
|
-
throw error;
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
path(ownerDid) {
|
|
1165
|
-
return join(this.directory, `${this.key(ownerDid)}.json`);
|
|
1166
|
-
}
|
|
1167
|
-
key(ownerDid) {
|
|
1168
|
-
return createHash("sha256").update(String(ownerDid)).digest("hex");
|
|
1169
|
-
}
|
|
1170
|
-
};
|
|
1171
1729
|
//#endregion
|
|
1172
1730
|
//#region lib/types/conversation-preferences.js
|
|
1173
1731
|
const STORE_VERSION = 1;
|
|
@@ -1187,7 +1745,7 @@ function isMissing$1(error) {
|
|
|
1187
1745
|
function isFileExists(error) {
|
|
1188
1746
|
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
1189
1747
|
}
|
|
1190
|
-
function isRecord$
|
|
1748
|
+
function isRecord$3(value) {
|
|
1191
1749
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1192
1750
|
}
|
|
1193
1751
|
function boundedString$1(value, maximum) {
|
|
@@ -1210,7 +1768,7 @@ function optionalTimestamp(value) {
|
|
|
1210
1768
|
}
|
|
1211
1769
|
/** Rebuild a bounded display-only snapshot before it reaches private persistence. */
|
|
1212
1770
|
function conversation(value) {
|
|
1213
|
-
if (!isRecord$
|
|
1771
|
+
if (!isRecord$3(value)) invalidState();
|
|
1214
1772
|
const kind = value.kind;
|
|
1215
1773
|
const id = boundedString$1(value.id, MAX_IDENTIFIER_CHARACTERS);
|
|
1216
1774
|
const title = boundedString$1(value.title, MAX_TITLE_CHARACTERS);
|
|
@@ -1249,14 +1807,14 @@ function conversation(value) {
|
|
|
1249
1807
|
invalidState();
|
|
1250
1808
|
}
|
|
1251
1809
|
function hiddenPreference(value) {
|
|
1252
|
-
if (!isRecord$
|
|
1810
|
+
if (!isRecord$3(value) || !Number.isSafeInteger(value.hiddenAt) || value.hiddenAt < 0) invalidState();
|
|
1253
1811
|
return {
|
|
1254
1812
|
conversation: conversation(value.conversation),
|
|
1255
1813
|
hiddenAt: value.hiddenAt
|
|
1256
1814
|
};
|
|
1257
1815
|
}
|
|
1258
1816
|
function preferences(value, expectedOwnerDid) {
|
|
1259
|
-
if (!isRecord$
|
|
1817
|
+
if (!isRecord$3(value) || value.version !== STORE_VERSION || value.ownerDid !== String(expectedOwnerDid) || !Array.isArray(value.hiddenConversations) || value.hiddenConversations.length > MAX_HIDDEN_CONVERSATIONS) invalidState();
|
|
1260
1818
|
const hiddenConversations = value.hiddenConversations.map(hiddenPreference);
|
|
1261
1819
|
const seen = /* @__PURE__ */ new Set();
|
|
1262
1820
|
for (const hidden of hiddenConversations) {
|
|
@@ -1285,7 +1843,7 @@ function emptyPreferences(ownerDid) {
|
|
|
1285
1843
|
/** Validate one browser mutation before entering private Host persistence. */
|
|
1286
1844
|
function normalizeConversationPreferenceMutation(value) {
|
|
1287
1845
|
try {
|
|
1288
|
-
if (!isRecord$
|
|
1846
|
+
if (!isRecord$3(value)) return void 0;
|
|
1289
1847
|
if (value.action === "hide") return {
|
|
1290
1848
|
action: "hide",
|
|
1291
1849
|
conversation: conversation(value.conversation)
|
|
@@ -1440,7 +1998,7 @@ function emptyState(identityScopeHash) {
|
|
|
1440
1998
|
conversations: {}
|
|
1441
1999
|
};
|
|
1442
2000
|
}
|
|
1443
|
-
function isRecord(value) {
|
|
2001
|
+
function isRecord$2(value) {
|
|
1444
2002
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1445
2003
|
}
|
|
1446
2004
|
function boundedString(value, field) {
|
|
@@ -1449,15 +2007,15 @@ function boundedString(value, field) {
|
|
|
1449
2007
|
}
|
|
1450
2008
|
/** Validate and detach the complete state file before the listener trusts any route. */
|
|
1451
2009
|
function parseState(value, identityScopeHash) {
|
|
1452
|
-
if (isRecord(value) && value.version === 1) return emptyState(identityScopeHash);
|
|
1453
|
-
if (isRecord(value) && value.version === STATE_VERSION && typeof value.identityScopeHash === "string" && value.identityScopeHash !== identityScopeHash) return emptyState(identityScopeHash);
|
|
1454
|
-
if (!isRecord(value) || value.version !== STATE_VERSION || value.identityScopeHash !== identityScopeHash || !isRecord(value.conversations)) throw new TypeError("awiki: listener state is invalid");
|
|
2010
|
+
if (isRecord$2(value) && value.version === 1) return emptyState(identityScopeHash);
|
|
2011
|
+
if (isRecord$2(value) && value.version === STATE_VERSION && typeof value.identityScopeHash === "string" && value.identityScopeHash !== identityScopeHash) return emptyState(identityScopeHash);
|
|
2012
|
+
if (!isRecord$2(value) || value.version !== STATE_VERSION || value.identityScopeHash !== identityScopeHash || !isRecord$2(value.conversations)) throw new TypeError("awiki: listener state is invalid");
|
|
1455
2013
|
const entries = Object.entries(value.conversations);
|
|
1456
2014
|
if (entries.length > MAX_CONVERSATIONS) throw new TypeError("awiki: listener state is too large");
|
|
1457
2015
|
const conversations = {};
|
|
1458
2016
|
for (const [conversationId, raw] of entries) {
|
|
1459
2017
|
const id = boundedString(conversationId, "conversation id");
|
|
1460
|
-
if (!isRecord(raw)) throw new TypeError("awiki: listener conversation state is invalid");
|
|
2018
|
+
if (!isRecord$2(raw)) throw new TypeError("awiki: listener conversation state is invalid");
|
|
1461
2019
|
if (Object.keys(raw).some((key) => ![
|
|
1462
2020
|
"peerDid",
|
|
1463
2021
|
"sessionId",
|
|
@@ -2026,6 +2584,9 @@ var IdentityRealtimeSupervisor = class {
|
|
|
2026
2584
|
lastFailureCode;
|
|
2027
2585
|
lastConnectedAtMs;
|
|
2028
2586
|
lastCommittedSyncCause;
|
|
2587
|
+
lastSyncPagesFetched;
|
|
2588
|
+
lastSyncMessagesHydrated;
|
|
2589
|
+
lastSyncOlderHistoryExcluded;
|
|
2029
2590
|
retryTimer;
|
|
2030
2591
|
resolveRetry;
|
|
2031
2592
|
constructor(realtime, config = {}, logger = {
|
|
@@ -2059,7 +2620,10 @@ var IdentityRealtimeSupervisor = class {
|
|
|
2059
2620
|
lifecyclePhase: this.lifecyclePhase,
|
|
2060
2621
|
...this.lastFailureCode === void 0 ? {} : { lastFailureCode: this.lastFailureCode },
|
|
2061
2622
|
...this.lastConnectedAtMs === void 0 ? {} : { lastConnectedAtMs: this.lastConnectedAtMs },
|
|
2062
|
-
...this.lastCommittedSyncCause === void 0 ? {} : { lastCommittedSyncCause: this.lastCommittedSyncCause }
|
|
2623
|
+
...this.lastCommittedSyncCause === void 0 ? {} : { lastCommittedSyncCause: this.lastCommittedSyncCause },
|
|
2624
|
+
...this.lastSyncPagesFetched === void 0 ? {} : { lastSyncPagesFetched: this.lastSyncPagesFetched },
|
|
2625
|
+
...this.lastSyncMessagesHydrated === void 0 ? {} : { lastSyncMessagesHydrated: this.lastSyncMessagesHydrated },
|
|
2626
|
+
...this.lastSyncOlderHistoryExcluded === void 0 ? {} : { lastSyncOlderHistoryExcluded: this.lastSyncOlderHistoryExcluded }
|
|
2063
2627
|
};
|
|
2064
2628
|
}
|
|
2065
2629
|
/** Fence late events, wake retry sleep, stop the exact session, and join the lifecycle. */
|
|
@@ -2134,57 +2698,815 @@ var IdentityRealtimeSupervisor = class {
|
|
|
2134
2698
|
}
|
|
2135
2699
|
}
|
|
2136
2700
|
}
|
|
2137
|
-
async synchronize(cause, generation) {
|
|
2138
|
-
await this.realtime.syncNow(syncReason(cause));
|
|
2139
|
-
if (!this.current(generation)) return;
|
|
2140
|
-
this.lastCommittedSyncCause = cause;
|
|
2141
|
-
|
|
2142
|
-
|
|
2701
|
+
async synchronize(cause, generation) {
|
|
2702
|
+
const result = await this.realtime.syncNow(syncReason(cause));
|
|
2703
|
+
if (!this.current(generation)) return;
|
|
2704
|
+
this.lastCommittedSyncCause = cause;
|
|
2705
|
+
this.lastSyncPagesFetched = result.pagesFetched;
|
|
2706
|
+
this.lastSyncMessagesHydrated = result.messagesHydrated;
|
|
2707
|
+
this.lastSyncOlderHistoryExcluded = result.olderHistoryExcluded;
|
|
2708
|
+
try {
|
|
2709
|
+
await this.config.onSynchronized?.(cause);
|
|
2710
|
+
} catch (error) {
|
|
2711
|
+
this.logger.warn("AWiki realtime post-sync consumer failed: %s", error instanceof Error ? error.message : "unknown failure");
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
observeConnection(event) {
|
|
2715
|
+
if (event.kind !== "connection_state_changed") return;
|
|
2716
|
+
this.connected = event.state === "connected";
|
|
2717
|
+
if (this.connected) {
|
|
2718
|
+
this.lifecyclePhase = "connected";
|
|
2719
|
+
this.lastConnectedAtMs = Date.now();
|
|
2720
|
+
this.lastFailureCode = void 0;
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
async stopSession(session) {
|
|
2724
|
+
if (this.stoppedSessions.has(session)) return;
|
|
2725
|
+
this.stoppedSessions.add(session);
|
|
2726
|
+
if (this.activeSession === session) this.activeSession = void 0;
|
|
2727
|
+
this.connected = false;
|
|
2728
|
+
this.lifecyclePhase = "stopping";
|
|
2729
|
+
this.stopCount += 1;
|
|
2730
|
+
await session.stop();
|
|
2731
|
+
}
|
|
2732
|
+
waitForRetry(failures, generation) {
|
|
2733
|
+
if (!this.current(generation)) return Promise.resolve();
|
|
2734
|
+
const delay = Math.min(RETRY_BASE_DELAY_MS * 2 ** Math.min(failures - 1, 10), RETRY_MAX_DELAY_MS);
|
|
2735
|
+
return new Promise((resolve) => {
|
|
2736
|
+
const finish = () => {
|
|
2737
|
+
if (this.retryTimer === timer) this.retryTimer = void 0;
|
|
2738
|
+
if (this.resolveRetry === finish) this.resolveRetry = void 0;
|
|
2739
|
+
resolve();
|
|
2740
|
+
};
|
|
2741
|
+
const timer = setTimeout(finish, delay);
|
|
2742
|
+
this.retryTimer = timer;
|
|
2743
|
+
this.resolveRetry = finish;
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2746
|
+
wakeRetry() {
|
|
2747
|
+
const timer = this.retryTimer;
|
|
2748
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
2749
|
+
this.resolveRetry?.();
|
|
2750
|
+
}
|
|
2751
|
+
};
|
|
2752
|
+
//#endregion
|
|
2753
|
+
//#region lib/types/profile-state.js
|
|
2754
|
+
function assertProfileName(value, source) {
|
|
2755
|
+
if (typeof value !== "string" || value.length === 0 || value.includes("/") || value.includes("\\") || value === "." || value === ".." || value === "node_modules") throw new TypeError(`awiki: ${source} supplied an invalid profile name`);
|
|
2756
|
+
return value;
|
|
2757
|
+
}
|
|
2758
|
+
/** Resolve only profile identities supplied by an authoritative DSH service or Loader directory. */
|
|
2759
|
+
function resolveAwikiProfileName(ctx, dshHome) {
|
|
2760
|
+
const desktopProfiles = ctx.get("desktopProfiles");
|
|
2761
|
+
if (desktopProfiles !== void 0) return assertProfileName(desktopProfiles.current?.name, "desktopProfiles.current");
|
|
2762
|
+
let profileDir;
|
|
2763
|
+
try {
|
|
2764
|
+
if (ctx.baseUrl === void 0) return void 0;
|
|
2765
|
+
const baseUrl = new URL(ctx.baseUrl);
|
|
2766
|
+
if (baseUrl.protocol !== "file:") return void 0;
|
|
2767
|
+
profileDir = resolve(fileURLToPath(baseUrl));
|
|
2768
|
+
} catch {
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
const profilesDir = resolve(dshHome, "profiles");
|
|
2772
|
+
if (dirname(profileDir) !== profilesDir) return void 0;
|
|
2773
|
+
return assertProfileName(basename(profileDir), "Loader profile directory");
|
|
2774
|
+
}
|
|
2775
|
+
/** Preserve the historical fallback while isolating desktop/CLI profiles at the Host boundary. */
|
|
2776
|
+
function resolveAwikiStateRoot(ctx, dshHome) {
|
|
2777
|
+
const profileName = resolveAwikiProfileName(ctx, dshHome);
|
|
2778
|
+
return profileName === void 0 ? join(dshHome, "awiki", "im-core") : join(dshHome, "awiki", profileName, "im-core");
|
|
2779
|
+
}
|
|
2780
|
+
//#endregion
|
|
2781
|
+
//#region lib/types/tenant-registry.js
|
|
2782
|
+
/** Host-owned durable AWiki tenant catalog and storage-scope migration. */
|
|
2783
|
+
const AWIKI_TENANT_REGISTRY_SCHEMA_VERSION = 1;
|
|
2784
|
+
const AWIKI_OFFICIAL_CATALOG_VERSION = 2;
|
|
2785
|
+
const AWIKI_PRIMARY_TENANT_ID = "builtin-primary";
|
|
2786
|
+
const AWIKI_SECONDARY_TENANT_ID = "builtin-secondary";
|
|
2787
|
+
/** Compatibility aliases for existing callers; the persisted IDs are generic slots. */
|
|
2788
|
+
const AWIKI_CHINA_TENANT_ID = AWIKI_PRIMARY_TENANT_ID;
|
|
2789
|
+
const AWIKI_GLOBAL_TENANT_ID = AWIKI_SECONDARY_TENANT_ID;
|
|
2790
|
+
const OFFICIALS = ["primary", "secondary"].map((slot) => {
|
|
2791
|
+
const configured = AWIKI_BUILTIN_TENANT_CONFIG.tenants[slot];
|
|
2792
|
+
return {
|
|
2793
|
+
slot,
|
|
2794
|
+
tenantId: slot === "primary" ? AWIKI_PRIMARY_TENANT_ID : AWIKI_SECONDARY_TENANT_ID,
|
|
2795
|
+
legacyTenantId: slot === "primary" ? "official-china" : "official-global",
|
|
2796
|
+
storageScopeId: `builtin-${slot}-v1`,
|
|
2797
|
+
displayName: configured.displayName["zh-CN"],
|
|
2798
|
+
backendOrigin: configured.backendOrigin,
|
|
2799
|
+
didHost: configured.didHost
|
|
2800
|
+
};
|
|
2801
|
+
});
|
|
2802
|
+
/** Immutable snapshot of the DSH endpoint defaults shipped before tenant registries existed. */
|
|
2803
|
+
const PRE_REGISTRY_DEFAULT_DOMAIN = "awiki.info";
|
|
2804
|
+
const PRE_REGISTRY_DEFAULT_ENDPOINTS = endpointsForOrigin(`https://${PRE_REGISTRY_DEFAULT_DOMAIN}`, PRE_REGISTRY_DEFAULT_DOMAIN);
|
|
2805
|
+
function registryFilePath(baseStateRoot) {
|
|
2806
|
+
return join(dirname(baseStateRoot), `${basename(baseStateRoot)}.tenant-registry.json`);
|
|
2807
|
+
}
|
|
2808
|
+
function endpointsForOrigin(origin, domain) {
|
|
2809
|
+
return {
|
|
2810
|
+
userServiceUrl: origin,
|
|
2811
|
+
messageServiceUrl: origin,
|
|
2812
|
+
mailServiceUrl: origin,
|
|
2813
|
+
messageServicePublicUrl: origin,
|
|
2814
|
+
messageServiceDid: `did:wba:${domain}`
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
function officialProfile(entry) {
|
|
2818
|
+
return {
|
|
2819
|
+
tenantId: entry.tenantId,
|
|
2820
|
+
storageScopeId: entry.storageScopeId,
|
|
2821
|
+
kind: "built_in",
|
|
2822
|
+
displayName: entry.displayName,
|
|
2823
|
+
backendBaseUrl: entry.backendOrigin,
|
|
2824
|
+
didHost: entry.didHost,
|
|
2825
|
+
lifecycle: "inactive",
|
|
2826
|
+
storageLayout: "scope-v1",
|
|
2827
|
+
endpoints: endpointsForOrigin(entry.backendOrigin, entry.didHost)
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
function isRecord$1(value) {
|
|
2831
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2832
|
+
}
|
|
2833
|
+
function safeString(value) {
|
|
2834
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
2835
|
+
}
|
|
2836
|
+
function decodeEndpoints(value) {
|
|
2837
|
+
if (!isRecord$1(value)) return void 0;
|
|
2838
|
+
const userServiceUrl = safeString(value.userServiceUrl);
|
|
2839
|
+
const messageServiceUrl = safeString(value.messageServiceUrl);
|
|
2840
|
+
const mailServiceUrl = safeString(value.mailServiceUrl);
|
|
2841
|
+
const messageServicePublicUrl = safeString(value.messageServicePublicUrl);
|
|
2842
|
+
const messageServiceDid = safeString(value.messageServiceDid);
|
|
2843
|
+
if (userServiceUrl === void 0 || messageServiceUrl === void 0 || mailServiceUrl === void 0 || messageServicePublicUrl === void 0 || messageServiceDid === void 0) return void 0;
|
|
2844
|
+
return {
|
|
2845
|
+
userServiceUrl,
|
|
2846
|
+
messageServiceUrl,
|
|
2847
|
+
mailServiceUrl,
|
|
2848
|
+
messageServicePublicUrl,
|
|
2849
|
+
messageServiceDid
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
function decodeTenant(value) {
|
|
2853
|
+
if (!isRecord$1(value)) return void 0;
|
|
2854
|
+
const tenantId = safeString(value.tenantId);
|
|
2855
|
+
const storageScopeId = safeString(value.storageScopeId);
|
|
2856
|
+
const displayName = safeString(value.displayName);
|
|
2857
|
+
const backendBaseUrl = safeString(value.backendBaseUrl);
|
|
2858
|
+
const didHost = safeString(value.didHost);
|
|
2859
|
+
const endpoints = decodeEndpoints(value.endpoints);
|
|
2860
|
+
if (tenantId === void 0 || storageScopeId === void 0 || displayName === void 0 || backendBaseUrl === void 0 || didHost === void 0 || endpoints === void 0 || value.kind !== "built_in" && value.kind !== "custom" || value.lifecycle !== "active" && value.lifecycle !== "inactive" && value.lifecycle !== "archived" || value.storageLayout !== "scope-v1" && value.storageLayout !== "legacy-base" && value.storageLayout !== "domain-v1") return;
|
|
2861
|
+
return {
|
|
2862
|
+
tenantId,
|
|
2863
|
+
storageScopeId,
|
|
2864
|
+
kind: value.kind,
|
|
2865
|
+
displayName,
|
|
2866
|
+
backendBaseUrl,
|
|
2867
|
+
didHost,
|
|
2868
|
+
lifecycle: value.lifecycle,
|
|
2869
|
+
storageLayout: value.storageLayout,
|
|
2870
|
+
endpoints
|
|
2871
|
+
};
|
|
2872
|
+
}
|
|
2873
|
+
function decodeDocument(value) {
|
|
2874
|
+
if (!isRecord$1(value) || value.schemaVersion !== 1 || !Number.isSafeInteger(value.officialCatalogVersion) || value.officialCatalogVersion < 1 || value.officialCatalogVersion > 2 || !Number.isSafeInteger(value.generation) || value.generation < 0 || typeof value.activeTenantId !== "string" || !Array.isArray(value.tenants)) return void 0;
|
|
2875
|
+
const tenants = value.tenants.map(decodeTenant);
|
|
2876
|
+
if (tenants.some((tenant) => tenant === void 0)) return void 0;
|
|
2877
|
+
const complete = tenants;
|
|
2878
|
+
if (new Set(complete.map((tenant) => tenant.tenantId)).size !== complete.length || new Set(complete.map((tenant) => tenant.storageScopeId)).size !== complete.length || !complete.some((tenant) => tenant.tenantId === value.activeTenantId && tenant.lifecycle === "active") || complete.filter((tenant) => tenant.lifecycle === "active").length !== 1) return void 0;
|
|
2879
|
+
return {
|
|
2880
|
+
schemaVersion: 1,
|
|
2881
|
+
officialCatalogVersion: 2,
|
|
2882
|
+
generation: value.generation,
|
|
2883
|
+
activeTenantId: value.activeTenantId,
|
|
2884
|
+
tenants: complete
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
function directoryHasData(path) {
|
|
2888
|
+
try {
|
|
2889
|
+
return statSync(path).isDirectory() && readdirSync(path).length > 0;
|
|
2890
|
+
} catch {
|
|
2891
|
+
return false;
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
function sameOfficialEndpoint(tenant, entry) {
|
|
2895
|
+
const origin = entry.backendOrigin;
|
|
2896
|
+
return tenant.didHost === entry.didHost || tenant.backendBaseUrl.replace(/\/$/u, "") === origin || tenant.endpoints.userServiceUrl.replace(/\/$/u, "") === origin;
|
|
2897
|
+
}
|
|
2898
|
+
function preservedTenantId(tenants, tenant) {
|
|
2899
|
+
const base = `custom-${tenant.storageScopeId}`;
|
|
2900
|
+
if (!tenants.some((candidate) => candidate.tenantId === base)) return base;
|
|
2901
|
+
let suffix = 2;
|
|
2902
|
+
while (tenants.some((candidate) => candidate.tenantId === `${base}-${suffix}`)) suffix += 1;
|
|
2903
|
+
return `${base}-${suffix}`;
|
|
2904
|
+
}
|
|
2905
|
+
function activate(document, tenantId, generation) {
|
|
2906
|
+
return {
|
|
2907
|
+
...document,
|
|
2908
|
+
generation,
|
|
2909
|
+
activeTenantId: tenantId,
|
|
2910
|
+
tenants: document.tenants.map((tenant) => ({
|
|
2911
|
+
...tenant,
|
|
2912
|
+
lifecycle: tenant.tenantId === tenantId ? "active" : tenant.lifecycle === "active" ? "inactive" : tenant.lifecycle
|
|
2913
|
+
}))
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
function reconcileOfficials(document) {
|
|
2917
|
+
const tenants = [...document.tenants];
|
|
2918
|
+
let activeTenantId = document.activeTenantId;
|
|
2919
|
+
for (const entry of OFFICIALS) {
|
|
2920
|
+
let exactIndex = tenants.findIndex((tenant) => tenant.tenantId === entry.tenantId);
|
|
2921
|
+
if (exactIndex >= 0) {
|
|
2922
|
+
const current = tenants[exactIndex];
|
|
2923
|
+
if (sameOfficialEndpoint(current, entry)) {
|
|
2924
|
+
tenants[exactIndex] = {
|
|
2925
|
+
...officialProfile(entry),
|
|
2926
|
+
storageScopeId: current.storageScopeId,
|
|
2927
|
+
storageLayout: current.storageLayout,
|
|
2928
|
+
lifecycle: current.lifecycle
|
|
2929
|
+
};
|
|
2930
|
+
continue;
|
|
2931
|
+
}
|
|
2932
|
+
const preservedId = preservedTenantId(tenants, current);
|
|
2933
|
+
tenants[exactIndex] = {
|
|
2934
|
+
...current,
|
|
2935
|
+
tenantId: preservedId,
|
|
2936
|
+
kind: "custom"
|
|
2937
|
+
};
|
|
2938
|
+
if (activeTenantId === current.tenantId) activeTenantId = preservedId;
|
|
2939
|
+
exactIndex = -1;
|
|
2940
|
+
}
|
|
2941
|
+
const legacyIndex = tenants.findIndex((tenant) => tenant.tenantId === entry.legacyTenantId);
|
|
2942
|
+
if (legacyIndex >= 0) {
|
|
2943
|
+
const current = tenants[legacyIndex];
|
|
2944
|
+
if (sameOfficialEndpoint(current, entry)) {
|
|
2945
|
+
tenants[legacyIndex] = {
|
|
2946
|
+
...officialProfile(entry),
|
|
2947
|
+
storageScopeId: current.storageScopeId,
|
|
2948
|
+
storageLayout: current.storageLayout,
|
|
2949
|
+
lifecycle: current.lifecycle
|
|
2950
|
+
};
|
|
2951
|
+
if (activeTenantId === current.tenantId) activeTenantId = entry.tenantId;
|
|
2952
|
+
continue;
|
|
2953
|
+
}
|
|
2954
|
+
tenants[legacyIndex] = {
|
|
2955
|
+
...current,
|
|
2956
|
+
kind: "custom"
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2959
|
+
const endpointIndex = tenants.findIndex((tenant) => sameOfficialEndpoint(tenant, entry));
|
|
2960
|
+
if (endpointIndex >= 0) {
|
|
2961
|
+
const current = tenants[endpointIndex];
|
|
2962
|
+
tenants[endpointIndex] = {
|
|
2963
|
+
...officialProfile(entry),
|
|
2964
|
+
storageScopeId: current.storageScopeId,
|
|
2965
|
+
storageLayout: current.storageLayout,
|
|
2966
|
+
lifecycle: current.lifecycle
|
|
2967
|
+
};
|
|
2968
|
+
if (activeTenantId === current.tenantId) activeTenantId = entry.tenantId;
|
|
2969
|
+
} else tenants.push(officialProfile(entry));
|
|
2970
|
+
}
|
|
2971
|
+
return activate({
|
|
2972
|
+
...document,
|
|
2973
|
+
officialCatalogVersion: 2,
|
|
2974
|
+
activeTenantId,
|
|
2975
|
+
tenants
|
|
2976
|
+
}, activeTenantId, document.generation);
|
|
2977
|
+
}
|
|
2978
|
+
function cloneDocument(document) {
|
|
2979
|
+
return structuredClone(document);
|
|
2980
|
+
}
|
|
2981
|
+
/** Durable registry. All mutations are synchronous, backup-aware, and atomic. */
|
|
2982
|
+
var AwikiTenantRegistry = class AwikiTenantRegistry {
|
|
2983
|
+
baseStateRoot;
|
|
2984
|
+
filePath;
|
|
2985
|
+
backupPath;
|
|
2986
|
+
diagnosticPath;
|
|
2987
|
+
document;
|
|
2988
|
+
persistent;
|
|
2989
|
+
constructor(baseStateRoot, document, persistent) {
|
|
2990
|
+
this.baseStateRoot = baseStateRoot;
|
|
2991
|
+
this.filePath = registryFilePath(baseStateRoot);
|
|
2992
|
+
this.backupPath = `${this.filePath}.pre-v1.bak`;
|
|
2993
|
+
this.diagnosticPath = `${this.filePath}.migration-error.txt`;
|
|
2994
|
+
this.document = document;
|
|
2995
|
+
this.persistent = persistent;
|
|
2996
|
+
}
|
|
2997
|
+
static open(baseStateRoot, seed) {
|
|
2998
|
+
const filePath = registryFilePath(baseStateRoot);
|
|
2999
|
+
if (existsSync(filePath)) try {
|
|
3000
|
+
const decoded = decodeDocument(JSON.parse(readFileSync(filePath, "utf8")));
|
|
3001
|
+
if (decoded === void 0) throw new Error("registry schema or invariants are invalid");
|
|
3002
|
+
const registry = new AwikiTenantRegistry(baseStateRoot, reconcileOfficials(decoded), true);
|
|
3003
|
+
if (JSON.stringify(decoded) !== JSON.stringify(registry.document)) registry.persist(true);
|
|
3004
|
+
return registry;
|
|
2143
3005
|
} catch (error) {
|
|
2144
|
-
|
|
3006
|
+
const diagnostic = `AWiki tenant registry was not migrated: ${error instanceof Error ? error.message : "unknown error"}\n`;
|
|
3007
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
3008
|
+
writeFileSync(`${filePath}.migration-error.txt`, diagnostic, {
|
|
3009
|
+
encoding: "utf8",
|
|
3010
|
+
mode: 384
|
|
3011
|
+
});
|
|
3012
|
+
return new AwikiTenantRegistry(baseStateRoot, legacyDocument(seed, !seed.configured), false);
|
|
2145
3013
|
}
|
|
3014
|
+
const hasLegacyData = directoryHasData(baseStateRoot);
|
|
3015
|
+
const document = seed.configured || hasLegacyData ? legacyDocument(seed, hasLegacyData && !seed.configured) : freshDocument();
|
|
3016
|
+
const registry = new AwikiTenantRegistry(baseStateRoot, document, true);
|
|
3017
|
+
registry.persist(false);
|
|
3018
|
+
return registry;
|
|
2146
3019
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
this.
|
|
2152
|
-
|
|
2153
|
-
|
|
3020
|
+
snapshot(switching = false) {
|
|
3021
|
+
return {
|
|
3022
|
+
...cloneDocument(this.document),
|
|
3023
|
+
switching,
|
|
3024
|
+
...this.persistent ? {} : { diagnostic: this.diagnosticPath }
|
|
3025
|
+
};
|
|
3026
|
+
}
|
|
3027
|
+
active() {
|
|
3028
|
+
const tenant = this.document.tenants.find((candidate) => candidate.tenantId === this.document.activeTenantId);
|
|
3029
|
+
if (tenant === void 0) throw new Error("awiki: active tenant is unavailable");
|
|
3030
|
+
return structuredClone(tenant);
|
|
3031
|
+
}
|
|
3032
|
+
find(tenantId) {
|
|
3033
|
+
const tenant = this.document.tenants.find((candidate) => candidate.tenantId === tenantId);
|
|
3034
|
+
return tenant === void 0 ? void 0 : structuredClone(tenant);
|
|
3035
|
+
}
|
|
3036
|
+
stateRoot(tenant) {
|
|
3037
|
+
if (tenant.storageLayout === "legacy-base") return this.baseStateRoot;
|
|
3038
|
+
if (tenant.storageLayout === "domain-v1") return join(dirname(this.baseStateRoot), "tenants", tenant.didHost, basename(this.baseStateRoot));
|
|
3039
|
+
return join(dirname(this.baseStateRoot), "tenant-scopes", tenant.storageScopeId, basename(this.baseStateRoot));
|
|
3040
|
+
}
|
|
3041
|
+
createCustom(displayName, rawDomain) {
|
|
3042
|
+
const domain = normalizeAwikiDomain(rawDomain);
|
|
3043
|
+
const name = displayName.trim();
|
|
3044
|
+
if (name.length === 0 || Array.from(name).length > 80) throw new TypeError("awiki: tenant name is required and must not exceed 80 characters");
|
|
3045
|
+
if (this.document.tenants.some((tenant) => tenant.didHost === domain || tenant.backendBaseUrl === `https://${domain}`)) throw new Error("awiki: a tenant with this endpoint already exists");
|
|
3046
|
+
const tenant = {
|
|
3047
|
+
tenantId: randomUUID(),
|
|
3048
|
+
storageScopeId: randomUUID(),
|
|
3049
|
+
kind: "custom",
|
|
3050
|
+
displayName: name,
|
|
3051
|
+
backendBaseUrl: `https://${domain}`,
|
|
3052
|
+
didHost: domain,
|
|
3053
|
+
lifecycle: "inactive",
|
|
3054
|
+
storageLayout: "scope-v1",
|
|
3055
|
+
endpoints: endpointsForOrigin(`https://${domain}`, domain)
|
|
3056
|
+
};
|
|
3057
|
+
this.document = {
|
|
3058
|
+
...this.document,
|
|
3059
|
+
tenants: [...this.document.tenants, tenant]
|
|
3060
|
+
};
|
|
3061
|
+
this.persist(true);
|
|
3062
|
+
return structuredClone(tenant);
|
|
3063
|
+
}
|
|
3064
|
+
renameCustom(tenantId, displayName) {
|
|
3065
|
+
const name = displayName.trim();
|
|
3066
|
+
if (name.length === 0 || Array.from(name).length > 80) throw new TypeError("awiki: tenant name is required and must not exceed 80 characters");
|
|
3067
|
+
const current = this.find(tenantId);
|
|
3068
|
+
if (current === void 0) throw new Error("awiki: tenant does not exist");
|
|
3069
|
+
if (current.kind === "built_in") throw new Error("awiki: official tenants cannot be modified");
|
|
3070
|
+
const updated = {
|
|
3071
|
+
...current,
|
|
3072
|
+
displayName: name
|
|
3073
|
+
};
|
|
3074
|
+
this.document = {
|
|
3075
|
+
...this.document,
|
|
3076
|
+
tenants: this.document.tenants.map((tenant) => tenant.tenantId === tenantId ? updated : tenant)
|
|
3077
|
+
};
|
|
3078
|
+
this.persist(true);
|
|
3079
|
+
return structuredClone(updated);
|
|
3080
|
+
}
|
|
3081
|
+
archiveCustom(tenantId) {
|
|
3082
|
+
const current = this.find(tenantId);
|
|
3083
|
+
if (current === void 0) throw new Error("awiki: tenant does not exist");
|
|
3084
|
+
if (current.kind === "built_in") throw new Error("awiki: official tenants cannot be archived");
|
|
3085
|
+
if (current.lifecycle === "active") throw new Error("awiki: switch away before archiving this tenant");
|
|
3086
|
+
this.document = {
|
|
3087
|
+
...this.document,
|
|
3088
|
+
tenants: this.document.tenants.map((tenant) => tenant.tenantId === tenantId ? {
|
|
3089
|
+
...tenant,
|
|
3090
|
+
lifecycle: "archived"
|
|
3091
|
+
} : tenant)
|
|
3092
|
+
};
|
|
3093
|
+
this.persist(true);
|
|
3094
|
+
}
|
|
3095
|
+
commitActive(tenantId) {
|
|
3096
|
+
const target = this.document.tenants.find((tenant) => tenant.tenantId === tenantId);
|
|
3097
|
+
if (target === void 0 || target.lifecycle === "archived") throw new Error("awiki: target tenant is unavailable");
|
|
3098
|
+
const previous = this.document;
|
|
3099
|
+
this.document = activate(previous, tenantId, previous.generation + 1);
|
|
3100
|
+
try {
|
|
3101
|
+
this.persist(true);
|
|
3102
|
+
} catch (error) {
|
|
3103
|
+
this.document = previous;
|
|
3104
|
+
throw error;
|
|
2154
3105
|
}
|
|
3106
|
+
return cloneDocument(this.document);
|
|
2155
3107
|
}
|
|
2156
|
-
|
|
2157
|
-
if (this.
|
|
2158
|
-
this.
|
|
2159
|
-
if (this.
|
|
2160
|
-
|
|
2161
|
-
this.
|
|
2162
|
-
|
|
2163
|
-
|
|
3108
|
+
persist(backup) {
|
|
3109
|
+
if (!this.persistent) throw new Error("awiki: tenant registry is read-only until its migration diagnostic is resolved");
|
|
3110
|
+
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
3111
|
+
if (backup && existsSync(this.filePath) && !existsSync(this.backupPath)) copyFileSync(this.filePath, this.backupPath);
|
|
3112
|
+
const temporary = `${this.filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
3113
|
+
writeFileSync(temporary, `${JSON.stringify(this.document, null, 2)}\n`, {
|
|
3114
|
+
encoding: "utf8",
|
|
3115
|
+
mode: 384
|
|
3116
|
+
});
|
|
3117
|
+
renameSync(temporary, this.filePath);
|
|
2164
3118
|
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
3119
|
+
};
|
|
3120
|
+
function freshDocument() {
|
|
3121
|
+
const tenants = OFFICIALS.map(officialProfile);
|
|
3122
|
+
const activeTenantId = AWIKI_BUILTIN_TENANT_CONFIG.defaultSlot === "primary" ? AWIKI_PRIMARY_TENANT_ID : AWIKI_SECONDARY_TENANT_ID;
|
|
3123
|
+
return activate({
|
|
3124
|
+
schemaVersion: 1,
|
|
3125
|
+
officialCatalogVersion: 2,
|
|
3126
|
+
generation: 0,
|
|
3127
|
+
activeTenantId,
|
|
3128
|
+
tenants
|
|
3129
|
+
}, activeTenantId, 0);
|
|
3130
|
+
}
|
|
3131
|
+
function legacyDocument(seed, usePreRegistryDefault) {
|
|
3132
|
+
const selectedSlot = seed.legacyTenantSlot;
|
|
3133
|
+
const selectedEntry = selectedSlot === void 0 ? void 0 : OFFICIALS.find((candidate) => candidate.slot === selectedSlot);
|
|
3134
|
+
const historicalSeed = usePreRegistryDefault && selectedEntry === void 0 ? {
|
|
3135
|
+
...seed,
|
|
3136
|
+
domain: PRE_REGISTRY_DEFAULT_DOMAIN,
|
|
3137
|
+
...PRE_REGISTRY_DEFAULT_ENDPOINTS
|
|
3138
|
+
} : seed;
|
|
3139
|
+
const domain = selectedEntry?.didHost ?? normalizeAwikiDomain(historicalSeed.domain);
|
|
3140
|
+
const entry = selectedEntry ?? OFFICIALS.find((candidate) => candidate.didHost === domain);
|
|
3141
|
+
const endpoints = selectedEntry === void 0 ? {
|
|
3142
|
+
userServiceUrl: historicalSeed.userServiceUrl,
|
|
3143
|
+
messageServiceUrl: historicalSeed.messageServiceUrl,
|
|
3144
|
+
mailServiceUrl: historicalSeed.mailServiceUrl,
|
|
3145
|
+
messageServicePublicUrl: historicalSeed.messageServicePublicUrl,
|
|
3146
|
+
messageServiceDid: historicalSeed.messageServiceDid
|
|
3147
|
+
} : endpointsForOrigin(selectedEntry.backendOrigin, selectedEntry.didHost);
|
|
3148
|
+
const tenant = entry === void 0 ? {
|
|
3149
|
+
tenantId: randomUUID(),
|
|
3150
|
+
storageScopeId: randomUUID(),
|
|
3151
|
+
kind: "custom",
|
|
3152
|
+
displayName: domain,
|
|
3153
|
+
backendBaseUrl: historicalSeed.userServiceUrl,
|
|
3154
|
+
didHost: domain,
|
|
3155
|
+
lifecycle: "active",
|
|
3156
|
+
storageLayout: "legacy-base",
|
|
3157
|
+
endpoints
|
|
3158
|
+
} : {
|
|
3159
|
+
...officialProfile(entry),
|
|
3160
|
+
storageScopeId: randomUUID(),
|
|
3161
|
+
lifecycle: "active",
|
|
3162
|
+
storageLayout: "legacy-base",
|
|
3163
|
+
endpoints
|
|
3164
|
+
};
|
|
3165
|
+
return reconcileOfficials({
|
|
3166
|
+
schemaVersion: 1,
|
|
3167
|
+
officialCatalogVersion: 2,
|
|
3168
|
+
generation: 0,
|
|
3169
|
+
activeTenantId: tenant.tenantId,
|
|
3170
|
+
tenants: [tenant]
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
//#endregion
|
|
3174
|
+
//#region lib/types/package-version.generated.js
|
|
3175
|
+
/** Generated from package.json by scripts/sync-package-versions.mjs. */
|
|
3176
|
+
const DSH_AWIKI_PACKAGE_VERSION = "0.3.10";
|
|
3177
|
+
const DSH_AWIKI_MODEL_PROXY_PACKAGE_VERSION = "0.1.6";
|
|
3178
|
+
//#endregion
|
|
3179
|
+
//#region lib/types/update-policy.js
|
|
3180
|
+
/** Tenant-scoped DSH AWiki plugin update policy and verified cache. */
|
|
3181
|
+
const DSH_AWIKI_VERSION = DSH_AWIKI_PACKAGE_VERSION;
|
|
3182
|
+
const DSH_AWIKI_MODEL_PROXY_VERSION = DSH_AWIKI_MODEL_PROXY_PACKAGE_VERSION;
|
|
3183
|
+
const PRODUCT = "dsh-awiki";
|
|
3184
|
+
const CHANNEL = "stable";
|
|
3185
|
+
const MAX_POLICY_BYTES = 1048576;
|
|
3186
|
+
/** Load only this tenant's verified cache before its business runtime starts. */
|
|
3187
|
+
function readAwikiUpdatePolicyStatus(options) {
|
|
3188
|
+
const origin = new URL(options.tenant.backendBaseUrl).origin;
|
|
3189
|
+
const base = {
|
|
3190
|
+
tenantId: options.tenant.tenantId,
|
|
3191
|
+
policyOrigin: origin,
|
|
3192
|
+
tenantGeneration: options.generation,
|
|
3193
|
+
currentPluginVersion: options.currentPluginVersion ?? DSH_AWIKI_VERSION,
|
|
3194
|
+
...options.currentModelProxyVersion === void 0 ? {} : { currentModelProxyVersion: options.currentModelProxyVersion }
|
|
3195
|
+
};
|
|
3196
|
+
const cached = readCache(policyCachePath(options.stateRoot, options.tenant.tenantId, origin), origin);
|
|
3197
|
+
return cached === void 0 ? {
|
|
3198
|
+
...base,
|
|
3199
|
+
checkState: "unchecked",
|
|
3200
|
+
offline: false,
|
|
3201
|
+
usedCache: false,
|
|
3202
|
+
policyUnavailable: true,
|
|
3203
|
+
restricted: false,
|
|
3204
|
+
modelProxyRestricted: false
|
|
3205
|
+
} : statusFromPolicy(base, cached.policy, cached.checkedAt, false, true);
|
|
3206
|
+
}
|
|
3207
|
+
async function checkAwikiUpdatePolicy(options) {
|
|
3208
|
+
const origin = new URL(options.tenant.backendBaseUrl).origin;
|
|
3209
|
+
assertPolicyOrigin(origin, options.allowInsecureLoopback === true);
|
|
3210
|
+
const cachePath = policyCachePath(options.stateRoot, options.tenant.tenantId, origin);
|
|
3211
|
+
const cached = readCache(cachePath, origin);
|
|
3212
|
+
const currentPluginVersion = options.currentPluginVersion ?? DSH_AWIKI_VERSION;
|
|
3213
|
+
const base = {
|
|
3214
|
+
tenantId: options.tenant.tenantId,
|
|
3215
|
+
policyOrigin: origin,
|
|
3216
|
+
tenantGeneration: options.generation,
|
|
3217
|
+
currentPluginVersion,
|
|
3218
|
+
...options.currentModelProxyVersion === void 0 ? {} : { currentModelProxyVersion: options.currentModelProxyVersion }
|
|
3219
|
+
};
|
|
3220
|
+
const timeout = AbortSignal.timeout(options.timeoutMs ?? 15e3);
|
|
3221
|
+
const signal = options.signal === void 0 ? timeout : AbortSignal.any([options.signal, timeout]);
|
|
3222
|
+
const bounded = async (operation) => {
|
|
3223
|
+
signal.throwIfAborted();
|
|
3224
|
+
let abort;
|
|
3225
|
+
const cancelled = new Promise((_, reject) => {
|
|
3226
|
+
abort = () => {
|
|
3227
|
+
reject(signal.reason);
|
|
2173
3228
|
};
|
|
2174
|
-
|
|
2175
|
-
this.retryTimer = timer;
|
|
2176
|
-
this.resolveRetry = finish;
|
|
3229
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
2177
3230
|
});
|
|
3231
|
+
try {
|
|
3232
|
+
return await Promise.race([operation, cancelled]);
|
|
3233
|
+
} finally {
|
|
3234
|
+
signal.removeEventListener("abort", abort);
|
|
3235
|
+
}
|
|
3236
|
+
};
|
|
3237
|
+
try {
|
|
3238
|
+
signal.throwIfAborted();
|
|
3239
|
+
const endpoint = new URL("/user-service/v1/server-info", origin);
|
|
3240
|
+
endpoint.searchParams.set("client_platform", "dsh");
|
|
3241
|
+
const response = await bounded((options.fetcher ?? fetch)(endpoint, {
|
|
3242
|
+
method: "GET",
|
|
3243
|
+
headers: {
|
|
3244
|
+
accept: "application/json",
|
|
3245
|
+
"cache-control": "no-store"
|
|
3246
|
+
},
|
|
3247
|
+
cache: "no-store",
|
|
3248
|
+
redirect: "error",
|
|
3249
|
+
signal
|
|
3250
|
+
}));
|
|
3251
|
+
signal.throwIfAborted();
|
|
3252
|
+
if (response.status === 404 && options.tenant.kind === "custom") {
|
|
3253
|
+
rmSync(cachePath, { force: true });
|
|
3254
|
+
return {
|
|
3255
|
+
...base,
|
|
3256
|
+
offline: false,
|
|
3257
|
+
usedCache: false,
|
|
3258
|
+
policyUnavailable: true,
|
|
3259
|
+
checkState: "unavailable",
|
|
3260
|
+
restricted: false,
|
|
3261
|
+
modelProxyRestricted: false
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
if (response.redirected || response.url !== "" && new URL(response.url).origin !== origin) throw new Error("update policy response crossed its tenant origin");
|
|
3265
|
+
if (!response.ok) throw new Error(`policy status ${response.status}`);
|
|
3266
|
+
const bytes = await bounded(readPolicyBody(response));
|
|
3267
|
+
signal.throwIfAborted();
|
|
3268
|
+
if (bytes.byteLength > MAX_POLICY_BYTES) throw new Error("policy response exceeds 1 MiB");
|
|
3269
|
+
const policy = decodeServerInfoPolicy(JSON.parse(Buffer.from(bytes).toString("utf8")), origin, cached?.policy.policy_revision);
|
|
3270
|
+
if (policy === void 0) {
|
|
3271
|
+
rmSync(cachePath, { force: true });
|
|
3272
|
+
return {
|
|
3273
|
+
...base,
|
|
3274
|
+
offline: false,
|
|
3275
|
+
usedCache: false,
|
|
3276
|
+
policyUnavailable: true,
|
|
3277
|
+
checkState: "unavailable",
|
|
3278
|
+
restricted: false,
|
|
3279
|
+
modelProxyRestricted: false,
|
|
3280
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
if (cached !== void 0 && policy.policy_revision < cached.policy.policy_revision) throw new Error("policy revision moved backwards");
|
|
3284
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3285
|
+
try {
|
|
3286
|
+
writeCache(cachePath, {
|
|
3287
|
+
checkedAt,
|
|
3288
|
+
policy
|
|
3289
|
+
});
|
|
3290
|
+
} catch {}
|
|
3291
|
+
return statusFromPolicy(base, policy, checkedAt, false, false);
|
|
3292
|
+
} catch (error) {
|
|
3293
|
+
if (options.signal?.aborted === true) throw error;
|
|
3294
|
+
if (cached !== void 0) return {
|
|
3295
|
+
...statusFromPolicy(base, cached.policy, cached.checkedAt, true, true),
|
|
3296
|
+
checkState: "failed"
|
|
3297
|
+
};
|
|
3298
|
+
return {
|
|
3299
|
+
...base,
|
|
3300
|
+
offline: true,
|
|
3301
|
+
usedCache: false,
|
|
3302
|
+
policyUnavailable: true,
|
|
3303
|
+
checkState: "failed",
|
|
3304
|
+
restricted: false,
|
|
3305
|
+
modelProxyRestricted: false
|
|
3306
|
+
};
|
|
2178
3307
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
3308
|
+
}
|
|
3309
|
+
async function readPolicyBody(response) {
|
|
3310
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
3311
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_POLICY_BYTES) throw new Error("policy response exceeds 1 MiB");
|
|
3312
|
+
if (response.body === null) throw new Error("empty policy response");
|
|
3313
|
+
const reader = response.body.getReader();
|
|
3314
|
+
const chunks = [];
|
|
3315
|
+
let length = 0;
|
|
3316
|
+
try {
|
|
3317
|
+
while (true) {
|
|
3318
|
+
const { value, done } = await reader.read();
|
|
3319
|
+
if (done) break;
|
|
3320
|
+
length += value.byteLength;
|
|
3321
|
+
if (length > MAX_POLICY_BYTES) {
|
|
3322
|
+
await reader.cancel();
|
|
3323
|
+
throw new Error("policy response exceeds 1 MiB");
|
|
3324
|
+
}
|
|
3325
|
+
chunks.push(value);
|
|
3326
|
+
}
|
|
3327
|
+
} finally {
|
|
3328
|
+
reader.releaseLock();
|
|
2183
3329
|
}
|
|
2184
|
-
|
|
3330
|
+
return Buffer.concat(chunks, length);
|
|
3331
|
+
}
|
|
3332
|
+
function statusFromPolicy(base, policy, checkedAt, offline, usedCache) {
|
|
3333
|
+
const plugin = policy.packages.plugin;
|
|
3334
|
+
const modelProxy = policy.packages.model_proxy;
|
|
3335
|
+
const updateAvailable = compareVersions(base.currentPluginVersion, plugin.recommended_version) < 0 || modelProxy !== void 0 && base.currentModelProxyVersion !== void 0 && compareVersions(base.currentModelProxyVersion, modelProxy.recommended_version) < 0;
|
|
3336
|
+
const command = upgradeCommand(base, plugin, modelProxy);
|
|
3337
|
+
return {
|
|
3338
|
+
...base,
|
|
3339
|
+
checkState: "ready",
|
|
3340
|
+
updateAvailable,
|
|
3341
|
+
...command === void 0 ? {} : { upgradeCommand: command },
|
|
3342
|
+
policyRevision: policy.policy_revision,
|
|
3343
|
+
recommendedPluginVersion: plugin.recommended_version,
|
|
3344
|
+
minimumPluginVersion: plugin.min_supported_version,
|
|
3345
|
+
...modelProxy === void 0 ? {} : {
|
|
3346
|
+
recommendedModelProxyVersion: modelProxy.recommended_version,
|
|
3347
|
+
minimumModelProxyVersion: modelProxy.min_supported_version
|
|
3348
|
+
},
|
|
3349
|
+
releaseNotesUrl: policy.release_notes_url,
|
|
3350
|
+
...plugin.installable === false ? {} : { pluginTarget: publicTarget(plugin) },
|
|
3351
|
+
...modelProxy === void 0 || modelProxy.installable === false ? {} : { modelProxyTarget: publicTarget(modelProxy) },
|
|
3352
|
+
offline,
|
|
3353
|
+
usedCache,
|
|
3354
|
+
policyUnavailable: false,
|
|
3355
|
+
restricted: compareVersions(base.currentPluginVersion, plugin.min_supported_version) < 0,
|
|
3356
|
+
modelProxyRestricted: modelProxy !== void 0 && base.currentModelProxyVersion !== void 0 && compareVersions(base.currentModelProxyVersion, modelProxy.min_supported_version) < 0,
|
|
3357
|
+
checkedAt
|
|
3358
|
+
};
|
|
3359
|
+
}
|
|
3360
|
+
/** Exact published targets only; never downgrade a newer installed component. */
|
|
3361
|
+
function upgradeCommand(current, plugin, proxy) {
|
|
3362
|
+
const pluginUpgrade = compareVersions(current.currentPluginVersion, plugin.recommended_version) < 0;
|
|
3363
|
+
const proxyUpgrade = proxy !== void 0 && current.currentModelProxyVersion !== void 0 && compareVersions(current.currentModelProxyVersion, proxy.recommended_version) < 0;
|
|
3364
|
+
if (!pluginUpgrade && !proxyUpgrade || plugin.installable === false || current.currentModelProxyVersion !== void 0 && proxy?.installable === false) return void 0;
|
|
3365
|
+
if (current.currentModelProxyVersion !== void 0 && (proxy === void 0 || compareVersions(current.currentModelProxyVersion, proxy.recommended_version) > 0)) return void 0;
|
|
3366
|
+
const pluginVersion = pluginUpgrade ? plugin.recommended_version : current.currentPluginVersion;
|
|
3367
|
+
if (current.currentModelProxyVersion !== void 0 && proxy?.requires_plugin !== void 0 && !satisfies(pluginVersion, proxy.requires_plugin, { includePrerelease: true })) return void 0;
|
|
3368
|
+
return `dsh plugin add ${[pluginUpgrade ? `@awiki/dsh-plugin@${pluginVersion}` : void 0, proxyUpgrade ? `@awiki/dsh-model-proxy@${proxy.recommended_version}` : void 0].filter(Boolean).join(" ")}`;
|
|
3369
|
+
}
|
|
3370
|
+
function publicTarget(value) {
|
|
3371
|
+
return {
|
|
3372
|
+
name: value.name,
|
|
3373
|
+
recommendedVersion: value.recommended_version,
|
|
3374
|
+
minimumVersion: value.min_supported_version,
|
|
3375
|
+
integrity: value.integrity,
|
|
3376
|
+
...value.repository === void 0 ? {} : { repository: value.repository },
|
|
3377
|
+
...value.requires_plugin === void 0 ? {} : { requiresPlugin: value.requires_plugin }
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
function decodePolicy(value, origin) {
|
|
3381
|
+
if (!isRecord(value) || value.product !== PRODUCT || value.channel !== CHANNEL || value.policy_origin !== origin || !Number.isSafeInteger(value.policy_revision) || value.policy_revision < 1 || typeof value.published_at !== "string" || Number.isNaN(Date.parse(value.published_at)) || typeof value.release_notes_url !== "string" || !isRecord(value.packages)) throw new Error("invalid update policy");
|
|
3382
|
+
const releaseNotes = value.release_notes_url === "" ? void 0 : new URL(value.release_notes_url);
|
|
3383
|
+
if (releaseNotes !== void 0) assertPolicyOrigin(releaseNotes.origin, origin.startsWith("http://"));
|
|
3384
|
+
const plugin = decodePackage(value.packages.plugin, "@awiki/dsh-plugin");
|
|
3385
|
+
const modelProxy = value.packages.model_proxy === void 0 ? void 0 : decodePackage(value.packages.model_proxy, "@awiki/dsh-model-proxy");
|
|
3386
|
+
if (compareVersions(plugin.min_supported_version, plugin.recommended_version) > 0 || modelProxy !== void 0 && compareVersions(modelProxy.min_supported_version, modelProxy.recommended_version) > 0) throw new Error("minimum version exceeds recommended version");
|
|
3387
|
+
return {
|
|
3388
|
+
product: PRODUCT,
|
|
3389
|
+
channel: CHANNEL,
|
|
3390
|
+
policy_origin: origin,
|
|
3391
|
+
policy_revision: value.policy_revision,
|
|
3392
|
+
published_at: value.published_at,
|
|
3393
|
+
release_notes_url: releaseNotes?.toString() ?? "",
|
|
3394
|
+
packages: {
|
|
3395
|
+
plugin,
|
|
3396
|
+
...modelProxy === void 0 ? {} : { model_proxy: modelProxy }
|
|
3397
|
+
}
|
|
3398
|
+
};
|
|
3399
|
+
}
|
|
3400
|
+
function decodeServerInfoPolicy(value, origin, minimumRevision = 0) {
|
|
3401
|
+
if (!isRecord(value) || value.schema_version !== 1) throw new Error("invalid server-info");
|
|
3402
|
+
const releases = value.client_versions;
|
|
3403
|
+
if (!isRecord(releases) || releases.schema_version !== 1 || releases.channel !== CHANNEL || releases.policy_origin !== origin || !Number.isSafeInteger(releases.policy_revision) || releases.policy_revision < 1 || typeof releases.published_at !== "string" || Number.isNaN(Date.parse(releases.published_at)) || !isRecord(releases.products) || !isRecord(releases.products.dsh)) throw new Error("invalid client version policy");
|
|
3404
|
+
if (releases.policy_revision < minimumRevision) throw new Error("policy revision moved backwards");
|
|
3405
|
+
const product = releases.products.dsh;
|
|
3406
|
+
if (product.enabled === false) {
|
|
3407
|
+
if (product.compatibility === void 0 || product.compatibility === null) return void 0;
|
|
3408
|
+
if (!isRecord(product.compatibility)) throw new Error("invalid DSH compatibility");
|
|
3409
|
+
const component = (value, name) => {
|
|
3410
|
+
if (!isRecord(value)) throw new Error("invalid DSH component compatibility");
|
|
3411
|
+
return {
|
|
3412
|
+
name,
|
|
3413
|
+
recommended_version: value.recommended_version,
|
|
3414
|
+
min_supported_version: value.minimum_supported_version,
|
|
3415
|
+
installable: false
|
|
3416
|
+
};
|
|
3417
|
+
};
|
|
3418
|
+
return decodePolicy({
|
|
3419
|
+
product: PRODUCT,
|
|
3420
|
+
channel: CHANNEL,
|
|
3421
|
+
policy_origin: origin,
|
|
3422
|
+
policy_revision: releases.policy_revision,
|
|
3423
|
+
published_at: releases.published_at,
|
|
3424
|
+
release_notes_url: product.release_notes_url ?? "",
|
|
3425
|
+
packages: {
|
|
3426
|
+
plugin: component(product.compatibility.plugin, "@awiki/dsh-plugin"),
|
|
3427
|
+
...product.compatibility.model_proxy == null ? {} : { model_proxy: component(product.compatibility.model_proxy, "@awiki/dsh-model-proxy") }
|
|
3428
|
+
}
|
|
3429
|
+
}, origin);
|
|
3430
|
+
}
|
|
3431
|
+
if (product.enabled !== true || typeof product.release_notes_url !== "string" || !isRecord(product.plugin)) throw new Error("invalid DSH client version policy");
|
|
3432
|
+
const plugin = decodeServerPackage(product.plugin, "@awiki/dsh-plugin");
|
|
3433
|
+
const modelProxy = isRecord(product.model_proxy) && product.model_proxy.enabled === true ? decodeServerPackage(product.model_proxy, "@awiki/dsh-model-proxy") : void 0;
|
|
3434
|
+
return decodePolicy({
|
|
3435
|
+
product: PRODUCT,
|
|
3436
|
+
channel: CHANNEL,
|
|
3437
|
+
policy_origin: releases.policy_origin,
|
|
3438
|
+
policy_revision: releases.policy_revision,
|
|
3439
|
+
published_at: releases.published_at,
|
|
3440
|
+
release_notes_url: product.release_notes_url,
|
|
3441
|
+
packages: {
|
|
3442
|
+
plugin,
|
|
3443
|
+
...modelProxy === void 0 ? {} : { model_proxy: modelProxy }
|
|
3444
|
+
}
|
|
3445
|
+
}, origin);
|
|
3446
|
+
}
|
|
3447
|
+
function decodeServerPackage(value, expectedName) {
|
|
3448
|
+
return decodePackage({
|
|
3449
|
+
name: value.package_name,
|
|
3450
|
+
recommended_version: value.recommended_version,
|
|
3451
|
+
min_supported_version: value.minimum_supported_version,
|
|
3452
|
+
integrity: value.integrity,
|
|
3453
|
+
repository: value.repository === null ? void 0 : value.repository,
|
|
3454
|
+
requires_plugin: value.requires_plugin === null ? void 0 : value.requires_plugin
|
|
3455
|
+
}, expectedName);
|
|
3456
|
+
}
|
|
3457
|
+
function decodePackage(value, expectedName) {
|
|
3458
|
+
if (!isRecord(value) || value.name !== expectedName || typeof value.recommended_version !== "string" || typeof value.min_supported_version !== "string" || value.installable !== false && (typeof value.integrity !== "string" || !/^sha512-[A-Za-z0-9+/]+={0,2}$/u.test(value.integrity)) || value.repository !== void 0 && typeof value.repository !== "string" || value.requires_plugin !== void 0 && typeof value.requires_plugin !== "string") throw new Error("invalid update package target");
|
|
3459
|
+
assertVersion(value.recommended_version);
|
|
3460
|
+
assertVersion(value.min_supported_version);
|
|
3461
|
+
return {
|
|
3462
|
+
name: value.name,
|
|
3463
|
+
recommended_version: value.recommended_version,
|
|
3464
|
+
min_supported_version: value.min_supported_version,
|
|
3465
|
+
...value.installable === false ? { installable: false } : { integrity: value.integrity },
|
|
3466
|
+
...value.repository === void 0 ? {} : { repository: value.repository },
|
|
3467
|
+
...value.requires_plugin === void 0 ? {} : { requires_plugin: value.requires_plugin }
|
|
3468
|
+
};
|
|
3469
|
+
}
|
|
3470
|
+
function policyCachePath(stateRoot, tenantId, origin) {
|
|
3471
|
+
const key = createHash("sha256").update(`${tenantId}\n${origin}\n${PRODUCT}\n${CHANNEL}`, "utf8").digest("hex");
|
|
3472
|
+
return join(stateRoot, "update-policy", `${key}.json`);
|
|
3473
|
+
}
|
|
3474
|
+
function readCache(path, origin) {
|
|
3475
|
+
try {
|
|
3476
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
3477
|
+
if (!isRecord(value) || typeof value.checkedAt !== "string") return void 0;
|
|
3478
|
+
return {
|
|
3479
|
+
checkedAt: value.checkedAt,
|
|
3480
|
+
policy: decodePolicy(value.policy, origin)
|
|
3481
|
+
};
|
|
3482
|
+
} catch {
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
function writeCache(path, value) {
|
|
3487
|
+
mkdirSync(dirname(path), {
|
|
3488
|
+
recursive: true,
|
|
3489
|
+
mode: 448
|
|
3490
|
+
});
|
|
3491
|
+
const temporary = `${path}.tmp-${process.pid}`;
|
|
3492
|
+
writeFileSync(temporary, `${JSON.stringify(value, void 0, 2)}\n`, { mode: 384 });
|
|
3493
|
+
renameSync(temporary, path);
|
|
3494
|
+
}
|
|
3495
|
+
function assertPolicyOrigin(origin, allowLoopback) {
|
|
3496
|
+
const url = new URL(origin);
|
|
3497
|
+
if (url.protocol === "https:") return;
|
|
3498
|
+
if (allowLoopback && url.protocol === "http:" && [
|
|
3499
|
+
"localhost",
|
|
3500
|
+
"127.0.0.1",
|
|
3501
|
+
"[::1]"
|
|
3502
|
+
].includes(url.hostname)) return;
|
|
3503
|
+
throw new Error("update policy origin must use HTTPS");
|
|
3504
|
+
}
|
|
3505
|
+
function isRecord(value) {
|
|
3506
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3507
|
+
}
|
|
2185
3508
|
//#endregion
|
|
2186
3509
|
//#region lib/types/index.js
|
|
2187
|
-
/** Unified AWiki identity, messaging, attachment, Remote, and model-tool service. */
|
|
2188
3510
|
var __runInitializers = function(thisArg, initializers, value) {
|
|
2189
3511
|
var useValue = arguments.length > 2;
|
|
2190
3512
|
for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
@@ -2231,10 +3553,10 @@ const DEFAULT_ATTACHMENT_MAX_BYTES = 10485760;
|
|
|
2231
3553
|
const DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES = 67108864;
|
|
2232
3554
|
/** Default browser polling interval while the AWiki drawer is open. */
|
|
2233
3555
|
const DEFAULT_POLL_INTERVAL_MS = 3e3;
|
|
2234
|
-
/**
|
|
2235
|
-
const DEFAULT_AWIKI_SERVICE_URL =
|
|
2236
|
-
/**
|
|
2237
|
-
const DEFAULT_AWIKI_MESSAGE_SERVICE_DID =
|
|
3556
|
+
/** Package-configured primary AWiki service origin. */
|
|
3557
|
+
const DEFAULT_AWIKI_SERVICE_URL = AWIKI_DEFAULT_BUILTIN_TENANT.backendOrigin;
|
|
3558
|
+
/** Package-configured primary authoritative AWiki message-service DID. */
|
|
3559
|
+
const DEFAULT_AWIKI_MESSAGE_SERVICE_DID = `did:wba:${AWIKI_DEFAULT_BUILTIN_TENANT.didHost}`;
|
|
2238
3560
|
/** Host-owned model input cap after message minimization. */
|
|
2239
3561
|
const DEFAULT_SUMMARY_MAX_INPUT_BYTES = 32768;
|
|
2240
3562
|
/** Hard limit for one user-triggered conversation summary. */
|
|
@@ -2243,6 +3565,7 @@ const MAX_SUMMARY_MESSAGES = 50;
|
|
|
2243
3565
|
const Config = z.object({
|
|
2244
3566
|
userServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
2245
3567
|
userServiceDomain: z.string().default(DEFAULT_AWIKI_DOMAIN),
|
|
3568
|
+
legacyTenantSlot: z.string(),
|
|
2246
3569
|
messageServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
2247
3570
|
mailServiceUrl: z.string(),
|
|
2248
3571
|
messageServicePublicUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
@@ -2260,6 +3583,7 @@ const Config = z.object({
|
|
|
2260
3583
|
summaryMaxInputBytes: z.number().default(DEFAULT_SUMMARY_MAX_INPUT_BYTES)
|
|
2261
3584
|
});
|
|
2262
3585
|
const FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
3586
|
+
"client-version-unsupported",
|
|
2263
3587
|
"not-registered",
|
|
2264
3588
|
"signed-out",
|
|
2265
3589
|
"already-registered",
|
|
@@ -2269,6 +3593,7 @@ const FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
|
2269
3593
|
"handle-unavailable",
|
|
2270
3594
|
"not-found",
|
|
2271
3595
|
"forbidden",
|
|
3596
|
+
"device-rejoin-required",
|
|
2272
3597
|
"identity-recovery-required",
|
|
2273
3598
|
"conflict",
|
|
2274
3599
|
"rate-limited",
|
|
@@ -2292,10 +3617,10 @@ const RESUMABLE_JOIN_PHASES = /* @__PURE__ */ new Set([
|
|
|
2292
3617
|
"challenge_prepared",
|
|
2293
3618
|
"response_prepared",
|
|
2294
3619
|
"response_verified",
|
|
2295
|
-
"approval_prepared"
|
|
2296
|
-
"authorized"
|
|
3620
|
+
"approval_prepared"
|
|
2297
3621
|
]);
|
|
2298
3622
|
const FAILURE_MESSAGES = {
|
|
3623
|
+
"client-version-unsupported": "This AWiki plugin version is not supported by the active tenant. Update the plugin or switch tenants.",
|
|
2299
3624
|
"not-registered": "No AWiki identity is registered for this deployment.",
|
|
2300
3625
|
"signed-out": "This installation is signed out of AWiki.",
|
|
2301
3626
|
"already-registered": "This deployment already has an AWiki identity.",
|
|
@@ -2305,6 +3630,7 @@ const FAILURE_MESSAGES = {
|
|
|
2305
3630
|
"handle-unavailable": "The requested AWiki handle is unavailable.",
|
|
2306
3631
|
"not-found": "The requested AWiki resource was not found.",
|
|
2307
3632
|
"forbidden": "The AWiki operation is not permitted.",
|
|
3633
|
+
"device-rejoin-required": "This device credential was revoked and must join the Handle again.",
|
|
2308
3634
|
"identity-recovery-required": "The local AWiki identity must be recovered before it can be used again.",
|
|
2309
3635
|
"conflict": "The AWiki operation conflicts with current state.",
|
|
2310
3636
|
"rate-limited": "The AWiki service rate-limited the request.",
|
|
@@ -2368,29 +3694,19 @@ function serviceUrl(field, raw, allowInsecureLoopbackForTesting) {
|
|
|
2368
3694
|
if (url.username !== "" || url.password !== "" || url.hash !== "") throw new TypeError(`awiki: ${field} must not contain credentials or a URL fragment`);
|
|
2369
3695
|
return raw;
|
|
2370
3696
|
}
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
const
|
|
2376
|
-
if (
|
|
2377
|
-
return new URL("/api/identity-recovery", parsed).toString();
|
|
2378
|
-
}
|
|
2379
|
-
/** Accept only the closed Model Proxy success response; no ledger identifier may cross back. */
|
|
2380
|
-
async function acceptsRecoveryReconciliation(response) {
|
|
2381
|
-
if (!response.ok) return false;
|
|
2382
|
-
let value;
|
|
3697
|
+
function publishedServiceBaseUrl(services, name, allowInsecureLoopbackForTesting) {
|
|
3698
|
+
if (typeof services !== "object" || services === null || Array.isArray(services)) return void 0;
|
|
3699
|
+
const candidate = services[name];
|
|
3700
|
+
if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) return void 0;
|
|
3701
|
+
const value = candidate;
|
|
3702
|
+
if (value.enabled !== true || typeof value.base_url !== "string") return void 0;
|
|
2383
3703
|
try {
|
|
2384
|
-
const
|
|
2385
|
-
if (
|
|
2386
|
-
|
|
3704
|
+
const baseUrl = serviceUrl(`server-info.services.${name}.base_url`, value.base_url, allowInsecureLoopbackForTesting);
|
|
3705
|
+
if (new URL(baseUrl).search !== "") return void 0;
|
|
3706
|
+
return baseUrl;
|
|
2387
3707
|
} catch {
|
|
2388
|
-
return
|
|
3708
|
+
return;
|
|
2389
3709
|
}
|
|
2390
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
2391
|
-
const result = value;
|
|
2392
|
-
const keys = Object.keys(result).sort();
|
|
2393
|
-
return keys.length === 2 && keys[0] === "idempotent" && keys[1] === "restored" && result.restored === true && typeof result.idempotent === "boolean";
|
|
2394
3710
|
}
|
|
2395
3711
|
/** Validate a provider domain without inferring it from an API endpoint. */
|
|
2396
3712
|
function serviceDomain(raw, field = "userServiceDomain") {
|
|
@@ -2425,12 +3741,12 @@ function listenerAllowedPeers(raw, enabled) {
|
|
|
2425
3741
|
return peers;
|
|
2426
3742
|
}
|
|
2427
3743
|
/** Resolve and validate every deployment choice before publishing the service. */
|
|
2428
|
-
function resolveConfig(config) {
|
|
3744
|
+
function resolveConfig(ctx, config) {
|
|
2429
3745
|
const allowInsecureLoopbackForTesting = config.allowInsecureLoopbackForTesting ?? false;
|
|
2430
3746
|
const configuredStateRoot = config.stateRoot?.trim();
|
|
2431
3747
|
const configuredDshHome = process.env.DSH_HOME?.trim();
|
|
2432
3748
|
const dshHome = configuredDshHome === void 0 || configuredDshHome.length === 0 ? join(homedir(), ".dsh") : configuredDshHome;
|
|
2433
|
-
const stateRoot = configuredStateRoot === void 0 || configuredStateRoot.length === 0 ?
|
|
3749
|
+
const stateRoot = configuredStateRoot === void 0 || configuredStateRoot.length === 0 ? resolveAwikiStateRoot(ctx, dshHome) : configuredStateRoot;
|
|
2434
3750
|
if (!isAbsolute(stateRoot)) throw new TypeError("awiki: stateRoot must be an absolute path");
|
|
2435
3751
|
const attachmentMaxBytes = config.attachmentMaxBytes ?? 10485760;
|
|
2436
3752
|
if (!Number.isSafeInteger(attachmentMaxBytes) || attachmentMaxBytes < 1) throw new TypeError("awiki: attachmentMaxBytes must be a positive safe integer");
|
|
@@ -2446,17 +3762,20 @@ function resolveConfig(config) {
|
|
|
2446
3762
|
const allowedPeers = listenerAllowedPeers(config.listenerAllowedPeers, listenerEnabled);
|
|
2447
3763
|
const summaryMaxInputBytes = config.summaryMaxInputBytes ?? 32768;
|
|
2448
3764
|
if (!Number.isSafeInteger(summaryMaxInputBytes) || summaryMaxInputBytes < 1024) throw new TypeError("awiki: summaryMaxInputBytes must be a safe integer of at least 1024");
|
|
2449
|
-
const userServiceUrl = serviceUrl("userServiceUrl", config.userServiceUrl ??
|
|
2450
|
-
const messageServiceUrl = serviceUrl("messageServiceUrl", config.messageServiceUrl ??
|
|
3765
|
+
const userServiceUrl = serviceUrl("userServiceUrl", config.userServiceUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
3766
|
+
const messageServiceUrl = serviceUrl("messageServiceUrl", config.messageServiceUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
2451
3767
|
const mailServiceUrl = serviceUrl("mailServiceUrl", config.mailServiceUrl ?? userServiceUrl, allowInsecureLoopbackForTesting);
|
|
2452
|
-
const messageServicePublicUrl = serviceUrl("messageServicePublicUrl", config.messageServicePublicUrl ??
|
|
3768
|
+
const messageServicePublicUrl = serviceUrl("messageServicePublicUrl", config.messageServicePublicUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
3769
|
+
const legacyTenantSlot = config.legacyTenantSlot?.trim();
|
|
3770
|
+
if (legacyTenantSlot !== void 0 && legacyTenantSlot !== "primary" && legacyTenantSlot !== "secondary") throw new TypeError("awiki: legacyTenantSlot must be primary or secondary");
|
|
2453
3771
|
return {
|
|
2454
3772
|
userServiceUrl,
|
|
2455
|
-
userServiceDomain: serviceDomain(config.userServiceDomain ??
|
|
3773
|
+
userServiceDomain: serviceDomain(config.userServiceDomain ?? DEFAULT_AWIKI_DOMAIN),
|
|
3774
|
+
...legacyTenantSlot === void 0 ? {} : { legacyTenantSlot },
|
|
2456
3775
|
messageServiceUrl,
|
|
2457
3776
|
mailServiceUrl,
|
|
2458
3777
|
messageServicePublicUrl,
|
|
2459
|
-
messageServiceDid: serviceDid(config.messageServiceDid ??
|
|
3778
|
+
messageServiceDid: serviceDid(config.messageServiceDid ?? DEFAULT_AWIKI_MESSAGE_SERVICE_DID),
|
|
2460
3779
|
allowedAttachmentOrigins: attachmentOrigins(config.allowedAttachmentOrigins, messageServicePublicUrl, allowInsecureLoopbackForTesting),
|
|
2461
3780
|
allowInsecureLoopbackForTesting,
|
|
2462
3781
|
stateRoot,
|
|
@@ -2480,6 +3799,15 @@ function failure(code) {
|
|
|
2480
3799
|
message: FAILURE_MESSAGES[code]
|
|
2481
3800
|
};
|
|
2482
3801
|
}
|
|
3802
|
+
function integrationUnavailable() {
|
|
3803
|
+
return {
|
|
3804
|
+
ok: false,
|
|
3805
|
+
error: {
|
|
3806
|
+
code: "unavailable",
|
|
3807
|
+
message: "Guest messaging is not available for the active AWiki tenant."
|
|
3808
|
+
}
|
|
3809
|
+
};
|
|
3810
|
+
}
|
|
2483
3811
|
/** Normalize SDK and provider failures without returning remote bodies, credentials, or causes. */
|
|
2484
3812
|
function normalizeFailure(error) {
|
|
2485
3813
|
if (error instanceof ProviderUnavailableError) return {
|
|
@@ -2583,7 +3911,6 @@ function normalizeRecoveryPrepareRequest(request) {
|
|
|
2583
3911
|
}
|
|
2584
3912
|
const IDENTITY_ACCESS_RESPONSE_MAX_BYTES = 65536;
|
|
2585
3913
|
const SERVER_INFO_RESPONSE_MAX_BYTES = 65536;
|
|
2586
|
-
const RECOVERY_RECONCILIATION_RESPONSE_MAX_BYTES = 4096;
|
|
2587
3914
|
/** Read one untrusted discovery response without buffering beyond the fixed Host limit. */
|
|
2588
3915
|
async function readBoundedResponseText(response, maxBytes) {
|
|
2589
3916
|
const declaredLength = response.headers.get("content-length");
|
|
@@ -2794,6 +4121,12 @@ let AwikiService = (() => {
|
|
|
2794
4121
|
let _classSuper = TypertRemoteService;
|
|
2795
4122
|
let _instanceExtraInitializers = [];
|
|
2796
4123
|
let _getConfig_decorators;
|
|
4124
|
+
let _getIntegration_decorators;
|
|
4125
|
+
let _createIntegration_decorators;
|
|
4126
|
+
let _updateIntegration_decorators;
|
|
4127
|
+
let _rotateIntegrationId_decorators;
|
|
4128
|
+
let _closeIntegration_decorators;
|
|
4129
|
+
let _reopenIntegration_decorators;
|
|
2797
4130
|
let _getIdentity_decorators;
|
|
2798
4131
|
let _getSession_decorators;
|
|
2799
4132
|
let _logout_decorators;
|
|
@@ -2801,7 +4134,9 @@ let AwikiService = (() => {
|
|
|
2801
4134
|
let _inspectIdentityAccess_decorators;
|
|
2802
4135
|
let _sendRegistrationOtp_decorators;
|
|
2803
4136
|
let _registerIdentity_decorators;
|
|
4137
|
+
let _retireDeviceIdentityForRejoin_decorators;
|
|
2804
4138
|
let _beginDeviceJoin_decorators;
|
|
4139
|
+
let _getIdentityAccessState_decorators;
|
|
2805
4140
|
let _getDeviceJoinStatus_decorators;
|
|
2806
4141
|
let _cancelDeviceJoin_decorators;
|
|
2807
4142
|
let _refreshDeviceManagement_decorators;
|
|
@@ -2809,6 +4144,8 @@ let AwikiService = (() => {
|
|
|
2809
4144
|
let _approveDeviceJoin_decorators;
|
|
2810
4145
|
let _rejectDeviceJoin_decorators;
|
|
2811
4146
|
let _revokeDevice_decorators;
|
|
4147
|
+
let _prepareRootTransfer_decorators;
|
|
4148
|
+
let _confirmRootTransfer_decorators;
|
|
2812
4149
|
let _updateDisplayName_decorators;
|
|
2813
4150
|
let _getProfile_decorators;
|
|
2814
4151
|
let _updateProfile_decorators;
|
|
@@ -2817,12 +4154,14 @@ let AwikiService = (() => {
|
|
|
2817
4154
|
let _activateRecovery_decorators;
|
|
2818
4155
|
let _getRecoveryStatus_decorators;
|
|
2819
4156
|
let _resumeRecovery_decorators;
|
|
4157
|
+
let _enterRecoveredSession_decorators;
|
|
2820
4158
|
let _discardRecovery_decorators;
|
|
2821
4159
|
let _resolvePeer_decorators;
|
|
2822
4160
|
let _createGroup_decorators;
|
|
2823
4161
|
let _getGroup_decorators;
|
|
2824
4162
|
let _joinGroup_decorators;
|
|
2825
4163
|
let _leaveGroup_decorators;
|
|
4164
|
+
let _getDisplayProfiles_decorators;
|
|
2826
4165
|
let _listGroupMembers_decorators;
|
|
2827
4166
|
let _addGroupMember_decorators;
|
|
2828
4167
|
let _removeGroupMember_decorators;
|
|
@@ -2846,6 +4185,12 @@ let AwikiService = (() => {
|
|
|
2846
4185
|
static {
|
|
2847
4186
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
2848
4187
|
_getConfig_decorators = [Remote];
|
|
4188
|
+
_getIntegration_decorators = [Remote];
|
|
4189
|
+
_createIntegration_decorators = [Remote];
|
|
4190
|
+
_updateIntegration_decorators = [Remote];
|
|
4191
|
+
_rotateIntegrationId_decorators = [Remote];
|
|
4192
|
+
_closeIntegration_decorators = [Remote];
|
|
4193
|
+
_reopenIntegration_decorators = [Remote];
|
|
2849
4194
|
_getIdentity_decorators = [Remote];
|
|
2850
4195
|
_getSession_decorators = [Remote];
|
|
2851
4196
|
_logout_decorators = [Remote];
|
|
@@ -2853,7 +4198,9 @@ let AwikiService = (() => {
|
|
|
2853
4198
|
_inspectIdentityAccess_decorators = [Remote];
|
|
2854
4199
|
_sendRegistrationOtp_decorators = [Remote];
|
|
2855
4200
|
_registerIdentity_decorators = [Remote];
|
|
4201
|
+
_retireDeviceIdentityForRejoin_decorators = [Remote];
|
|
2856
4202
|
_beginDeviceJoin_decorators = [Remote];
|
|
4203
|
+
_getIdentityAccessState_decorators = [Remote];
|
|
2857
4204
|
_getDeviceJoinStatus_decorators = [Remote];
|
|
2858
4205
|
_cancelDeviceJoin_decorators = [Remote];
|
|
2859
4206
|
_refreshDeviceManagement_decorators = [Remote];
|
|
@@ -2861,6 +4208,8 @@ let AwikiService = (() => {
|
|
|
2861
4208
|
_approveDeviceJoin_decorators = [Remote];
|
|
2862
4209
|
_rejectDeviceJoin_decorators = [Remote];
|
|
2863
4210
|
_revokeDevice_decorators = [Remote];
|
|
4211
|
+
_prepareRootTransfer_decorators = [Remote];
|
|
4212
|
+
_confirmRootTransfer_decorators = [Remote];
|
|
2864
4213
|
_updateDisplayName_decorators = [Remote];
|
|
2865
4214
|
_getProfile_decorators = [Remote];
|
|
2866
4215
|
_updateProfile_decorators = [Remote];
|
|
@@ -2869,12 +4218,14 @@ let AwikiService = (() => {
|
|
|
2869
4218
|
_activateRecovery_decorators = [Remote];
|
|
2870
4219
|
_getRecoveryStatus_decorators = [Remote];
|
|
2871
4220
|
_resumeRecovery_decorators = [Remote];
|
|
4221
|
+
_enterRecoveredSession_decorators = [Remote];
|
|
2872
4222
|
_discardRecovery_decorators = [Remote];
|
|
2873
4223
|
_resolvePeer_decorators = [Remote];
|
|
2874
4224
|
_createGroup_decorators = [Remote];
|
|
2875
4225
|
_getGroup_decorators = [Remote];
|
|
2876
4226
|
_joinGroup_decorators = [Remote];
|
|
2877
4227
|
_leaveGroup_decorators = [Remote];
|
|
4228
|
+
_getDisplayProfiles_decorators = [Remote];
|
|
2878
4229
|
_listGroupMembers_decorators = [Remote];
|
|
2879
4230
|
_addGroupMember_decorators = [Remote];
|
|
2880
4231
|
_removeGroupMember_decorators = [Remote];
|
|
@@ -2905,6 +4256,72 @@ let AwikiService = (() => {
|
|
|
2905
4256
|
},
|
|
2906
4257
|
metadata: _metadata
|
|
2907
4258
|
}, null, _instanceExtraInitializers);
|
|
4259
|
+
__esDecorate(this, null, _getIntegration_decorators, {
|
|
4260
|
+
kind: "method",
|
|
4261
|
+
name: "getIntegration",
|
|
4262
|
+
static: false,
|
|
4263
|
+
private: false,
|
|
4264
|
+
access: {
|
|
4265
|
+
has: (obj) => "getIntegration" in obj,
|
|
4266
|
+
get: (obj) => obj.getIntegration
|
|
4267
|
+
},
|
|
4268
|
+
metadata: _metadata
|
|
4269
|
+
}, null, _instanceExtraInitializers);
|
|
4270
|
+
__esDecorate(this, null, _createIntegration_decorators, {
|
|
4271
|
+
kind: "method",
|
|
4272
|
+
name: "createIntegration",
|
|
4273
|
+
static: false,
|
|
4274
|
+
private: false,
|
|
4275
|
+
access: {
|
|
4276
|
+
has: (obj) => "createIntegration" in obj,
|
|
4277
|
+
get: (obj) => obj.createIntegration
|
|
4278
|
+
},
|
|
4279
|
+
metadata: _metadata
|
|
4280
|
+
}, null, _instanceExtraInitializers);
|
|
4281
|
+
__esDecorate(this, null, _updateIntegration_decorators, {
|
|
4282
|
+
kind: "method",
|
|
4283
|
+
name: "updateIntegration",
|
|
4284
|
+
static: false,
|
|
4285
|
+
private: false,
|
|
4286
|
+
access: {
|
|
4287
|
+
has: (obj) => "updateIntegration" in obj,
|
|
4288
|
+
get: (obj) => obj.updateIntegration
|
|
4289
|
+
},
|
|
4290
|
+
metadata: _metadata
|
|
4291
|
+
}, null, _instanceExtraInitializers);
|
|
4292
|
+
__esDecorate(this, null, _rotateIntegrationId_decorators, {
|
|
4293
|
+
kind: "method",
|
|
4294
|
+
name: "rotateIntegrationId",
|
|
4295
|
+
static: false,
|
|
4296
|
+
private: false,
|
|
4297
|
+
access: {
|
|
4298
|
+
has: (obj) => "rotateIntegrationId" in obj,
|
|
4299
|
+
get: (obj) => obj.rotateIntegrationId
|
|
4300
|
+
},
|
|
4301
|
+
metadata: _metadata
|
|
4302
|
+
}, null, _instanceExtraInitializers);
|
|
4303
|
+
__esDecorate(this, null, _closeIntegration_decorators, {
|
|
4304
|
+
kind: "method",
|
|
4305
|
+
name: "closeIntegration",
|
|
4306
|
+
static: false,
|
|
4307
|
+
private: false,
|
|
4308
|
+
access: {
|
|
4309
|
+
has: (obj) => "closeIntegration" in obj,
|
|
4310
|
+
get: (obj) => obj.closeIntegration
|
|
4311
|
+
},
|
|
4312
|
+
metadata: _metadata
|
|
4313
|
+
}, null, _instanceExtraInitializers);
|
|
4314
|
+
__esDecorate(this, null, _reopenIntegration_decorators, {
|
|
4315
|
+
kind: "method",
|
|
4316
|
+
name: "reopenIntegration",
|
|
4317
|
+
static: false,
|
|
4318
|
+
private: false,
|
|
4319
|
+
access: {
|
|
4320
|
+
has: (obj) => "reopenIntegration" in obj,
|
|
4321
|
+
get: (obj) => obj.reopenIntegration
|
|
4322
|
+
},
|
|
4323
|
+
metadata: _metadata
|
|
4324
|
+
}, null, _instanceExtraInitializers);
|
|
2908
4325
|
__esDecorate(this, null, _getIdentity_decorators, {
|
|
2909
4326
|
kind: "method",
|
|
2910
4327
|
name: "getIdentity",
|
|
@@ -2982,6 +4399,17 @@ let AwikiService = (() => {
|
|
|
2982
4399
|
},
|
|
2983
4400
|
metadata: _metadata
|
|
2984
4401
|
}, null, _instanceExtraInitializers);
|
|
4402
|
+
__esDecorate(this, null, _retireDeviceIdentityForRejoin_decorators, {
|
|
4403
|
+
kind: "method",
|
|
4404
|
+
name: "retireDeviceIdentityForRejoin",
|
|
4405
|
+
static: false,
|
|
4406
|
+
private: false,
|
|
4407
|
+
access: {
|
|
4408
|
+
has: (obj) => "retireDeviceIdentityForRejoin" in obj,
|
|
4409
|
+
get: (obj) => obj.retireDeviceIdentityForRejoin
|
|
4410
|
+
},
|
|
4411
|
+
metadata: _metadata
|
|
4412
|
+
}, null, _instanceExtraInitializers);
|
|
2985
4413
|
__esDecorate(this, null, _beginDeviceJoin_decorators, {
|
|
2986
4414
|
kind: "method",
|
|
2987
4415
|
name: "beginDeviceJoin",
|
|
@@ -2993,6 +4421,17 @@ let AwikiService = (() => {
|
|
|
2993
4421
|
},
|
|
2994
4422
|
metadata: _metadata
|
|
2995
4423
|
}, null, _instanceExtraInitializers);
|
|
4424
|
+
__esDecorate(this, null, _getIdentityAccessState_decorators, {
|
|
4425
|
+
kind: "method",
|
|
4426
|
+
name: "getIdentityAccessState",
|
|
4427
|
+
static: false,
|
|
4428
|
+
private: false,
|
|
4429
|
+
access: {
|
|
4430
|
+
has: (obj) => "getIdentityAccessState" in obj,
|
|
4431
|
+
get: (obj) => obj.getIdentityAccessState
|
|
4432
|
+
},
|
|
4433
|
+
metadata: _metadata
|
|
4434
|
+
}, null, _instanceExtraInitializers);
|
|
2996
4435
|
__esDecorate(this, null, _getDeviceJoinStatus_decorators, {
|
|
2997
4436
|
kind: "method",
|
|
2998
4437
|
name: "getDeviceJoinStatus",
|
|
@@ -3070,6 +4509,28 @@ let AwikiService = (() => {
|
|
|
3070
4509
|
},
|
|
3071
4510
|
metadata: _metadata
|
|
3072
4511
|
}, null, _instanceExtraInitializers);
|
|
4512
|
+
__esDecorate(this, null, _prepareRootTransfer_decorators, {
|
|
4513
|
+
kind: "method",
|
|
4514
|
+
name: "prepareRootTransfer",
|
|
4515
|
+
static: false,
|
|
4516
|
+
private: false,
|
|
4517
|
+
access: {
|
|
4518
|
+
has: (obj) => "prepareRootTransfer" in obj,
|
|
4519
|
+
get: (obj) => obj.prepareRootTransfer
|
|
4520
|
+
},
|
|
4521
|
+
metadata: _metadata
|
|
4522
|
+
}, null, _instanceExtraInitializers);
|
|
4523
|
+
__esDecorate(this, null, _confirmRootTransfer_decorators, {
|
|
4524
|
+
kind: "method",
|
|
4525
|
+
name: "confirmRootTransfer",
|
|
4526
|
+
static: false,
|
|
4527
|
+
private: false,
|
|
4528
|
+
access: {
|
|
4529
|
+
has: (obj) => "confirmRootTransfer" in obj,
|
|
4530
|
+
get: (obj) => obj.confirmRootTransfer
|
|
4531
|
+
},
|
|
4532
|
+
metadata: _metadata
|
|
4533
|
+
}, null, _instanceExtraInitializers);
|
|
3073
4534
|
__esDecorate(this, null, _updateDisplayName_decorators, {
|
|
3074
4535
|
kind: "method",
|
|
3075
4536
|
name: "updateDisplayName",
|
|
@@ -3158,6 +4619,17 @@ let AwikiService = (() => {
|
|
|
3158
4619
|
},
|
|
3159
4620
|
metadata: _metadata
|
|
3160
4621
|
}, null, _instanceExtraInitializers);
|
|
4622
|
+
__esDecorate(this, null, _enterRecoveredSession_decorators, {
|
|
4623
|
+
kind: "method",
|
|
4624
|
+
name: "enterRecoveredSession",
|
|
4625
|
+
static: false,
|
|
4626
|
+
private: false,
|
|
4627
|
+
access: {
|
|
4628
|
+
has: (obj) => "enterRecoveredSession" in obj,
|
|
4629
|
+
get: (obj) => obj.enterRecoveredSession
|
|
4630
|
+
},
|
|
4631
|
+
metadata: _metadata
|
|
4632
|
+
}, null, _instanceExtraInitializers);
|
|
3161
4633
|
__esDecorate(this, null, _discardRecovery_decorators, {
|
|
3162
4634
|
kind: "method",
|
|
3163
4635
|
name: "discardRecovery",
|
|
@@ -3219,8 +4691,19 @@ let AwikiService = (() => {
|
|
|
3219
4691
|
static: false,
|
|
3220
4692
|
private: false,
|
|
3221
4693
|
access: {
|
|
3222
|
-
has: (obj) => "leaveGroup" in obj,
|
|
3223
|
-
get: (obj) => obj.leaveGroup
|
|
4694
|
+
has: (obj) => "leaveGroup" in obj,
|
|
4695
|
+
get: (obj) => obj.leaveGroup
|
|
4696
|
+
},
|
|
4697
|
+
metadata: _metadata
|
|
4698
|
+
}, null, _instanceExtraInitializers);
|
|
4699
|
+
__esDecorate(this, null, _getDisplayProfiles_decorators, {
|
|
4700
|
+
kind: "method",
|
|
4701
|
+
name: "getDisplayProfiles",
|
|
4702
|
+
static: false,
|
|
4703
|
+
private: false,
|
|
4704
|
+
access: {
|
|
4705
|
+
has: (obj) => "getDisplayProfiles" in obj,
|
|
4706
|
+
get: (obj) => obj.getDisplayProfiles
|
|
3224
4707
|
},
|
|
3225
4708
|
metadata: _metadata
|
|
3226
4709
|
}, null, _instanceExtraInitializers);
|
|
@@ -3445,11 +4928,23 @@ let AwikiService = (() => {
|
|
|
3445
4928
|
resolved = __runInitializers(this, _instanceExtraInitializers);
|
|
3446
4929
|
sessionStore;
|
|
3447
4930
|
imageAttachmentCache;
|
|
3448
|
-
sentMailStore;
|
|
3449
4931
|
conversationPreferenceStore;
|
|
3450
4932
|
startupUserServiceDomain;
|
|
3451
4933
|
settingsProvider;
|
|
4934
|
+
tenantRegistry;
|
|
4935
|
+
activeTenant;
|
|
4936
|
+
activeClientOptions;
|
|
4937
|
+
clientFactory;
|
|
3452
4938
|
provider;
|
|
4939
|
+
runtimeGeneration = 0;
|
|
4940
|
+
tenantSwitching = false;
|
|
4941
|
+
tenantParticipants = /* @__PURE__ */ new Set();
|
|
4942
|
+
activeCapabilities;
|
|
4943
|
+
updatePolicyStatus;
|
|
4944
|
+
updatePolicyRequest;
|
|
4945
|
+
updatePolicyTask;
|
|
4946
|
+
updatePolicyTaskKey;
|
|
4947
|
+
desktopDistribution;
|
|
3453
4948
|
signedOut;
|
|
3454
4949
|
sessionMutation = Promise.resolve();
|
|
3455
4950
|
sessionRevision = 0;
|
|
@@ -3460,12 +4955,14 @@ let AwikiService = (() => {
|
|
|
3460
4955
|
requestSessions = /* @__PURE__ */ new Map();
|
|
3461
4956
|
deviceRefs = /* @__PURE__ */ new Map();
|
|
3462
4957
|
deviceIds = /* @__PURE__ */ new Map();
|
|
4958
|
+
rootTransfers = /* @__PURE__ */ new Map();
|
|
3463
4959
|
activeSummaryRequests = /* @__PURE__ */ new Set();
|
|
3464
4960
|
summaryProvider;
|
|
3465
|
-
recoveryReconciliationTarget;
|
|
3466
4961
|
hostContext;
|
|
3467
4962
|
/** Trusted same-process external HTTP authentication dispatcher. Never Remote. */
|
|
3468
4963
|
externalHttpAuth;
|
|
4964
|
+
integrationClient;
|
|
4965
|
+
mailListClient;
|
|
3469
4966
|
workspaceContext;
|
|
3470
4967
|
/**
|
|
3471
4968
|
* @param ctx - owning Host context.
|
|
@@ -3474,12 +4971,8 @@ let AwikiService = (() => {
|
|
|
3474
4971
|
constructor(ctx, config) {
|
|
3475
4972
|
super(ctx, "awiki");
|
|
3476
4973
|
this.hostContext = ctx;
|
|
3477
|
-
this.resolved = resolveConfig(config);
|
|
4974
|
+
this.resolved = resolveConfig(ctx, config);
|
|
3478
4975
|
this.externalHttpAuth = createAwikiExternalHttpAuth(() => this.acquireExternalHttpAuthSession());
|
|
3479
|
-
this.sessionStore = new AwikiSessionStore(this.resolved.stateRoot);
|
|
3480
|
-
this.imageAttachmentCache = new AwikiImageAttachmentCache(this.resolved.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes);
|
|
3481
|
-
this.sentMailStore = new AwikiSentMailStore(this.resolved.stateRoot);
|
|
3482
|
-
this.conversationPreferenceStore = new AwikiConversationPreferenceStore(this.resolved.stateRoot);
|
|
3483
4976
|
this.startupUserServiceDomain = this.resolved.userServiceDomain;
|
|
3484
4977
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
3485
4978
|
const provider = settingsCtx.settings;
|
|
@@ -3498,7 +4991,17 @@ let AwikiService = (() => {
|
|
|
3498
4991
|
}, "awiki: release settings namespace");
|
|
3499
4992
|
});
|
|
3500
4993
|
ctx.inject(["connection"], (connectionCtx) => {
|
|
3501
|
-
connectionCtx.connection.rpc.handle(AWIKI_SETTINGS_RPC_CHANNEL, createAwikiSettingsRpcHandler(() => this.settingsProvider
|
|
4994
|
+
connectionCtx.connection.rpc.handle(AWIKI_SETTINGS_RPC_CHANNEL, createAwikiSettingsRpcHandler(() => this.settingsProvider, {
|
|
4995
|
+
describe: () => this.getTenantRegistryView(),
|
|
4996
|
+
create: (displayName, domain) => this.createCustomTenant(displayName, domain),
|
|
4997
|
+
rename: (tenantId, displayName) => this.renameCustomTenant(tenantId, displayName),
|
|
4998
|
+
switch: (tenantId) => this.switchTenant(tenantId),
|
|
4999
|
+
archive: (tenantId) => this.archiveCustomTenant(tenantId),
|
|
5000
|
+
describeUpdate: () => this.getUpdatePolicyStatus(),
|
|
5001
|
+
refreshUpdate: () => this.refreshUpdatePolicy(),
|
|
5002
|
+
describeDesktopUpdate: () => this.getDesktopUpdate(),
|
|
5003
|
+
refreshDesktopUpdate: () => this.refreshDesktopUpdate()
|
|
5004
|
+
}), { authority: "loopback" });
|
|
3502
5005
|
});
|
|
3503
5006
|
ctx.inject(["workspaceRegistry"], (workspaceCtx) => {
|
|
3504
5007
|
this.workspaceContext = workspaceCtx;
|
|
@@ -3511,8 +5014,28 @@ let AwikiService = (() => {
|
|
|
3511
5014
|
if (current !== void 0) await this.stopAgentConsumer(current);
|
|
3512
5015
|
}, "awiki: release Workspace listener composition");
|
|
3513
5016
|
});
|
|
5017
|
+
ctx.inject(["desktopDistribution"], (desktopCtx) => {
|
|
5018
|
+
const service = desktopCtx.get("desktopDistribution");
|
|
5019
|
+
if (typeof service !== "object" || service === null || !("getSnapshot" in service) || typeof service.getSnapshot !== "function" || !("check" in service) || typeof service.check !== "function") return;
|
|
5020
|
+
const candidate = service;
|
|
5021
|
+
if (decodeDesktopDistribution(candidate.getSnapshot()) === void 0) return;
|
|
5022
|
+
this.desktopDistribution = candidate;
|
|
5023
|
+
desktopCtx.effect(() => () => {
|
|
5024
|
+
if (this.desktopDistribution === candidate) this.desktopDistribution = void 0;
|
|
5025
|
+
});
|
|
5026
|
+
});
|
|
5027
|
+
ctx.effect(() => {
|
|
5028
|
+
const timer = setInterval(() => {
|
|
5029
|
+
this.refreshUpdatePolicy().catch(() => void 0);
|
|
5030
|
+
}, 216e5);
|
|
5031
|
+
timer.unref();
|
|
5032
|
+
return () => {
|
|
5033
|
+
clearInterval(timer);
|
|
5034
|
+
};
|
|
5035
|
+
});
|
|
3514
5036
|
registerAwikiTools(ctx, this);
|
|
3515
5037
|
ctx.effect(() => async () => {
|
|
5038
|
+
this.updatePolicyRequest?.abort();
|
|
3516
5039
|
const provider = this.provider;
|
|
3517
5040
|
if (provider !== void 0) await this.disposeProvider(provider);
|
|
3518
5041
|
}, "awiki: dispose current client provider");
|
|
@@ -3528,30 +5051,276 @@ let AwikiService = (() => {
|
|
|
3528
5051
|
* @returns asynchronous disposer for the exact registered client.
|
|
3529
5052
|
*/
|
|
3530
5053
|
registerClientFactory(factory) {
|
|
3531
|
-
if (this.provider !== void 0) throw new Error("awiki: a client provider is already registered");
|
|
5054
|
+
if (this.clientFactory !== void 0 || this.provider !== void 0) throw new Error("awiki: a client provider is already registered");
|
|
5055
|
+
this.clientFactory = factory;
|
|
5056
|
+
try {
|
|
5057
|
+
this.openTenantProvider(this.ensureTenantRegistry().active(), factory);
|
|
5058
|
+
} catch (error) {
|
|
5059
|
+
this.clientFactory = void 0;
|
|
5060
|
+
throw error;
|
|
5061
|
+
}
|
|
5062
|
+
let active = true;
|
|
5063
|
+
return async () => {
|
|
5064
|
+
if (!active) return;
|
|
5065
|
+
active = false;
|
|
5066
|
+
this.clientFactory = void 0;
|
|
5067
|
+
const current = this.provider;
|
|
5068
|
+
if (current !== void 0) await this.disposeProvider(current);
|
|
5069
|
+
};
|
|
5070
|
+
}
|
|
5071
|
+
ensureTenantRegistry() {
|
|
5072
|
+
if (this.tenantRegistry !== void 0) return this.tenantRegistry;
|
|
5073
|
+
const domain = this.startupUserServiceDomain;
|
|
5074
|
+
const configuredOrigin = `https://${this.resolved.userServiceDomain}`;
|
|
5075
|
+
const tenantOrigin = `https://${domain}`;
|
|
5076
|
+
const derive = (value) => value.replace(/\/$/u, "") === configuredOrigin ? tenantOrigin : value;
|
|
5077
|
+
const seed = {
|
|
5078
|
+
domain,
|
|
5079
|
+
configured: domain !== DEFAULT_AWIKI_DOMAIN || this.resolved.userServiceUrl.replace(/\/$/u, "") !== configuredOrigin || this.resolved.messageServiceUrl.replace(/\/$/u, "") !== configuredOrigin,
|
|
5080
|
+
...this.resolved.legacyTenantSlot === void 0 ? {} : { legacyTenantSlot: this.resolved.legacyTenantSlot },
|
|
5081
|
+
userServiceUrl: derive(this.resolved.userServiceUrl),
|
|
5082
|
+
messageServiceUrl: derive(this.resolved.messageServiceUrl),
|
|
5083
|
+
mailServiceUrl: derive(this.resolved.mailServiceUrl),
|
|
5084
|
+
messageServicePublicUrl: derive(this.resolved.messageServicePublicUrl),
|
|
5085
|
+
messageServiceDid: this.resolved.messageServiceDid === `did:wba:${this.resolved.userServiceDomain}` ? `did:wba:${domain}` : this.resolved.messageServiceDid
|
|
5086
|
+
};
|
|
5087
|
+
this.tenantRegistry = AwikiTenantRegistry.open(this.resolved.stateRoot, seed);
|
|
5088
|
+
return this.tenantRegistry;
|
|
5089
|
+
}
|
|
5090
|
+
optionsForTenant(tenant) {
|
|
5091
|
+
const endpoints = tenant.endpoints;
|
|
5092
|
+
const messageServicePublicOrigin = new URL(endpoints.messageServicePublicUrl).origin;
|
|
5093
|
+
return {
|
|
5094
|
+
...endpoints,
|
|
5095
|
+
userServiceDomain: tenant.didHost,
|
|
5096
|
+
allowedAttachmentOrigins: tenant.storageLayout === "legacy-base" ? this.resolved.allowedAttachmentOrigins : [messageServicePublicOrigin],
|
|
5097
|
+
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
5098
|
+
allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
|
|
5099
|
+
stateRoot: this.ensureTenantRegistry().stateRoot(tenant)
|
|
5100
|
+
};
|
|
5101
|
+
}
|
|
5102
|
+
ensureTenantOptions() {
|
|
5103
|
+
if (this.activeClientOptions !== void 0) return this.activeClientOptions;
|
|
5104
|
+
const tenant = this.ensureTenantRegistry().active();
|
|
5105
|
+
const options = this.optionsForTenant(tenant);
|
|
5106
|
+
this.bindTenantState(tenant, options);
|
|
5107
|
+
return options;
|
|
5108
|
+
}
|
|
5109
|
+
bindTenantState(tenant, options) {
|
|
5110
|
+
this.updatePolicyRequest?.abort();
|
|
5111
|
+
this.updatePolicyStatus = void 0;
|
|
5112
|
+
this.updatePolicyTask = void 0;
|
|
5113
|
+
this.activeTenant = tenant;
|
|
5114
|
+
this.activeClientOptions = options;
|
|
5115
|
+
this.startupUserServiceDomain = tenant.didHost;
|
|
5116
|
+
this.sessionStore = new AwikiSessionStore(options.stateRoot);
|
|
5117
|
+
this.imageAttachmentCache = new AwikiImageAttachmentCache(options.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes);
|
|
5118
|
+
this.conversationPreferenceStore = new AwikiConversationPreferenceStore(options.stateRoot);
|
|
5119
|
+
this.mailListClient = new AwikiMailListClient(options.mailServiceUrl, this.externalHttpAuth);
|
|
5120
|
+
this.integrationClient = void 0;
|
|
5121
|
+
this.activeCapabilities = {
|
|
5122
|
+
tenantId: tenant.tenantId,
|
|
5123
|
+
generation: this.runtimeGeneration,
|
|
5124
|
+
online: false,
|
|
5125
|
+
handleRecoveryPhoneEnabled: false
|
|
5126
|
+
};
|
|
5127
|
+
this.signedOut = void 0;
|
|
5128
|
+
this.activeIdentityDid = void 0;
|
|
3532
5129
|
this.pendingDeviceJoin = void 0;
|
|
3533
5130
|
this.activeDeviceJoinSessionId = void 0;
|
|
5131
|
+
}
|
|
5132
|
+
openTenantProvider(tenant, factory, startRuntime = true) {
|
|
5133
|
+
const options = this.optionsForTenant(tenant);
|
|
3534
5134
|
const provider = {
|
|
3535
|
-
client: factory(
|
|
3536
|
-
userServiceUrl: this.resolved.userServiceUrl,
|
|
3537
|
-
userServiceDomain: this.startupUserServiceDomain,
|
|
3538
|
-
messageServiceUrl: this.resolved.messageServiceUrl,
|
|
3539
|
-
mailServiceUrl: this.resolved.mailServiceUrl,
|
|
3540
|
-
messageServicePublicUrl: this.resolved.messageServicePublicUrl,
|
|
3541
|
-
messageServiceDid: this.resolved.messageServiceDid,
|
|
3542
|
-
allowedAttachmentOrigins: this.resolved.allowedAttachmentOrigins,
|
|
3543
|
-
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
3544
|
-
allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
|
|
3545
|
-
stateRoot: this.resolved.stateRoot
|
|
3546
|
-
}),
|
|
5135
|
+
client: factory(options),
|
|
3547
5136
|
realtimeGeneration: 0,
|
|
3548
5137
|
agentConsumerGeneration: 0,
|
|
3549
5138
|
localDeviceJoinRequestCountAfterSync: 0
|
|
3550
5139
|
};
|
|
5140
|
+
this.bindTenantState(tenant, options);
|
|
3551
5141
|
this.provider = provider;
|
|
3552
|
-
this.
|
|
3553
|
-
this.
|
|
3554
|
-
return
|
|
5142
|
+
this.refreshUpdatePolicy().catch(() => void 0);
|
|
5143
|
+
if (startRuntime) this.ensureProviderRuntime(provider);
|
|
5144
|
+
return provider;
|
|
5145
|
+
}
|
|
5146
|
+
/** Read the Host-owned catalog for trusted loopback settings surfaces. */
|
|
5147
|
+
getTenantRegistryView() {
|
|
5148
|
+
return this.ensureTenantRegistry().snapshot(this.tenantSwitching);
|
|
5149
|
+
}
|
|
5150
|
+
/** Browser-safe, same-process update state for Desktop and loopback settings. */
|
|
5151
|
+
getUpdatePolicyStatus() {
|
|
5152
|
+
const tenant = this.activeTenant ?? this.ensureTenantRegistry().active();
|
|
5153
|
+
const modelProxyVersion = this.currentModelProxyVersion();
|
|
5154
|
+
return this.updatePolicyStatus ?? readAwikiUpdatePolicyStatus({
|
|
5155
|
+
tenant,
|
|
5156
|
+
generation: this.runtimeGeneration,
|
|
5157
|
+
stateRoot: this.resolved.stateRoot,
|
|
5158
|
+
currentPluginVersion: DSH_AWIKI_VERSION,
|
|
5159
|
+
...modelProxyVersion === void 0 ? {} : { currentModelProxyVersion: modelProxyVersion }
|
|
5160
|
+
});
|
|
5161
|
+
}
|
|
5162
|
+
/** Only the Desktop provider owns its release source; tenants cannot override it. */
|
|
5163
|
+
getDesktopUpdate() {
|
|
5164
|
+
return decodeDesktopDistribution(this.desktopDistribution?.getSnapshot());
|
|
5165
|
+
}
|
|
5166
|
+
async refreshDesktopUpdate() {
|
|
5167
|
+
const provider = this.desktopDistribution;
|
|
5168
|
+
if (provider === void 0) return void 0;
|
|
5169
|
+
const value = await provider.check();
|
|
5170
|
+
return provider === this.desktopDistribution ? decodeDesktopDistribution(value) : this.getDesktopUpdate();
|
|
5171
|
+
}
|
|
5172
|
+
/** Refresh only the active generation; late results from old tenants are discarded. */
|
|
5173
|
+
refreshUpdatePolicy() {
|
|
5174
|
+
const key = JSON.stringify([this.runtimeGeneration, this.currentModelProxyVersion()]);
|
|
5175
|
+
if (this.updatePolicyTask !== void 0 && this.updatePolicyTaskKey === key) return this.updatePolicyTask;
|
|
5176
|
+
const task = this.performUpdatePolicyRefresh().finally(() => {
|
|
5177
|
+
if (this.updatePolicyTask === task) this.updatePolicyTask = void 0;
|
|
5178
|
+
});
|
|
5179
|
+
this.updatePolicyTask = task;
|
|
5180
|
+
this.updatePolicyTaskKey = key;
|
|
5181
|
+
return task;
|
|
5182
|
+
}
|
|
5183
|
+
async performUpdatePolicyRefresh() {
|
|
5184
|
+
const tenant = this.activeTenant ?? this.ensureTenantRegistry().active();
|
|
5185
|
+
const generation = this.runtimeGeneration;
|
|
5186
|
+
this.updatePolicyRequest?.abort();
|
|
5187
|
+
const request = new AbortController();
|
|
5188
|
+
this.updatePolicyRequest = request;
|
|
5189
|
+
const currentModelProxyVersion = this.currentModelProxyVersion();
|
|
5190
|
+
const status = await checkAwikiUpdatePolicy({
|
|
5191
|
+
tenant,
|
|
5192
|
+
generation,
|
|
5193
|
+
stateRoot: this.resolved.stateRoot,
|
|
5194
|
+
currentPluginVersion: DSH_AWIKI_VERSION,
|
|
5195
|
+
...currentModelProxyVersion === void 0 ? {} : { currentModelProxyVersion },
|
|
5196
|
+
allowInsecureLoopback: this.resolved.allowInsecureLoopbackForTesting,
|
|
5197
|
+
signal: request.signal
|
|
5198
|
+
});
|
|
5199
|
+
if (this.updatePolicyRequest === request && this.runtimeGeneration === generation && this.activeTenant?.tenantId === tenant.tenantId) {
|
|
5200
|
+
const wasRestricted = this.getUpdatePolicyStatus().restricted;
|
|
5201
|
+
this.updatePolicyStatus = status;
|
|
5202
|
+
this.hostContext.emit("awiki/update-policy", status);
|
|
5203
|
+
if (status.restricted) {
|
|
5204
|
+
this.invalidateSummaries();
|
|
5205
|
+
const provider = this.provider;
|
|
5206
|
+
if (provider !== void 0) try {
|
|
5207
|
+
await this.stopProviderRuntime(provider);
|
|
5208
|
+
} catch (error) {
|
|
5209
|
+
this.ctx.logger("awiki-update").warn("AWiki restricted-version runtime shutdown failed: %s", error instanceof Error ? error.message : "unknown failure");
|
|
5210
|
+
}
|
|
5211
|
+
} else if (wasRestricted && this.provider !== void 0 && !this.tenantSwitching) this.ensureProviderRuntime(this.provider);
|
|
5212
|
+
}
|
|
5213
|
+
return this.getUpdatePolicyStatus();
|
|
5214
|
+
}
|
|
5215
|
+
currentModelProxyVersion() {
|
|
5216
|
+
for (const participant of this.tenantParticipants) if (participant.component?.product === "dsh-awiki-model-proxy") return participant.component.version;
|
|
5217
|
+
}
|
|
5218
|
+
createCustomTenant(displayName, domain) {
|
|
5219
|
+
this.assertVersionSupported();
|
|
5220
|
+
this.ensureTenantRegistry().createCustom(displayName, domain);
|
|
5221
|
+
return this.getTenantRegistryView();
|
|
5222
|
+
}
|
|
5223
|
+
renameCustomTenant(tenantId, displayName) {
|
|
5224
|
+
this.assertVersionSupported();
|
|
5225
|
+
this.ensureTenantRegistry().renameCustom(tenantId, displayName);
|
|
5226
|
+
return this.getTenantRegistryView();
|
|
5227
|
+
}
|
|
5228
|
+
archiveCustomTenant(tenantId) {
|
|
5229
|
+
this.assertVersionSupported();
|
|
5230
|
+
this.ensureTenantRegistry().archiveCustom(tenantId);
|
|
5231
|
+
return this.getTenantRegistryView();
|
|
5232
|
+
}
|
|
5233
|
+
/** Register one same-process resource owner in every transactional tenant change. */
|
|
5234
|
+
registerTenantLifecycleParticipant(participant) {
|
|
5235
|
+
if (this.tenantParticipants.has(participant)) throw new Error("awiki: tenant lifecycle participant is already registered");
|
|
5236
|
+
this.tenantParticipants.add(participant);
|
|
5237
|
+
if (participant.component !== void 0) this.updatePolicyStatus = void 0;
|
|
5238
|
+
this.refreshUpdatePolicy().catch(() => void 0);
|
|
5239
|
+
let active = true;
|
|
5240
|
+
return () => {
|
|
5241
|
+
if (!active) return;
|
|
5242
|
+
active = false;
|
|
5243
|
+
this.tenantParticipants.delete(participant);
|
|
5244
|
+
if (participant.component !== void 0) this.updatePolicyStatus = void 0;
|
|
5245
|
+
this.refreshUpdatePolicy().catch(() => void 0);
|
|
5246
|
+
};
|
|
5247
|
+
}
|
|
5248
|
+
/** Replace the complete Core runtime and commit the active tenant only after opening succeeds. */
|
|
5249
|
+
async switchTenant(tenantId) {
|
|
5250
|
+
if (this.tenantSwitching) throw new Error("awiki: a tenant switch is already in progress");
|
|
5251
|
+
const registry = this.ensureTenantRegistry();
|
|
5252
|
+
const previousTenant = registry.active();
|
|
5253
|
+
if (previousTenant.tenantId === tenantId) return this.getTenantRegistryView();
|
|
5254
|
+
const target = registry.find(tenantId);
|
|
5255
|
+
if (target === void 0 || target.lifecycle === "archived") throw new Error("awiki: target tenant is unavailable");
|
|
5256
|
+
const factory = this.clientFactory;
|
|
5257
|
+
const previousProvider = this.provider;
|
|
5258
|
+
if (factory === void 0 || previousProvider === void 0) throw new Error("awiki: client provider is unavailable");
|
|
5259
|
+
this.tenantSwitching = true;
|
|
5260
|
+
this.updatePolicyRequest?.abort();
|
|
5261
|
+
this.updatePolicyTask = void 0;
|
|
5262
|
+
const context = {
|
|
5263
|
+
from: previousTenant,
|
|
5264
|
+
to: target,
|
|
5265
|
+
generation: ++this.runtimeGeneration
|
|
5266
|
+
};
|
|
5267
|
+
const prepared = [];
|
|
5268
|
+
let targetOpened = false;
|
|
5269
|
+
let committed = false;
|
|
5270
|
+
try {
|
|
5271
|
+
await this.sessionMutation;
|
|
5272
|
+
this.invalidateSummaries();
|
|
5273
|
+
for (const participant of this.tenantParticipants) {
|
|
5274
|
+
await participant.prepareSwitch(context);
|
|
5275
|
+
prepared.push(participant);
|
|
5276
|
+
}
|
|
5277
|
+
await this.disposeProvider(previousProvider);
|
|
5278
|
+
const targetProvider = this.openTenantProvider(target, factory, false);
|
|
5279
|
+
targetOpened = true;
|
|
5280
|
+
await targetProvider.client.getIdentity();
|
|
5281
|
+
await Promise.all([this.discoverTenantCapabilities(3e3), this.refreshUpdatePolicy()]);
|
|
5282
|
+
registry.commitActive(target.tenantId);
|
|
5283
|
+
committed = true;
|
|
5284
|
+
for (const participant of prepared) await participant.commitSwitch?.(context);
|
|
5285
|
+
this.tenantSwitching = false;
|
|
5286
|
+
this.ensureProviderRuntime(targetProvider);
|
|
5287
|
+
const view = this.getTenantRegistryView();
|
|
5288
|
+
this.hostContext.emit("awiki/tenant", view);
|
|
5289
|
+
return view;
|
|
5290
|
+
} catch (error) {
|
|
5291
|
+
const rollbackFailures = [];
|
|
5292
|
+
if (targetOpened) {
|
|
5293
|
+
const current = this.provider;
|
|
5294
|
+
if (current !== void 0) try {
|
|
5295
|
+
await this.disposeProvider(current);
|
|
5296
|
+
} catch (rollbackError) {
|
|
5297
|
+
rollbackFailures.push(rollbackError);
|
|
5298
|
+
}
|
|
5299
|
+
}
|
|
5300
|
+
if (committed) try {
|
|
5301
|
+
registry.commitActive(previousTenant.tenantId);
|
|
5302
|
+
} catch (rollbackError) {
|
|
5303
|
+
rollbackFailures.push(rollbackError);
|
|
5304
|
+
}
|
|
5305
|
+
if (this.provider === void 0) try {
|
|
5306
|
+
const restored = this.openTenantProvider(previousTenant, factory, false);
|
|
5307
|
+
await restored.client.getIdentity();
|
|
5308
|
+
this.ensureProviderRuntime(restored);
|
|
5309
|
+
} catch (rollbackError) {
|
|
5310
|
+
rollbackFailures.push(rollbackError);
|
|
5311
|
+
}
|
|
5312
|
+
for (const participant of prepared.reverse()) try {
|
|
5313
|
+
await participant.rollbackSwitch?.(context);
|
|
5314
|
+
} catch (rollbackError) {
|
|
5315
|
+
rollbackFailures.push(rollbackError);
|
|
5316
|
+
}
|
|
5317
|
+
if (rollbackFailures.length > 0) throw new AggregateError([error, ...rollbackFailures], "awiki: tenant switch failed and rollback was incomplete");
|
|
5318
|
+
this.tenantSwitching = false;
|
|
5319
|
+
if (this.provider !== void 0) this.ensureProviderRuntime(this.provider);
|
|
5320
|
+
throw new Error("awiki: tenant switch failed; the previous tenant was restored", { cause: error });
|
|
5321
|
+
} finally {
|
|
5322
|
+
this.tenantSwitching = false;
|
|
5323
|
+
}
|
|
3555
5324
|
}
|
|
3556
5325
|
/** Safe same-process diagnostics for focused E2E. Never exposed through Typert Remote. */
|
|
3557
5326
|
getRealtimeDiagnostics() {
|
|
@@ -3570,18 +5339,6 @@ let AwikiService = (() => {
|
|
|
3570
5339
|
localDeviceJoinRequestCountAfterSync: provider?.localDeviceJoinRequestCountAfterSync ?? 0
|
|
3571
5340
|
};
|
|
3572
5341
|
}
|
|
3573
|
-
/** Register the optional Model Proxy recovery target without exposing an arbitrary callback or token. */
|
|
3574
|
-
registerRecoveryReconciliationTarget(target) {
|
|
3575
|
-
if (this.recoveryReconciliationTarget !== void 0) throw new Error("awiki: a recovery reconciliation target is already registered");
|
|
3576
|
-
const registered = Object.freeze({ endpoint: recoveryReconciliationEndpoint(target, this.resolved.allowInsecureLoopbackForTesting) });
|
|
3577
|
-
this.recoveryReconciliationTarget = registered;
|
|
3578
|
-
let active = true;
|
|
3579
|
-
return () => {
|
|
3580
|
-
if (!active) return;
|
|
3581
|
-
active = false;
|
|
3582
|
-
if (this.recoveryReconciliationTarget === registered) this.recoveryReconciliationTarget = void 0;
|
|
3583
|
-
};
|
|
3584
|
-
}
|
|
3585
5342
|
/** Register one replaceable conversation-summary provider for this deployment. */
|
|
3586
5343
|
registerSummaryProvider(provider) {
|
|
3587
5344
|
if (this.summaryProvider !== void 0) throw new Error("awiki: a summary provider is already registered");
|
|
@@ -3598,13 +5355,66 @@ let AwikiService = (() => {
|
|
|
3598
5355
|
* @returns Browser-safe polling configuration without SDK endpoints or state paths.
|
|
3599
5356
|
*/
|
|
3600
5357
|
async getConfig() {
|
|
3601
|
-
|
|
5358
|
+
this.ensureTenantOptions();
|
|
5359
|
+
const capabilities = await this.discoverTenantCapabilities(1e4);
|
|
5360
|
+
if (this.tenantSwitching || capabilities.generation !== this.runtimeGeneration || capabilities.tenantId !== this.activeTenant?.tenantId) return {
|
|
5361
|
+
ok: false,
|
|
5362
|
+
error: failure("conflict")
|
|
5363
|
+
};
|
|
5364
|
+
return {
|
|
5365
|
+
ok: true,
|
|
5366
|
+
value: {
|
|
5367
|
+
pollIntervalMs: this.resolved.pollIntervalMs,
|
|
5368
|
+
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
5369
|
+
handleRecoveryPhoneEnabled: capabilities.handleRecoveryPhoneEnabled,
|
|
5370
|
+
...capabilities.guestGatewayBaseUrl === void 0 ? {} : { integrationGuideUrl: new URL("/guest/guide/integration", capabilities.guestGatewayBaseUrl).toString() },
|
|
5371
|
+
tenantId: capabilities.tenantId,
|
|
5372
|
+
tenantGeneration: capabilities.generation,
|
|
5373
|
+
tenantOnline: capabilities.online,
|
|
5374
|
+
services: {
|
|
5375
|
+
modelProxy: { enabled: capabilities.modelProxyBaseUrl !== void 0 },
|
|
5376
|
+
guestGateway: { enabled: capabilities.guestGatewayBaseUrl !== void 0 }
|
|
5377
|
+
}
|
|
5378
|
+
}
|
|
5379
|
+
};
|
|
5380
|
+
}
|
|
5381
|
+
/** Trusted same-process capability binding for optional tenant participants. */
|
|
5382
|
+
getTenantCapabilities() {
|
|
5383
|
+
const capabilities = this.activeCapabilities;
|
|
5384
|
+
if (capabilities === void 0) throw new Error("awiki: tenant capabilities are unavailable");
|
|
5385
|
+
return { ...capabilities };
|
|
5386
|
+
}
|
|
5387
|
+
/** Refresh the active tenant's optional service advertisement without affecting Core availability. */
|
|
5388
|
+
refreshTenantCapabilities() {
|
|
5389
|
+
this.ensureTenantOptions();
|
|
5390
|
+
return this.discoverTenantCapabilities(1e4);
|
|
5391
|
+
}
|
|
5392
|
+
async discoverTenantCapabilities(timeoutMs) {
|
|
5393
|
+
const options = this.activeClientOptions;
|
|
5394
|
+
const tenant = this.activeTenant;
|
|
5395
|
+
if (options === void 0 || tenant === void 0) throw new ProviderUnavailableError();
|
|
5396
|
+
const generation = this.runtimeGeneration;
|
|
5397
|
+
const fallback = {
|
|
5398
|
+
tenantId: tenant.tenantId,
|
|
5399
|
+
generation,
|
|
5400
|
+
online: false,
|
|
5401
|
+
handleRecoveryPhoneEnabled: false
|
|
5402
|
+
};
|
|
5403
|
+
const publish = (capabilities) => {
|
|
5404
|
+
if (this.runtimeGeneration === generation && this.activeTenant?.tenantId === tenant.tenantId) {
|
|
5405
|
+
this.activeCapabilities = capabilities;
|
|
5406
|
+
this.integrationClient = capabilities.guestGatewayBaseUrl === void 0 ? void 0 : new AwikiIntegrationClient(capabilities.guestGatewayBaseUrl, this.externalHttpAuth);
|
|
5407
|
+
}
|
|
5408
|
+
return capabilities;
|
|
5409
|
+
};
|
|
3602
5410
|
const abort = new AbortController();
|
|
3603
5411
|
const timeout = setTimeout(() => {
|
|
3604
5412
|
abort.abort();
|
|
3605
|
-
},
|
|
5413
|
+
}, timeoutMs);
|
|
3606
5414
|
try {
|
|
3607
|
-
const
|
|
5415
|
+
const endpoint = new URL("/user-service/v1/server-info", options.userServiceUrl);
|
|
5416
|
+
endpoint.searchParams.set("client_platform", "dsh");
|
|
5417
|
+
const response = await fetch(endpoint, {
|
|
3608
5418
|
method: "GET",
|
|
3609
5419
|
headers: {
|
|
3610
5420
|
accept: "application/json",
|
|
@@ -3614,25 +5424,58 @@ let AwikiService = (() => {
|
|
|
3614
5424
|
redirect: "error",
|
|
3615
5425
|
signal: abort.signal
|
|
3616
5426
|
});
|
|
3617
|
-
if (response.ok)
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
5427
|
+
if (!response.ok) return publish(fallback);
|
|
5428
|
+
const text = await readBoundedResponseText(response, SERVER_INFO_RESPONSE_MAX_BYTES);
|
|
5429
|
+
const value = text === void 0 ? void 0 : JSON.parse(text);
|
|
5430
|
+
if (typeof value !== "object" || value === null || value.schema_version !== 1) return publish(fallback);
|
|
5431
|
+
const methods = value.identity?.handle_recovery?.methods;
|
|
5432
|
+
const handleRecoveryPhoneEnabled = Array.isArray(methods) && methods.some((method) => typeof method === "object" && method !== null && method.id === "phone" && method.enabled === true && method.verification?.required === true && method.verification?.type === "sms_otp");
|
|
5433
|
+
const services = value.services;
|
|
5434
|
+
const modelProxyBaseUrl = publishedServiceBaseUrl(services, "model_proxy", this.resolved.allowInsecureLoopbackForTesting);
|
|
5435
|
+
const guestGatewayBaseUrl = publishedServiceBaseUrl(services, "guest_gateway", false);
|
|
5436
|
+
return publish({
|
|
5437
|
+
tenantId: tenant.tenantId,
|
|
5438
|
+
generation,
|
|
5439
|
+
online: true,
|
|
5440
|
+
handleRecoveryPhoneEnabled,
|
|
5441
|
+
...modelProxyBaseUrl === void 0 ? {} : { modelProxyBaseUrl },
|
|
5442
|
+
...guestGatewayBaseUrl === void 0 ? {} : { guestGatewayBaseUrl }
|
|
5443
|
+
});
|
|
5444
|
+
} catch {
|
|
5445
|
+
return publish(fallback);
|
|
5446
|
+
} finally {
|
|
3626
5447
|
clearTimeout(timeout);
|
|
3627
5448
|
}
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
5449
|
+
}
|
|
5450
|
+
/** Read the Integration owned by the active full Handle through the fixed Host client. */
|
|
5451
|
+
getIntegration() {
|
|
5452
|
+
if (this.getUpdatePolicyStatus().restricted) return Promise.resolve(integrationUnavailable());
|
|
5453
|
+
return this.integrationClient?.read() ?? Promise.resolve(integrationUnavailable());
|
|
5454
|
+
}
|
|
5455
|
+
/** Create the active full Handle's only Integration. */
|
|
5456
|
+
createIntegration(request) {
|
|
5457
|
+
if (this.getUpdatePolicyStatus().restricted) return Promise.resolve(integrationUnavailable());
|
|
5458
|
+
return this.integrationClient?.create(request) ?? Promise.resolve(integrationUnavailable());
|
|
5459
|
+
}
|
|
5460
|
+
/** Update editable Integration fields with optimistic concurrency. */
|
|
5461
|
+
updateIntegration(request) {
|
|
5462
|
+
if (this.getUpdatePolicyStatus().restricted) return Promise.resolve(integrationUnavailable());
|
|
5463
|
+
return this.integrationClient?.update(request) ?? Promise.resolve(integrationUnavailable());
|
|
5464
|
+
}
|
|
5465
|
+
/** Atomically replace the current public Integration id. */
|
|
5466
|
+
rotateIntegrationId(request) {
|
|
5467
|
+
if (this.getUpdatePolicyStatus().restricted) return Promise.resolve(integrationUnavailable());
|
|
5468
|
+
return this.integrationClient?.rotate(request) ?? Promise.resolve(integrationUnavailable());
|
|
5469
|
+
}
|
|
5470
|
+
/** Close the Integration and revoke its current public id. */
|
|
5471
|
+
closeIntegration(request) {
|
|
5472
|
+
if (this.getUpdatePolicyStatus().restricted) return Promise.resolve(integrationUnavailable());
|
|
5473
|
+
return this.integrationClient?.close(request) ?? Promise.resolve(integrationUnavailable());
|
|
5474
|
+
}
|
|
5475
|
+
/** Revalidate one closed Integration and issue a new public id. */
|
|
5476
|
+
reopenIntegration(request) {
|
|
5477
|
+
if (this.getUpdatePolicyStatus().restricted) return Promise.resolve(integrationUnavailable());
|
|
5478
|
+
return this.integrationClient?.reopen(request) ?? Promise.resolve(integrationUnavailable());
|
|
3636
5479
|
}
|
|
3637
5480
|
/**
|
|
3638
5481
|
* Read the deployment's identity status.
|
|
@@ -3674,6 +5517,7 @@ let AwikiService = (() => {
|
|
|
3674
5517
|
ok: false,
|
|
3675
5518
|
error: failure("invalid-request")
|
|
3676
5519
|
});
|
|
5520
|
+
this.sessionRevision += 1;
|
|
3677
5521
|
return this.mutateSession(async () => {
|
|
3678
5522
|
if (await this.isSignedOut()) return {
|
|
3679
5523
|
ok: true,
|
|
@@ -3741,12 +5585,17 @@ let AwikiService = (() => {
|
|
|
3741
5585
|
}
|
|
3742
5586
|
/** Classify one configured-domain Handle before selecting the registration or recovery OTP purpose. */
|
|
3743
5587
|
async inspectIdentityAccess(request) {
|
|
3744
|
-
const
|
|
5588
|
+
const options = this.activeClientOptions;
|
|
5589
|
+
if (options === void 0) return {
|
|
5590
|
+
ok: false,
|
|
5591
|
+
error: normalizeFailure(new ProviderUnavailableError())
|
|
5592
|
+
};
|
|
5593
|
+
const target = identityAccessTarget(request, options.userServiceDomain);
|
|
3745
5594
|
if (target === void 0) return {
|
|
3746
5595
|
ok: false,
|
|
3747
5596
|
error: failure("invalid-request")
|
|
3748
5597
|
};
|
|
3749
|
-
const endpoint = new URL(`/.well-known/handle/${encodeURIComponent(target.localPart)}`,
|
|
5598
|
+
const endpoint = new URL(`/.well-known/handle/${encodeURIComponent(target.localPart)}`, options.userServiceUrl);
|
|
3750
5599
|
const abort = new AbortController();
|
|
3751
5600
|
const timeout = setTimeout(() => {
|
|
3752
5601
|
abort.abort();
|
|
@@ -3863,6 +5712,37 @@ let AwikiService = (() => {
|
|
|
3863
5712
|
}
|
|
3864
5713
|
};
|
|
3865
5714
|
}
|
|
5715
|
+
/** Retire only this revoked device credential; ordinary local messages remain for the rejoined identity. */
|
|
5716
|
+
retireDeviceIdentityForRejoin() {
|
|
5717
|
+
return this.mutateSession(async () => {
|
|
5718
|
+
const provider = this.provider;
|
|
5719
|
+
if (provider !== void 0) await this.stopProviderRuntime(provider);
|
|
5720
|
+
const result = await this.run((client) => client.retireDefaultIdentityForRejoin(), { allowSignedOut: true });
|
|
5721
|
+
if (!result.ok) {
|
|
5722
|
+
if (provider !== void 0 && this.provider === provider) this.ensureProviderRuntime(provider);
|
|
5723
|
+
return result;
|
|
5724
|
+
}
|
|
5725
|
+
try {
|
|
5726
|
+
await this.sessionStore.signIn();
|
|
5727
|
+
} catch {
|
|
5728
|
+
try {
|
|
5729
|
+
await this.sessionStore.signIn();
|
|
5730
|
+
} catch {
|
|
5731
|
+
this.ctx.logger("awiki").warn("AWiki local session marker could not be refreshed after device retirement");
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
this.signedOut = false;
|
|
5735
|
+
this.activeIdentityDid = void 0;
|
|
5736
|
+
this.pendingDeviceJoin = void 0;
|
|
5737
|
+
this.activeDeviceJoinSessionId = void 0;
|
|
5738
|
+
this.invalidateSummaries();
|
|
5739
|
+
this.publishSession({ status: "unregistered" });
|
|
5740
|
+
return {
|
|
5741
|
+
ok: true,
|
|
5742
|
+
value: { completed: true }
|
|
5743
|
+
};
|
|
5744
|
+
});
|
|
5745
|
+
}
|
|
3866
5746
|
/** Consume the exact in-memory continuation; ordinary Join never claims rebind user presence. */
|
|
3867
5747
|
async beginDeviceJoin() {
|
|
3868
5748
|
const continuation = this.pendingDeviceJoin;
|
|
@@ -3870,20 +5750,53 @@ let AwikiService = (() => {
|
|
|
3870
5750
|
ok: false,
|
|
3871
5751
|
error: failure("conflict")
|
|
3872
5752
|
};
|
|
3873
|
-
if (continuation.mode
|
|
5753
|
+
if (continuation.mode === "ordinary" && continuation.requiresUserPresence || continuation.mode === "handle-recovery-rebind" && !continuation.requiresUserPresence) return {
|
|
3874
5754
|
ok: false,
|
|
3875
5755
|
error: failure("forbidden")
|
|
3876
5756
|
};
|
|
3877
|
-
this.
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
5757
|
+
const result = await this.run(async (client) => {
|
|
5758
|
+
let userPresenceConfirmed = false;
|
|
5759
|
+
if (continuation.mode === "handle-recovery-rebind") {
|
|
5760
|
+
if (!client.trustedUserPresenceSupported) throw Object.assign(/* @__PURE__ */ new Error("trusted user presence unsupported"), {
|
|
5761
|
+
name: "AwikiSdkError",
|
|
5762
|
+
code: "forbidden"
|
|
5763
|
+
});
|
|
5764
|
+
userPresenceConfirmed = await client.confirmUserPresence("Rejoin your recovered AWiki identity on this device");
|
|
5765
|
+
if (!userPresenceConfirmed) throw Object.assign(/* @__PURE__ */ new Error("user presence denied"), {
|
|
5766
|
+
name: "AwikiSdkError",
|
|
5767
|
+
code: "forbidden"
|
|
5768
|
+
});
|
|
5769
|
+
}
|
|
5770
|
+
this.pendingDeviceJoin = void 0;
|
|
5771
|
+
return client.beginDeviceJoin({
|
|
5772
|
+
continuationId: continuation.continuationId,
|
|
5773
|
+
operationId: `dsh-device-join-${randomUUID()}`,
|
|
5774
|
+
userPresenceConfirmed
|
|
5775
|
+
});
|
|
5776
|
+
});
|
|
3883
5777
|
if (!result.ok) return result;
|
|
3884
5778
|
this.activeDeviceJoinSessionId = result.value.joinSessionId;
|
|
3885
5779
|
return this.applyCandidateJoinProgress(result.value);
|
|
3886
5780
|
}
|
|
5781
|
+
/** Read local workflow authority before offering a new login. No remote mutations. */
|
|
5782
|
+
getIdentityAccessState() {
|
|
5783
|
+
return this.run(async (client) => {
|
|
5784
|
+
const joining = await this.selectDeviceJoinSession(client) !== null;
|
|
5785
|
+
const recoveries = await client.listPendingRecoveries();
|
|
5786
|
+
const domain = this.activeClientOptions?.userServiceDomain;
|
|
5787
|
+
const choice = this.pendingDeviceJoin;
|
|
5788
|
+
return {
|
|
5789
|
+
choice: joining || choice === void 0 ? null : {
|
|
5790
|
+
status: "join-required",
|
|
5791
|
+
fullHandle: choice.fullHandle,
|
|
5792
|
+
mode: choice.mode,
|
|
5793
|
+
requiresUserPresence: choice.requiresUserPresence
|
|
5794
|
+
},
|
|
5795
|
+
joining,
|
|
5796
|
+
recoveries: recoveries.filter((value) => domain !== void 0 && value.fullHandle.slice(value.fullHandle.indexOf(".") + 1) === domain)
|
|
5797
|
+
};
|
|
5798
|
+
}, { allowSignedOut: true });
|
|
5799
|
+
}
|
|
3887
5800
|
/** Restore from Core local_sessions and advance only the exact resumable Join. */
|
|
3888
5801
|
async getDeviceJoinStatus() {
|
|
3889
5802
|
const result = await this.run(async (client) => {
|
|
@@ -3976,7 +5889,18 @@ let AwikiService = (() => {
|
|
|
3976
5889
|
code: "forbidden"
|
|
3977
5890
|
});
|
|
3978
5891
|
const prompt = await client.prepareDeviceJoinApproval(joinSessionId);
|
|
3979
|
-
|
|
5892
|
+
try {
|
|
5893
|
+
return await client.confirmDeviceJoinApproval(prompt.approvalHandle);
|
|
5894
|
+
} catch (error) {
|
|
5895
|
+
try {
|
|
5896
|
+
await client.syncDeviceManagement();
|
|
5897
|
+
} catch {}
|
|
5898
|
+
try {
|
|
5899
|
+
const recovered = await client.getLocalDeviceJoinVerificationProgress(joinSessionId);
|
|
5900
|
+
if (adminJoinPhase(recovered) === "authorized") return recovered;
|
|
5901
|
+
} catch {}
|
|
5902
|
+
throw error;
|
|
5903
|
+
}
|
|
3980
5904
|
});
|
|
3981
5905
|
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
3982
5906
|
}
|
|
@@ -4013,6 +5937,63 @@ let AwikiService = (() => {
|
|
|
4013
5937
|
return this.deviceManagementSnapshot(client);
|
|
4014
5938
|
});
|
|
4015
5939
|
}
|
|
5940
|
+
/** Prepare a short-lived Core authorization without exposing it to Browser. */
|
|
5941
|
+
async prepareRootTransfer(request) {
|
|
5942
|
+
const deviceId = this.deviceIds.get(request?.deviceRef);
|
|
5943
|
+
if (deviceId === void 0) return {
|
|
5944
|
+
ok: false,
|
|
5945
|
+
error: failure("invalid-request")
|
|
5946
|
+
};
|
|
5947
|
+
return this.run(async (client) => {
|
|
5948
|
+
const did = await this.requireRootTransferRecipient(client, deviceId);
|
|
5949
|
+
const preparation = await client.prepareRootKeyTransfer(deviceId);
|
|
5950
|
+
if (preparation.recipient.did !== did || preparation.recipient.deviceId !== deviceId) throw Object.assign(/* @__PURE__ */ new Error("root transfer recipient mismatch"), {
|
|
5951
|
+
name: "AwikiSdkError",
|
|
5952
|
+
code: "remote"
|
|
5953
|
+
});
|
|
5954
|
+
for (const [reference, pending] of this.rootTransfers) if (pending.deviceId === deviceId) this.rootTransfers.delete(reference);
|
|
5955
|
+
const transferRef = `root-transfer-${randomUUID()}`;
|
|
5956
|
+
this.rootTransfers.set(transferRef, {
|
|
5957
|
+
authorizationHandle: preparation.authorizationHandle,
|
|
5958
|
+
deviceId,
|
|
5959
|
+
deviceRef: request.deviceRef,
|
|
5960
|
+
did
|
|
5961
|
+
});
|
|
5962
|
+
return {
|
|
5963
|
+
transferRef,
|
|
5964
|
+
deviceRef: request.deviceRef,
|
|
5965
|
+
expiresAt: preparation.expiresAt
|
|
5966
|
+
};
|
|
5967
|
+
});
|
|
5968
|
+
}
|
|
5969
|
+
/** Authenticate locally, recheck fresh context, then consume one exact Core authorization. */
|
|
5970
|
+
async confirmRootTransfer(request) {
|
|
5971
|
+
const pending = this.rootTransfers.get(request?.transferRef);
|
|
5972
|
+
if (pending === void 0) return {
|
|
5973
|
+
ok: false,
|
|
5974
|
+
error: failure("invalid-request")
|
|
5975
|
+
};
|
|
5976
|
+
this.rootTransfers.delete(request.transferRef);
|
|
5977
|
+
return this.run(async (client) => {
|
|
5978
|
+
if (!await client.confirmUserPresence("Grant AWiki device management access")) throw Object.assign(/* @__PURE__ */ new Error("user presence denied"), {
|
|
5979
|
+
name: "AwikiSdkError",
|
|
5980
|
+
code: "forbidden"
|
|
5981
|
+
});
|
|
5982
|
+
if (await this.requireRootTransferRecipient(client, pending.deviceId) !== pending.did) throw Object.assign(/* @__PURE__ */ new Error("root transfer identity changed"), {
|
|
5983
|
+
name: "AwikiSdkError",
|
|
5984
|
+
code: "forbidden"
|
|
5985
|
+
});
|
|
5986
|
+
const sent = await client.confirmAndSendRootKeyTransfer(pending.authorizationHandle);
|
|
5987
|
+
if (sent.recipientDeviceId !== pending.deviceId) throw Object.assign(/* @__PURE__ */ new Error("root transfer receipt mismatch"), {
|
|
5988
|
+
name: "AwikiSdkError",
|
|
5989
|
+
code: "remote"
|
|
5990
|
+
});
|
|
5991
|
+
return {
|
|
5992
|
+
deviceRef: pending.deviceRef,
|
|
5993
|
+
acceptedAt: sent.acceptedAt
|
|
5994
|
+
};
|
|
5995
|
+
});
|
|
5996
|
+
}
|
|
4016
5997
|
/**
|
|
4017
5998
|
* Update the deployment identity's public WNS display name.
|
|
4018
5999
|
* @param request - replacement display name selected by the user.
|
|
@@ -4068,12 +6049,7 @@ let AwikiService = (() => {
|
|
|
4068
6049
|
ok: false,
|
|
4069
6050
|
error: failure("invalid-request")
|
|
4070
6051
|
};
|
|
4071
|
-
|
|
4072
|
-
if (result.ok && !await this.applyRecoveredSession(result.value)) return {
|
|
4073
|
-
ok: false,
|
|
4074
|
-
error: failure("remote")
|
|
4075
|
-
};
|
|
4076
|
-
return result;
|
|
6052
|
+
return this.run((client) => client.activateRecovery(normalized), { allowSignedOut: true });
|
|
4077
6053
|
}
|
|
4078
6054
|
/** Read durable recovery state before deciding whether a retry is valid. */
|
|
4079
6055
|
async getRecoveryStatus(request) {
|
|
@@ -4082,12 +6058,7 @@ let AwikiService = (() => {
|
|
|
4082
6058
|
ok: false,
|
|
4083
6059
|
error: failure("invalid-request")
|
|
4084
6060
|
};
|
|
4085
|
-
|
|
4086
|
-
if (result.ok && !await this.applyRecoveredSession(result.value)) return {
|
|
4087
|
-
ok: false,
|
|
4088
|
-
error: failure("remote")
|
|
4089
|
-
};
|
|
4090
|
-
return result;
|
|
6061
|
+
return this.run((client) => client.getRecoveryStatus(normalized), { allowSignedOut: true });
|
|
4091
6062
|
}
|
|
4092
6063
|
/** Resume only the exact Core-owned operation selected by the browser. */
|
|
4093
6064
|
async resumeRecovery(request) {
|
|
@@ -4096,12 +6067,23 @@ let AwikiService = (() => {
|
|
|
4096
6067
|
ok: false,
|
|
4097
6068
|
error: failure("invalid-request")
|
|
4098
6069
|
};
|
|
4099
|
-
|
|
4100
|
-
|
|
6070
|
+
return this.run((client) => client.resumeRecovery(normalized), { allowSignedOut: true });
|
|
6071
|
+
}
|
|
6072
|
+
/** Explicitly enter an applied identity; reads and late Core completions never sign in. */
|
|
6073
|
+
async enterRecoveredSession(request) {
|
|
6074
|
+
const normalized = normalizeRecoveryOperation(request);
|
|
6075
|
+
if (normalized === void 0) return {
|
|
4101
6076
|
ok: false,
|
|
4102
|
-
error: failure("
|
|
6077
|
+
error: failure("invalid-request")
|
|
4103
6078
|
};
|
|
4104
|
-
|
|
6079
|
+
const requestGeneration = this.sessionRevision;
|
|
6080
|
+
const result = await this.run((client) => client.getRecoveryStatus(normalized), { allowSignedOut: true });
|
|
6081
|
+
if (!result.ok) return result;
|
|
6082
|
+
if (result.value.phase !== "applied" || !result.value.allowedActions?.includes("activate_identity")) return {
|
|
6083
|
+
ok: false,
|
|
6084
|
+
error: failure("conflict")
|
|
6085
|
+
};
|
|
6086
|
+
return this.applyRecoveredSession(result.value, requestGeneration);
|
|
4105
6087
|
}
|
|
4106
6088
|
/** Discard only a pre-attempt operation; Core rejects post-attempt deletion. */
|
|
4107
6089
|
async discardRecovery(request) {
|
|
@@ -4188,6 +6170,13 @@ let AwikiService = (() => {
|
|
|
4188
6170
|
} : result;
|
|
4189
6171
|
}
|
|
4190
6172
|
/** Read one authoritative versioned member page. */
|
|
6173
|
+
getDisplayProfiles(peers) {
|
|
6174
|
+
if (!Array.isArray(peers) || peers.length > 100 || peers.some((peer) => typeof peer !== "string" || !peer.startsWith("did:"))) return Promise.resolve({
|
|
6175
|
+
ok: false,
|
|
6176
|
+
error: failure("invalid-request")
|
|
6177
|
+
});
|
|
6178
|
+
return this.run((client) => client.getDisplayProfiles(peers));
|
|
6179
|
+
}
|
|
4191
6180
|
listGroupMembers(request) {
|
|
4192
6181
|
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
4193
6182
|
if (groupDid === void 0 || request.cursor !== void 0 && (typeof request.cursor !== "string" || request.cursor.length > 4096) || request.limit !== void 0 && (!Number.isSafeInteger(request.limit) || request.limit < 1 || request.limit > 100)) return Promise.resolve({
|
|
@@ -4479,9 +6468,11 @@ let AwikiService = (() => {
|
|
|
4479
6468
|
error: failure("invalid-request")
|
|
4480
6469
|
};
|
|
4481
6470
|
}
|
|
6471
|
+
const requestGeneration = this.sessionRevision;
|
|
4482
6472
|
return this.run(async (client) => {
|
|
4483
|
-
|
|
4484
|
-
|
|
6473
|
+
const page = normalized.folder === "sent" ? await this.mailListClient.listOutbound(normalized) : await client.listMailInbox(normalized);
|
|
6474
|
+
if (this.sessionRevision !== requestGeneration) throw new AwikiSdkError("network");
|
|
6475
|
+
return page;
|
|
4485
6476
|
});
|
|
4486
6477
|
}
|
|
4487
6478
|
/** Read one bounded plain-text mail message. */
|
|
@@ -4495,15 +6486,7 @@ let AwikiService = (() => {
|
|
|
4495
6486
|
error: failure("invalid-request")
|
|
4496
6487
|
};
|
|
4497
6488
|
}
|
|
4498
|
-
return this.run(
|
|
4499
|
-
if (!isLocalSentMailId(normalized.messageId)) return client.readMail(normalized);
|
|
4500
|
-
const local = await this.sentMailStore.read(await this.ownerDid(client), normalized.messageId);
|
|
4501
|
-
if (local === void 0) throw Object.assign(/* @__PURE__ */ new Error("sent mail not found"), {
|
|
4502
|
-
name: "AwikiSdkError",
|
|
4503
|
-
code: "not-found"
|
|
4504
|
-
});
|
|
4505
|
-
return local;
|
|
4506
|
-
});
|
|
6489
|
+
return this.run((client) => client.readMail(normalized));
|
|
4507
6490
|
}
|
|
4508
6491
|
/** Mark explicitly selected mail messages read. Browser callers require an explicit click. */
|
|
4509
6492
|
markMailRead(request) {
|
|
@@ -4520,21 +6503,7 @@ let AwikiService = (() => {
|
|
|
4520
6503
|
error: failure("invalid-request")
|
|
4521
6504
|
};
|
|
4522
6505
|
}
|
|
4523
|
-
return this.run(
|
|
4524
|
-
const result = await client.sendMail(normalized);
|
|
4525
|
-
if (!result.accepted) return result;
|
|
4526
|
-
try {
|
|
4527
|
-
const ownerDid = await this.ownerDid(client);
|
|
4528
|
-
const account = await client.getMailAccount().catch(() => void 0);
|
|
4529
|
-
await this.sentMailStore.append(ownerDid, normalized, result, account);
|
|
4530
|
-
return result;
|
|
4531
|
-
} catch {
|
|
4532
|
-
return result.warnings.length >= 100 ? result : {
|
|
4533
|
-
...result,
|
|
4534
|
-
warnings: [...result.warnings, "Sent history could not be saved locally."]
|
|
4535
|
-
};
|
|
4536
|
-
}
|
|
4537
|
-
});
|
|
6506
|
+
return this.run((client) => client.sendMail(normalized));
|
|
4538
6507
|
}
|
|
4539
6508
|
/**
|
|
4540
6509
|
* Permanently remove the exact SDK-owned local state after an explicit browser acknowledgement.
|
|
@@ -4547,17 +6516,19 @@ let AwikiService = (() => {
|
|
|
4547
6516
|
ok: false,
|
|
4548
6517
|
error: failure("invalid-request")
|
|
4549
6518
|
});
|
|
6519
|
+
this.sessionRevision += 1;
|
|
4550
6520
|
return this.mutateSession(async () => {
|
|
4551
6521
|
const provider = this.provider;
|
|
4552
6522
|
if (provider !== void 0) await this.stopProviderRuntime(provider);
|
|
4553
|
-
|
|
6523
|
+
let result = await this.run((client) => client.clearLocalData(), { allowSignedOut: true });
|
|
6524
|
+
if (!result.ok && result.error.code === "remote") result = await this.run((client) => client.clearLocalData(), { allowSignedOut: true });
|
|
4554
6525
|
if (!result.ok) {
|
|
4555
6526
|
if (provider !== void 0 && this.provider === provider) this.ensureProviderRuntime(provider);
|
|
4556
6527
|
return result;
|
|
4557
6528
|
}
|
|
4558
6529
|
try {
|
|
4559
6530
|
await this.imageAttachmentCache.clear();
|
|
4560
|
-
await this.
|
|
6531
|
+
await clearLegacySentMailCache(this.activeClientOptions?.stateRoot ?? this.resolved.stateRoot);
|
|
4561
6532
|
await this.conversationPreferenceStore.clear();
|
|
4562
6533
|
await this.sessionStore.signIn();
|
|
4563
6534
|
this.signedOut = false;
|
|
@@ -4576,23 +6547,64 @@ let AwikiService = (() => {
|
|
|
4576
6547
|
});
|
|
4577
6548
|
}
|
|
4578
6549
|
/** Re-enter only after Core confirms that the exact recovered identity is applied locally. */
|
|
4579
|
-
async applyRecoveredSession(progress) {
|
|
4580
|
-
if (progress.phase !== "applied") return
|
|
6550
|
+
async applyRecoveredSession(progress, requestGeneration) {
|
|
6551
|
+
if (progress.phase !== "applied") return {
|
|
6552
|
+
ok: true,
|
|
6553
|
+
value: progress
|
|
6554
|
+
};
|
|
4581
6555
|
return this.mutateSession(async () => {
|
|
4582
6556
|
const provider = this.provider;
|
|
4583
|
-
if (provider === void 0) return
|
|
6557
|
+
if (provider === void 0) return {
|
|
6558
|
+
ok: false,
|
|
6559
|
+
error: failure("remote")
|
|
6560
|
+
};
|
|
6561
|
+
if (this.sessionRevision !== requestGeneration) return {
|
|
6562
|
+
ok: false,
|
|
6563
|
+
error: {
|
|
6564
|
+
...failure("remote"),
|
|
6565
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(this.activeIdentityDid === progress.currentDid, "replaced")
|
|
6566
|
+
}
|
|
6567
|
+
};
|
|
4584
6568
|
try {
|
|
4585
6569
|
const identity = await provider.client.getIdentity();
|
|
4586
|
-
|
|
6570
|
+
const currentPrincipalMatchesRecovery = identity !== null && identity.did === progress.currentDid;
|
|
6571
|
+
if (this.provider !== provider || this.sessionRevision !== requestGeneration) return {
|
|
6572
|
+
ok: false,
|
|
6573
|
+
error: {
|
|
6574
|
+
...failure("remote"),
|
|
6575
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(currentPrincipalMatchesRecovery, "replaced")
|
|
6576
|
+
}
|
|
6577
|
+
};
|
|
6578
|
+
if (!currentPrincipalMatchesRecovery) return {
|
|
6579
|
+
ok: false,
|
|
6580
|
+
error: {
|
|
6581
|
+
...failure("remote"),
|
|
6582
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(false, "current")
|
|
6583
|
+
}
|
|
6584
|
+
};
|
|
4587
6585
|
const alreadyActive = this.signedOut === false && this.activeIdentityDid === identity.did;
|
|
4588
6586
|
if (!alreadyActive) {
|
|
4589
6587
|
await this.sessionStore.signIn();
|
|
6588
|
+
if (this.provider !== provider || this.sessionRevision !== requestGeneration) return {
|
|
6589
|
+
ok: false,
|
|
6590
|
+
error: {
|
|
6591
|
+
...failure("remote"),
|
|
6592
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(true, "replaced")
|
|
6593
|
+
}
|
|
6594
|
+
};
|
|
4590
6595
|
this.signedOut = false;
|
|
4591
6596
|
this.activeIdentityDid = identity.did;
|
|
4592
6597
|
this.invalidateSummaries();
|
|
6598
|
+
requestGeneration = this.sessionRevision;
|
|
4593
6599
|
}
|
|
4594
|
-
const
|
|
4595
|
-
if (this.provider !== provider) return
|
|
6600
|
+
const mailRecoveryObservability = await this.reconcileRecoveredMailbox(provider);
|
|
6601
|
+
if (this.provider !== provider || this.sessionRevision !== requestGeneration) return {
|
|
6602
|
+
ok: false,
|
|
6603
|
+
error: {
|
|
6604
|
+
...failure("remote"),
|
|
6605
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(true, "replaced")
|
|
6606
|
+
}
|
|
6607
|
+
};
|
|
4596
6608
|
if (!alreadyActive) {
|
|
4597
6609
|
const session = {
|
|
4598
6610
|
status: "active",
|
|
@@ -4601,49 +6613,38 @@ let AwikiService = (() => {
|
|
|
4601
6613
|
this.publishSession(session);
|
|
4602
6614
|
this.ensureProviderRuntime(provider);
|
|
4603
6615
|
}
|
|
4604
|
-
return
|
|
6616
|
+
return {
|
|
6617
|
+
ok: true,
|
|
6618
|
+
value: {
|
|
6619
|
+
...progress,
|
|
6620
|
+
mailRecoveryObservability
|
|
6621
|
+
}
|
|
6622
|
+
};
|
|
4605
6623
|
} catch {
|
|
4606
|
-
return
|
|
6624
|
+
return {
|
|
6625
|
+
ok: false,
|
|
6626
|
+
error: failure("remote")
|
|
6627
|
+
};
|
|
4607
6628
|
}
|
|
4608
6629
|
});
|
|
4609
6630
|
}
|
|
4610
|
-
/** Rebind Mail first-use ownership and
|
|
4611
|
-
async
|
|
4612
|
-
const logger = this.ctx.logger("awiki-recovery");
|
|
4613
|
-
let mailboxRestored = false;
|
|
6631
|
+
/** Rebind Mail first-use ownership and retain only its closed, secret-free classification. */
|
|
6632
|
+
async reconcileRecoveredMailbox(provider) {
|
|
4614
6633
|
try {
|
|
4615
6634
|
await provider.client.getMailAccount();
|
|
4616
|
-
|
|
4617
|
-
} catch {
|
|
4618
|
-
|
|
4619
|
-
}
|
|
4620
|
-
const target = this.recoveryReconciliationTarget;
|
|
4621
|
-
if (target === void 0) return mailboxRestored;
|
|
4622
|
-
try {
|
|
4623
|
-
const authority = await provider.client.issueRecoveryAttestation({ operationId });
|
|
4624
|
-
if (!await acceptsRecoveryReconciliation(await this.externalHttpAuth.dispatch(new Request(target.endpoint, {
|
|
4625
|
-
method: "POST",
|
|
4626
|
-
headers: { "content-type": "application/json" },
|
|
4627
|
-
body: JSON.stringify({ attestation: authority.attestation })
|
|
4628
|
-
}), (request) => fetch(request)))) {
|
|
4629
|
-
logger.warn("awiki: recovered model account reconciliation is pending");
|
|
4630
|
-
return false;
|
|
4631
|
-
}
|
|
4632
|
-
return mailboxRestored;
|
|
4633
|
-
} catch {
|
|
4634
|
-
logger.warn("awiki: recovered model account reconciliation is pending");
|
|
4635
|
-
return false;
|
|
6635
|
+
return successfulMailRecoveryObservability();
|
|
6636
|
+
} catch (error) {
|
|
6637
|
+
return failedMailRecoveryObservability(error);
|
|
4636
6638
|
}
|
|
4637
6639
|
}
|
|
4638
6640
|
/** Select the only resumable new-device session; Core local_sessions is the sole restart SoT. */
|
|
4639
6641
|
async selectDeviceJoinSession(client) {
|
|
4640
|
-
const
|
|
6642
|
+
const resumable = (await client.listLocalDeviceJoinSessions()).filter((value) => value.side === "new_device" && RESUMABLE_JOIN_PHASES.has(value.localPhase));
|
|
4641
6643
|
if (this.activeDeviceJoinSessionId !== void 0) {
|
|
4642
|
-
const tracked =
|
|
6644
|
+
const tracked = resumable.find((value) => value.joinSessionId === this.activeDeviceJoinSessionId);
|
|
4643
6645
|
if (tracked !== void 0) return tracked.joinSessionId;
|
|
4644
6646
|
this.activeDeviceJoinSessionId = void 0;
|
|
4645
6647
|
}
|
|
4646
|
-
const resumable = sessions.filter((value) => value.side === "new_device" && RESUMABLE_JOIN_PHASES.has(value.localPhase));
|
|
4647
6648
|
if (resumable.length > 1) throw Object.assign(/* @__PURE__ */ new Error("multiple local joins"), {
|
|
4648
6649
|
name: "AwikiSdkError",
|
|
4649
6650
|
code: "conflict"
|
|
@@ -4710,6 +6711,25 @@ let AwikiService = (() => {
|
|
|
4710
6711
|
code: "forbidden"
|
|
4711
6712
|
});
|
|
4712
6713
|
}
|
|
6714
|
+
async requireRootTransferRecipient(client, deviceId) {
|
|
6715
|
+
if (!client.trustedUserPresenceSupported) throw Object.assign(/* @__PURE__ */ new Error("trusted user presence unsupported"), {
|
|
6716
|
+
name: "AwikiSdkError",
|
|
6717
|
+
code: "forbidden"
|
|
6718
|
+
});
|
|
6719
|
+
await this.requireDeviceManager(client);
|
|
6720
|
+
await client.syncDeviceManagement();
|
|
6721
|
+
const identity = await client.getIdentity();
|
|
6722
|
+
if (identity === null) throw Object.assign(/* @__PURE__ */ new Error("identity required"), {
|
|
6723
|
+
name: "AwikiSdkError",
|
|
6724
|
+
code: "not-registered"
|
|
6725
|
+
});
|
|
6726
|
+
const target = (await client.getDeviceRegistry()).find((device) => device.deviceId === deviceId);
|
|
6727
|
+
if (target === void 0 || target.isCurrent || target.status !== "active" || target.role !== "member" || target.managementReady) throw Object.assign(/* @__PURE__ */ new Error("root transfer recipient unavailable"), {
|
|
6728
|
+
name: "AwikiSdkError",
|
|
6729
|
+
code: "forbidden"
|
|
6730
|
+
});
|
|
6731
|
+
return identity.did;
|
|
6732
|
+
}
|
|
4713
6733
|
requestRef(joinSessionId) {
|
|
4714
6734
|
const existing = this.requestRefs.get(joinSessionId);
|
|
4715
6735
|
if (existing !== void 0) return existing;
|
|
@@ -4734,8 +6754,10 @@ let AwikiService = (() => {
|
|
|
4734
6754
|
this.requestSessions.clear();
|
|
4735
6755
|
this.deviceRefs.clear();
|
|
4736
6756
|
this.deviceIds.clear();
|
|
6757
|
+
this.rootTransfers.clear();
|
|
4737
6758
|
return {
|
|
4738
6759
|
canManage: false,
|
|
6760
|
+
rootTransferSupported: client.trustedUserPresenceSupported,
|
|
4739
6761
|
...current.role === void 0 ? {} : { role: current.role },
|
|
4740
6762
|
readiness: current.readiness,
|
|
4741
6763
|
devices: [],
|
|
@@ -4744,11 +6766,20 @@ let AwikiService = (() => {
|
|
|
4744
6766
|
}
|
|
4745
6767
|
const requests = await client.listLocalDeviceJoinRequests();
|
|
4746
6768
|
await Promise.all(requests.filter((request) => request.claimedByCurrentDevice).map((request) => client.getLocalDeviceJoinVerificationProgress(request.joinSessionId).catch(() => void 0)));
|
|
6769
|
+
const devices = await client.getDeviceRegistry();
|
|
6770
|
+
const identity = await client.getIdentity();
|
|
6771
|
+
const joinedAtByDeviceId = /* @__PURE__ */ new Map();
|
|
6772
|
+
for (const request of requests) {
|
|
6773
|
+
const previous = joinedAtByDeviceId.get(request.protocolDeviceId);
|
|
6774
|
+
if (previous === void 0 || request.issuedAt > previous) joinedAtByDeviceId.set(request.protocolDeviceId, request.issuedAt);
|
|
6775
|
+
}
|
|
6776
|
+
const initialDeviceJoinedAt = identity === null ? void 0 : new Date(identity.registeredAt).toISOString();
|
|
4747
6777
|
return {
|
|
4748
6778
|
canManage: true,
|
|
6779
|
+
rootTransferSupported: client.trustedUserPresenceSupported,
|
|
4749
6780
|
role: "admin",
|
|
4750
6781
|
readiness: "admin_ready",
|
|
4751
|
-
devices: (
|
|
6782
|
+
devices: devices.filter((device) => device.status === "active").map((device) => this.publicDevice(device, joinedAtByDeviceId.get(device.deviceId) ?? (device.isCurrent ? initialDeviceJoinedAt : void 0))),
|
|
4752
6783
|
requests: requests.map((request) => ({
|
|
4753
6784
|
requestRef: this.requestRef(request.joinSessionId),
|
|
4754
6785
|
candidateKeyFingerprint: request.candidateKeyFingerprint,
|
|
@@ -4760,9 +6791,12 @@ let AwikiService = (() => {
|
|
|
4760
6791
|
}))
|
|
4761
6792
|
};
|
|
4762
6793
|
}
|
|
4763
|
-
publicDevice(device) {
|
|
6794
|
+
publicDevice(device, joinedAt) {
|
|
6795
|
+
const fingerprint = createHash("sha256").update(device.deviceId).digest("hex").slice(0, 8).toUpperCase();
|
|
4764
6796
|
return {
|
|
4765
6797
|
deviceRef: this.deviceRef(device.deviceId),
|
|
6798
|
+
displayId: `${fingerprint.slice(0, 4)}-${fingerprint.slice(4)}`,
|
|
6799
|
+
...joinedAt === void 0 ? {} : { joinedAt },
|
|
4766
6800
|
status: device.status,
|
|
4767
6801
|
role: device.role,
|
|
4768
6802
|
managementReady: device.managementReady,
|
|
@@ -4788,6 +6822,7 @@ let AwikiService = (() => {
|
|
|
4788
6822
|
this.requestSessions.clear();
|
|
4789
6823
|
this.deviceRefs.clear();
|
|
4790
6824
|
this.deviceIds.clear();
|
|
6825
|
+
this.rootTransfers.clear();
|
|
4791
6826
|
for (const controller of this.activeSummaryRequests) controller.abort();
|
|
4792
6827
|
this.activeSummaryRequests.clear();
|
|
4793
6828
|
}
|
|
@@ -4809,6 +6844,11 @@ let AwikiService = (() => {
|
|
|
4809
6844
|
/** Invoke the current client and normalize every rejection to a public result. */
|
|
4810
6845
|
async run(operation, options = {}) {
|
|
4811
6846
|
try {
|
|
6847
|
+
const generation = this.runtimeGeneration;
|
|
6848
|
+
if (this.getUpdatePolicyStatus().restricted) return {
|
|
6849
|
+
ok: false,
|
|
6850
|
+
error: failure("client-version-unsupported")
|
|
6851
|
+
};
|
|
4812
6852
|
if (options.allowSignedOut !== true && await this.isSignedOut()) return {
|
|
4813
6853
|
ok: false,
|
|
4814
6854
|
error: failure("signed-out")
|
|
@@ -4816,6 +6856,10 @@ let AwikiService = (() => {
|
|
|
4816
6856
|
const provider = this.provider;
|
|
4817
6857
|
if (provider === void 0) throw new ProviderUnavailableError();
|
|
4818
6858
|
const value = await operation(provider.client);
|
|
6859
|
+
if (this.runtimeGeneration !== generation || this.provider !== provider) return {
|
|
6860
|
+
ok: false,
|
|
6861
|
+
error: failure("conflict")
|
|
6862
|
+
};
|
|
4819
6863
|
if (options.skipAttachmentByteValidation !== true && containsUnexpectedBinary(value, /* @__PURE__ */ new Set())) return {
|
|
4820
6864
|
ok: false,
|
|
4821
6865
|
error: failure("remote")
|
|
@@ -4831,6 +6875,9 @@ let AwikiService = (() => {
|
|
|
4831
6875
|
};
|
|
4832
6876
|
}
|
|
4833
6877
|
}
|
|
6878
|
+
assertVersionSupported() {
|
|
6879
|
+
if (this.getUpdatePolicyStatus().restricted) throw new Error("awiki: this plugin version must be updated before using the active tenant");
|
|
6880
|
+
}
|
|
4834
6881
|
/** Validate mail input before entering the provider and preserve fixed public failures. */
|
|
4835
6882
|
runValidatedMail(validate, operation) {
|
|
4836
6883
|
let request;
|
|
@@ -4883,12 +6930,22 @@ let AwikiService = (() => {
|
|
|
4883
6930
|
}
|
|
4884
6931
|
/** Serialize sign-in, sign-out, and destructive clear transitions. */
|
|
4885
6932
|
mutateSession(operation) {
|
|
4886
|
-
const
|
|
6933
|
+
const generation = this.runtimeGeneration;
|
|
6934
|
+
if (this.tenantSwitching) return Promise.resolve({
|
|
6935
|
+
ok: false,
|
|
6936
|
+
error: failure("conflict")
|
|
6937
|
+
});
|
|
6938
|
+
const invoke = () => this.tenantSwitching || generation !== this.runtimeGeneration ? Promise.resolve({
|
|
6939
|
+
ok: false,
|
|
6940
|
+
error: failure("conflict")
|
|
6941
|
+
}) : operation();
|
|
6942
|
+
const pending = this.sessionMutation.then(invoke, invoke);
|
|
4887
6943
|
this.sessionMutation = pending.then(() => void 0, () => void 0);
|
|
4888
6944
|
return pending;
|
|
4889
6945
|
}
|
|
4890
6946
|
/** Start identity realtime and the optional Agent consumer without blocking identity success. */
|
|
4891
6947
|
ensureProviderRuntime(provider) {
|
|
6948
|
+
if (this.tenantSwitching) return;
|
|
4892
6949
|
const identityDid = this.activeIdentityDid;
|
|
4893
6950
|
if (provider.runtimeReplacement !== void 0) return;
|
|
4894
6951
|
if (identityDid !== void 0 && provider.realtimeIdentityDid !== void 0 && provider.realtimeIdentityDid !== identityDid) {
|
|
@@ -4914,6 +6971,7 @@ let AwikiService = (() => {
|
|
|
4914
6971
|
provider.runtimeReplacement = observed;
|
|
4915
6972
|
}
|
|
4916
6973
|
ensureRealtimeSupervisor(provider) {
|
|
6974
|
+
if (this.getUpdatePolicyStatus().restricted) return;
|
|
4917
6975
|
if (!this.resolved.realtimeEnabled || provider.realtimeSupervisor !== void 0 || provider.realtimeStartup !== void 0 || this.provider !== provider || (provider.client.realtime ?? provider.client.listener) === void 0) return;
|
|
4918
6976
|
const generation = provider.realtimeGeneration;
|
|
4919
6977
|
const source = provider.client.realtime ?? provider.client.listener;
|
|
@@ -4959,6 +7017,7 @@ let AwikiService = (() => {
|
|
|
4959
7017
|
await provider.agentConsumer?.reconcileOnce();
|
|
4960
7018
|
}
|
|
4961
7019
|
ensureAgentConsumer(provider) {
|
|
7020
|
+
if (this.getUpdatePolicyStatus().restricted) return;
|
|
4962
7021
|
const workspaceContext = this.workspaceContext;
|
|
4963
7022
|
const source = provider.client.agentInbox ?? provider.client.listener;
|
|
4964
7023
|
const identityDid = this.activeIdentityDid;
|
|
@@ -4973,6 +7032,7 @@ let AwikiService = (() => {
|
|
|
4973
7032
|
const agents = new DshAwikiListenerAgentRuntime(workspaceContext, this.resolved.listener.workspacePath);
|
|
4974
7033
|
const consumer = new AwikiAgentListener(source, agents, {
|
|
4975
7034
|
...this.resolved.listener,
|
|
7035
|
+
stateRoot: this.activeClientOptions?.stateRoot ?? this.resolved.listener.stateRoot,
|
|
4976
7036
|
identityScope: identityDid
|
|
4977
7037
|
}, logger);
|
|
4978
7038
|
provider.agentConsumer = consumer;
|
|
@@ -5047,4 +7107,4 @@ function containsUnexpectedBinary(value, seen) {
|
|
|
5047
7107
|
return false;
|
|
5048
7108
|
}
|
|
5049
7109
|
//#endregion
|
|
5050
|
-
export { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_DOMAIN_FIELD, AWIKI_EXTERNAL_HTTP_MAX_BODY_BYTES, AWIKI_HISTORY_TOOL, AWIKI_IDENTITY_STATUS_TOOL, AWIKI_LIST_CONVERSATIONS_TOOL, AWIKI_LOGOUT_CONFIRMATION, AWIKI_MAIL_ACCOUNT_TOOL, AWIKI_MAIL_INBOX_TOOL, AWIKI_MAIL_MARK_READ_TOOL, AWIKI_MAIL_READ_TOOL, AWIKI_MAIL_SEND_TOOL, AWIKI_SEND_ATTACHMENT_TOOL, AWIKI_SEND_MESSAGE_TOOL, AWIKI_SETTINGS_NAMESPACE, AwikiExternalHttpAuthError, AwikiService, AwikiService as default, AwikiSettingsSchema, Config, DEFAULT_ATTACHMENT_MAX_BYTES, DEFAULT_AWIKI_DOMAIN, DEFAULT_AWIKI_MESSAGE_SERVICE_DID, DEFAULT_AWIKI_SERVICE_URL, DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES, DEFAULT_POLL_INTERVAL_MS, DEFAULT_SUMMARY_MAX_INPUT_BYTES, MAX_SUMMARY_MESSAGES, normalizeAwikiDomain, validateAwikiSettings };
|
|
7110
|
+
export { AWIKI_CHINA_TENANT_ID, AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_DOMAIN_FIELD, AWIKI_EXTERNAL_HTTP_MAX_BODY_BYTES, AWIKI_GLOBAL_TENANT_ID, AWIKI_HISTORY_TOOL, AWIKI_IDENTITY_STATUS_TOOL, AWIKI_LIST_CONVERSATIONS_TOOL, AWIKI_LOGOUT_CONFIRMATION, AWIKI_MAIL_ACCOUNT_TOOL, AWIKI_MAIL_INBOX_TOOL, AWIKI_MAIL_MARK_READ_TOOL, AWIKI_MAIL_READ_TOOL, AWIKI_MAIL_SEND_TOOL, AWIKI_OFFICIAL_CATALOG_VERSION, AWIKI_SEND_ATTACHMENT_TOOL, AWIKI_SEND_MESSAGE_TOOL, AWIKI_SETTINGS_NAMESPACE, AWIKI_TENANT_REGISTRY_SCHEMA_VERSION, AwikiExternalHttpAuthError, AwikiService, AwikiService as default, AwikiSettingsSchema, Config, DEFAULT_ATTACHMENT_MAX_BYTES, DEFAULT_AWIKI_DOMAIN, DEFAULT_AWIKI_MESSAGE_SERVICE_DID, DEFAULT_AWIKI_SERVICE_URL, DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES, DEFAULT_POLL_INTERVAL_MS, DEFAULT_SUMMARY_MAX_INPUT_BYTES, DSH_AWIKI_MODEL_PROXY_VERSION, DSH_AWIKI_VERSION, MAX_SUMMARY_MESSAGES, compareVersions as compareAwikiPluginVersions, normalizeAwikiDomain, validateAwikiSettings };
|