@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/types/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Unified AWiki identity, messaging, attachment, Remote, and model-tool service. */
|
|
2
1
|
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
3
2
|
var useValue = arguments.length > 2;
|
|
4
3
|
for (var i = 0; i < initializers.length; i++) {
|
|
@@ -33,6 +32,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
33
32
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
34
33
|
done = true;
|
|
35
34
|
};
|
|
35
|
+
import { decodeDesktopDistribution } from "./desktop-distribution.js";
|
|
36
|
+
/** Unified AWiki identity, messaging, attachment, Remote, and model-tool service. */
|
|
36
37
|
import { Context } from '@deepseek-ai/cordis';
|
|
37
38
|
import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
38
39
|
import { homedir } from 'node:os';
|
|
@@ -42,20 +43,29 @@ import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
|
42
43
|
import { settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
43
44
|
import { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "./types.js";
|
|
44
45
|
import { AwikiExternalHttpAuthError, createAwikiExternalHttpAuth, externalHttpAuthError, mapProviderError as mapExternalHttpProviderError, } from "./external-http-auth.js";
|
|
45
|
-
import {
|
|
46
|
+
import { AwikiIntegrationClient } from "./integration-client.js";
|
|
47
|
+
import { clearLegacySentMailCache } from "./legacy-sent-mail-cache.js";
|
|
48
|
+
import { AwikiMailListClient } from "./mail-list-client.js";
|
|
49
|
+
import { AwikiSdkError, downloadedAttachment } from "./sdk-adapter.js";
|
|
50
|
+
import { failedMailRecoveryObservability, fencedMailRecoveryObservability, successfulMailRecoveryObservability, } from "./mail-recovery-observability.js";
|
|
46
51
|
import { registerAwikiTools } from "./tools.js";
|
|
47
52
|
import { mailInboxRequest, mailMarkReadRequest, mailReadRequest, mailSendRequest, } from "./mail.js";
|
|
48
53
|
import { AwikiSettingsSchema, validateAwikiSettings, } from "./settings.js";
|
|
49
54
|
import { AWIKI_SETTINGS_NAMESPACE, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain } from "./domain.js";
|
|
55
|
+
import { AWIKI_DEFAULT_BUILTIN_TENANT, } from "./builtin-tenant-config.js";
|
|
50
56
|
import { AWIKI_SETTINGS_RPC_CHANNEL } from "./settings-rpc-contract.js";
|
|
51
57
|
import { createAwikiSettingsRpcHandler } from "./settings-rpc.js";
|
|
52
58
|
import { AwikiSessionStore } from "./session.js";
|
|
53
59
|
import { AwikiImageAttachmentCache, minimumImageAttachmentCacheMaxBytes } from "./attachment-cache.js";
|
|
54
|
-
import { AwikiSentMailStore, isLocalSentMailId } from "./sent-mail-store.js";
|
|
55
60
|
import { AwikiConversationPreferenceStore, normalizeConversationPreferenceMutation, } from "./conversation-preferences.js";
|
|
56
61
|
import { AwikiAgentListener, DshAwikiListenerAgentRuntime, } from "./listener.js";
|
|
57
62
|
import { IdentityRealtimeSupervisor, } from "./realtime-supervisor.js";
|
|
63
|
+
import { resolveAwikiStateRoot } from "./profile-state.js";
|
|
64
|
+
import { AwikiTenantRegistry, } from "./tenant-registry.js";
|
|
65
|
+
import { checkAwikiUpdatePolicy, readAwikiUpdatePolicyStatus, DSH_AWIKI_VERSION, } from "./update-policy.js";
|
|
58
66
|
export { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "./types.js";
|
|
67
|
+
export { AWIKI_CHINA_TENANT_ID, AWIKI_GLOBAL_TENANT_ID, AWIKI_OFFICIAL_CATALOG_VERSION, AWIKI_TENANT_REGISTRY_SCHEMA_VERSION, } from "./tenant-registry.js";
|
|
68
|
+
export { DSH_AWIKI_MODEL_PROXY_VERSION, DSH_AWIKI_VERSION, compareVersions as compareAwikiPluginVersions, } from "./update-policy.js";
|
|
59
69
|
export { AWIKI_EXTERNAL_HTTP_MAX_BODY_BYTES, AwikiExternalHttpAuthError, } from "./external-http-auth.js";
|
|
60
70
|
export { AWIKI_DOMAIN_FIELD, AWIKI_SETTINGS_NAMESPACE, AwikiSettingsSchema, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain, validateAwikiSettings, } from "./settings.js";
|
|
61
71
|
export { AWIKI_HISTORY_TOOL, AWIKI_IDENTITY_STATUS_TOOL, AWIKI_LIST_CONVERSATIONS_TOOL, 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, } from "./tools.js";
|
|
@@ -65,10 +75,10 @@ export const DEFAULT_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
|
65
75
|
export const DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES = 64 * 1024 * 1024;
|
|
66
76
|
/** Default browser polling interval while the AWiki drawer is open. */
|
|
67
77
|
export const DEFAULT_POLL_INTERVAL_MS = 3_000;
|
|
68
|
-
/**
|
|
69
|
-
export const DEFAULT_AWIKI_SERVICE_URL =
|
|
70
|
-
/**
|
|
71
|
-
export const DEFAULT_AWIKI_MESSAGE_SERVICE_DID =
|
|
78
|
+
/** Package-configured primary AWiki service origin. */
|
|
79
|
+
export const DEFAULT_AWIKI_SERVICE_URL = AWIKI_DEFAULT_BUILTIN_TENANT.backendOrigin;
|
|
80
|
+
/** Package-configured primary authoritative AWiki message-service DID. */
|
|
81
|
+
export const DEFAULT_AWIKI_MESSAGE_SERVICE_DID = `did:wba:${AWIKI_DEFAULT_BUILTIN_TENANT.didHost}`;
|
|
72
82
|
/** Host-owned model input cap after message minimization. */
|
|
73
83
|
export const DEFAULT_SUMMARY_MAX_INPUT_BYTES = 32 * 1024;
|
|
74
84
|
/** Hard limit for one user-triggered conversation summary. */
|
|
@@ -77,6 +87,7 @@ export const MAX_SUMMARY_MESSAGES = 50;
|
|
|
77
87
|
export const Config = z.object({
|
|
78
88
|
userServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
79
89
|
userServiceDomain: z.string().default(DEFAULT_AWIKI_DOMAIN),
|
|
90
|
+
legacyTenantSlot: z.string(),
|
|
80
91
|
messageServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
81
92
|
mailServiceUrl: z.string(),
|
|
82
93
|
messageServicePublicUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
@@ -94,6 +105,7 @@ export const Config = z.object({
|
|
|
94
105
|
summaryMaxInputBytes: z.number().default(DEFAULT_SUMMARY_MAX_INPUT_BYTES),
|
|
95
106
|
});
|
|
96
107
|
const FAILURE_CODES = new Set([
|
|
108
|
+
'client-version-unsupported',
|
|
97
109
|
'not-registered',
|
|
98
110
|
'signed-out',
|
|
99
111
|
'already-registered',
|
|
@@ -103,6 +115,7 @@ const FAILURE_CODES = new Set([
|
|
|
103
115
|
'handle-unavailable',
|
|
104
116
|
'not-found',
|
|
105
117
|
'forbidden',
|
|
118
|
+
'device-rejoin-required',
|
|
106
119
|
'identity-recovery-required',
|
|
107
120
|
'conflict',
|
|
108
121
|
'rate-limited',
|
|
@@ -127,9 +140,9 @@ const RESUMABLE_JOIN_PHASES = new Set([
|
|
|
127
140
|
'response_prepared',
|
|
128
141
|
'response_verified',
|
|
129
142
|
'approval_prepared',
|
|
130
|
-
'authorized',
|
|
131
143
|
]);
|
|
132
144
|
const FAILURE_MESSAGES = {
|
|
145
|
+
'client-version-unsupported': 'This AWiki plugin version is not supported by the active tenant. Update the plugin or switch tenants.',
|
|
133
146
|
'not-registered': 'No AWiki identity is registered for this deployment.',
|
|
134
147
|
'signed-out': 'This installation is signed out of AWiki.',
|
|
135
148
|
'already-registered': 'This deployment already has an AWiki identity.',
|
|
@@ -139,6 +152,7 @@ const FAILURE_MESSAGES = {
|
|
|
139
152
|
'handle-unavailable': 'The requested AWiki handle is unavailable.',
|
|
140
153
|
'not-found': 'The requested AWiki resource was not found.',
|
|
141
154
|
'forbidden': 'The AWiki operation is not permitted.',
|
|
155
|
+
'device-rejoin-required': 'This device credential was revoked and must join the Handle again.',
|
|
142
156
|
'identity-recovery-required': 'The local AWiki identity must be recovered before it can be used again.',
|
|
143
157
|
'conflict': 'The AWiki operation conflicts with current state.',
|
|
144
158
|
'rate-limited': 'The AWiki service rate-limited the request.',
|
|
@@ -237,41 +251,25 @@ function serviceUrl(field, raw, allowInsecureLoopbackForTesting) {
|
|
|
237
251
|
}
|
|
238
252
|
return raw;
|
|
239
253
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
if (
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
return new URL('/api/identity-recovery', parsed).toString();
|
|
251
|
-
}
|
|
252
|
-
/** Accept only the closed Model Proxy success response; no ledger identifier may cross back. */
|
|
253
|
-
async function acceptsRecoveryReconciliation(response) {
|
|
254
|
-
if (!response.ok)
|
|
255
|
-
return false;
|
|
256
|
-
let value;
|
|
254
|
+
function publishedServiceBaseUrl(services, name, allowInsecureLoopbackForTesting) {
|
|
255
|
+
if (typeof services !== 'object' || services === null || Array.isArray(services))
|
|
256
|
+
return undefined;
|
|
257
|
+
const candidate = services[name];
|
|
258
|
+
if (typeof candidate !== 'object' || candidate === null || Array.isArray(candidate))
|
|
259
|
+
return undefined;
|
|
260
|
+
const value = candidate;
|
|
261
|
+
if (value.enabled !== true || typeof value.base_url !== 'string')
|
|
262
|
+
return undefined;
|
|
257
263
|
try {
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
264
|
+
const baseUrl = serviceUrl(`server-info.services.${name}.base_url`, value.base_url, allowInsecureLoopbackForTesting);
|
|
265
|
+
const parsed = new URL(baseUrl);
|
|
266
|
+
if (parsed.search !== '')
|
|
267
|
+
return undefined;
|
|
268
|
+
return baseUrl;
|
|
262
269
|
}
|
|
263
270
|
catch {
|
|
264
|
-
return
|
|
271
|
+
return undefined;
|
|
265
272
|
}
|
|
266
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
267
|
-
return false;
|
|
268
|
-
const result = value;
|
|
269
|
-
const keys = Object.keys(result).sort();
|
|
270
|
-
return keys.length === 2
|
|
271
|
-
&& keys[0] === 'idempotent'
|
|
272
|
-
&& keys[1] === 'restored'
|
|
273
|
-
&& result.restored === true
|
|
274
|
-
&& typeof result.idempotent === 'boolean';
|
|
275
273
|
}
|
|
276
274
|
/** Validate a provider domain without inferring it from an API endpoint. */
|
|
277
275
|
function serviceDomain(raw, field = 'userServiceDomain') {
|
|
@@ -320,7 +318,7 @@ function listenerAllowedPeers(raw, enabled) {
|
|
|
320
318
|
return peers;
|
|
321
319
|
}
|
|
322
320
|
/** Resolve and validate every deployment choice before publishing the service. */
|
|
323
|
-
function resolveConfig(config) {
|
|
321
|
+
function resolveConfig(ctx, config) {
|
|
324
322
|
const allowInsecureLoopbackForTesting = config.allowInsecureLoopbackForTesting ?? false;
|
|
325
323
|
const configuredStateRoot = config.stateRoot?.trim();
|
|
326
324
|
const configuredDshHome = process.env.DSH_HOME?.trim();
|
|
@@ -328,7 +326,7 @@ function resolveConfig(config) {
|
|
|
328
326
|
? join(homedir(), '.dsh')
|
|
329
327
|
: configuredDshHome;
|
|
330
328
|
const stateRoot = configuredStateRoot === undefined || configuredStateRoot.length === 0
|
|
331
|
-
?
|
|
329
|
+
? resolveAwikiStateRoot(ctx, dshHome)
|
|
332
330
|
: configuredStateRoot;
|
|
333
331
|
if (!isAbsolute(stateRoot))
|
|
334
332
|
throw new TypeError('awiki: stateRoot must be an absolute path');
|
|
@@ -363,9 +361,16 @@ function resolveConfig(config) {
|
|
|
363
361
|
const messageServiceUrl = serviceUrl('messageServiceUrl', config.messageServiceUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
364
362
|
const mailServiceUrl = serviceUrl('mailServiceUrl', config.mailServiceUrl ?? userServiceUrl, allowInsecureLoopbackForTesting);
|
|
365
363
|
const messageServicePublicUrl = serviceUrl('messageServicePublicUrl', config.messageServicePublicUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
364
|
+
const legacyTenantSlot = config.legacyTenantSlot?.trim();
|
|
365
|
+
if (legacyTenantSlot !== undefined
|
|
366
|
+
&& legacyTenantSlot !== 'primary'
|
|
367
|
+
&& legacyTenantSlot !== 'secondary') {
|
|
368
|
+
throw new TypeError('awiki: legacyTenantSlot must be primary or secondary');
|
|
369
|
+
}
|
|
366
370
|
return {
|
|
367
371
|
userServiceUrl,
|
|
368
372
|
userServiceDomain: serviceDomain(config.userServiceDomain ?? DEFAULT_AWIKI_DOMAIN),
|
|
373
|
+
...legacyTenantSlot === undefined ? {} : { legacyTenantSlot },
|
|
369
374
|
messageServiceUrl,
|
|
370
375
|
mailServiceUrl,
|
|
371
376
|
messageServicePublicUrl,
|
|
@@ -390,6 +395,15 @@ function resolveConfig(config) {
|
|
|
390
395
|
function failure(code) {
|
|
391
396
|
return { code, message: FAILURE_MESSAGES[code] };
|
|
392
397
|
}
|
|
398
|
+
function integrationUnavailable() {
|
|
399
|
+
return {
|
|
400
|
+
ok: false,
|
|
401
|
+
error: {
|
|
402
|
+
code: 'unavailable',
|
|
403
|
+
message: 'Guest messaging is not available for the active AWiki tenant.',
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
393
407
|
/** Normalize SDK and provider failures without returning remote bodies, credentials, or causes. */
|
|
394
408
|
function normalizeFailure(error) {
|
|
395
409
|
if (error instanceof ProviderUnavailableError) {
|
|
@@ -506,7 +520,6 @@ function normalizeRecoveryPrepareRequest(request) {
|
|
|
506
520
|
}
|
|
507
521
|
const IDENTITY_ACCESS_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
508
522
|
const SERVER_INFO_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
509
|
-
const RECOVERY_RECONCILIATION_RESPONSE_MAX_BYTES = 4 * 1024;
|
|
510
523
|
/** Read one untrusted discovery response without buffering beyond the fixed Host limit. */
|
|
511
524
|
async function readBoundedResponseText(response, maxBytes) {
|
|
512
525
|
const declaredLength = response.headers.get('content-length');
|
|
@@ -728,6 +741,12 @@ let AwikiService = (() => {
|
|
|
728
741
|
let _classSuper = TypertRemoteService;
|
|
729
742
|
let _instanceExtraInitializers = [];
|
|
730
743
|
let _getConfig_decorators;
|
|
744
|
+
let _getIntegration_decorators;
|
|
745
|
+
let _createIntegration_decorators;
|
|
746
|
+
let _updateIntegration_decorators;
|
|
747
|
+
let _rotateIntegrationId_decorators;
|
|
748
|
+
let _closeIntegration_decorators;
|
|
749
|
+
let _reopenIntegration_decorators;
|
|
731
750
|
let _getIdentity_decorators;
|
|
732
751
|
let _getSession_decorators;
|
|
733
752
|
let _logout_decorators;
|
|
@@ -735,7 +754,9 @@ let AwikiService = (() => {
|
|
|
735
754
|
let _inspectIdentityAccess_decorators;
|
|
736
755
|
let _sendRegistrationOtp_decorators;
|
|
737
756
|
let _registerIdentity_decorators;
|
|
757
|
+
let _retireDeviceIdentityForRejoin_decorators;
|
|
738
758
|
let _beginDeviceJoin_decorators;
|
|
759
|
+
let _getIdentityAccessState_decorators;
|
|
739
760
|
let _getDeviceJoinStatus_decorators;
|
|
740
761
|
let _cancelDeviceJoin_decorators;
|
|
741
762
|
let _refreshDeviceManagement_decorators;
|
|
@@ -743,6 +764,8 @@ let AwikiService = (() => {
|
|
|
743
764
|
let _approveDeviceJoin_decorators;
|
|
744
765
|
let _rejectDeviceJoin_decorators;
|
|
745
766
|
let _revokeDevice_decorators;
|
|
767
|
+
let _prepareRootTransfer_decorators;
|
|
768
|
+
let _confirmRootTransfer_decorators;
|
|
746
769
|
let _updateDisplayName_decorators;
|
|
747
770
|
let _getProfile_decorators;
|
|
748
771
|
let _updateProfile_decorators;
|
|
@@ -751,12 +774,14 @@ let AwikiService = (() => {
|
|
|
751
774
|
let _activateRecovery_decorators;
|
|
752
775
|
let _getRecoveryStatus_decorators;
|
|
753
776
|
let _resumeRecovery_decorators;
|
|
777
|
+
let _enterRecoveredSession_decorators;
|
|
754
778
|
let _discardRecovery_decorators;
|
|
755
779
|
let _resolvePeer_decorators;
|
|
756
780
|
let _createGroup_decorators;
|
|
757
781
|
let _getGroup_decorators;
|
|
758
782
|
let _joinGroup_decorators;
|
|
759
783
|
let _leaveGroup_decorators;
|
|
784
|
+
let _getDisplayProfiles_decorators;
|
|
760
785
|
let _listGroupMembers_decorators;
|
|
761
786
|
let _addGroupMember_decorators;
|
|
762
787
|
let _removeGroupMember_decorators;
|
|
@@ -780,6 +805,12 @@ let AwikiService = (() => {
|
|
|
780
805
|
static {
|
|
781
806
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
782
807
|
_getConfig_decorators = [Remote];
|
|
808
|
+
_getIntegration_decorators = [Remote];
|
|
809
|
+
_createIntegration_decorators = [Remote];
|
|
810
|
+
_updateIntegration_decorators = [Remote];
|
|
811
|
+
_rotateIntegrationId_decorators = [Remote];
|
|
812
|
+
_closeIntegration_decorators = [Remote];
|
|
813
|
+
_reopenIntegration_decorators = [Remote];
|
|
783
814
|
_getIdentity_decorators = [Remote];
|
|
784
815
|
_getSession_decorators = [Remote];
|
|
785
816
|
_logout_decorators = [Remote];
|
|
@@ -787,7 +818,9 @@ let AwikiService = (() => {
|
|
|
787
818
|
_inspectIdentityAccess_decorators = [Remote];
|
|
788
819
|
_sendRegistrationOtp_decorators = [Remote];
|
|
789
820
|
_registerIdentity_decorators = [Remote];
|
|
821
|
+
_retireDeviceIdentityForRejoin_decorators = [Remote];
|
|
790
822
|
_beginDeviceJoin_decorators = [Remote];
|
|
823
|
+
_getIdentityAccessState_decorators = [Remote];
|
|
791
824
|
_getDeviceJoinStatus_decorators = [Remote];
|
|
792
825
|
_cancelDeviceJoin_decorators = [Remote];
|
|
793
826
|
_refreshDeviceManagement_decorators = [Remote];
|
|
@@ -795,6 +828,8 @@ let AwikiService = (() => {
|
|
|
795
828
|
_approveDeviceJoin_decorators = [Remote];
|
|
796
829
|
_rejectDeviceJoin_decorators = [Remote];
|
|
797
830
|
_revokeDevice_decorators = [Remote];
|
|
831
|
+
_prepareRootTransfer_decorators = [Remote];
|
|
832
|
+
_confirmRootTransfer_decorators = [Remote];
|
|
798
833
|
_updateDisplayName_decorators = [Remote];
|
|
799
834
|
_getProfile_decorators = [Remote];
|
|
800
835
|
_updateProfile_decorators = [Remote];
|
|
@@ -803,12 +838,14 @@ let AwikiService = (() => {
|
|
|
803
838
|
_activateRecovery_decorators = [Remote];
|
|
804
839
|
_getRecoveryStatus_decorators = [Remote];
|
|
805
840
|
_resumeRecovery_decorators = [Remote];
|
|
841
|
+
_enterRecoveredSession_decorators = [Remote];
|
|
806
842
|
_discardRecovery_decorators = [Remote];
|
|
807
843
|
_resolvePeer_decorators = [Remote];
|
|
808
844
|
_createGroup_decorators = [Remote];
|
|
809
845
|
_getGroup_decorators = [Remote];
|
|
810
846
|
_joinGroup_decorators = [Remote];
|
|
811
847
|
_leaveGroup_decorators = [Remote];
|
|
848
|
+
_getDisplayProfiles_decorators = [Remote];
|
|
812
849
|
_listGroupMembers_decorators = [Remote];
|
|
813
850
|
_addGroupMember_decorators = [Remote];
|
|
814
851
|
_removeGroupMember_decorators = [Remote];
|
|
@@ -829,6 +866,12 @@ let AwikiService = (() => {
|
|
|
829
866
|
_sendMail_decorators = [Remote];
|
|
830
867
|
_clearLocalData_decorators = [Remote];
|
|
831
868
|
__esDecorate(this, null, _getConfig_decorators, { kind: "method", name: "getConfig", static: false, private: false, access: { has: obj => "getConfig" in obj, get: obj => obj.getConfig }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
869
|
+
__esDecorate(this, null, _getIntegration_decorators, { kind: "method", name: "getIntegration", static: false, private: false, access: { has: obj => "getIntegration" in obj, get: obj => obj.getIntegration }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
870
|
+
__esDecorate(this, null, _createIntegration_decorators, { kind: "method", name: "createIntegration", static: false, private: false, access: { has: obj => "createIntegration" in obj, get: obj => obj.createIntegration }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
871
|
+
__esDecorate(this, null, _updateIntegration_decorators, { kind: "method", name: "updateIntegration", static: false, private: false, access: { has: obj => "updateIntegration" in obj, get: obj => obj.updateIntegration }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
872
|
+
__esDecorate(this, null, _rotateIntegrationId_decorators, { kind: "method", name: "rotateIntegrationId", static: false, private: false, access: { has: obj => "rotateIntegrationId" in obj, get: obj => obj.rotateIntegrationId }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
873
|
+
__esDecorate(this, null, _closeIntegration_decorators, { kind: "method", name: "closeIntegration", static: false, private: false, access: { has: obj => "closeIntegration" in obj, get: obj => obj.closeIntegration }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
874
|
+
__esDecorate(this, null, _reopenIntegration_decorators, { kind: "method", name: "reopenIntegration", static: false, private: false, access: { has: obj => "reopenIntegration" in obj, get: obj => obj.reopenIntegration }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
832
875
|
__esDecorate(this, null, _getIdentity_decorators, { kind: "method", name: "getIdentity", static: false, private: false, access: { has: obj => "getIdentity" in obj, get: obj => obj.getIdentity }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
833
876
|
__esDecorate(this, null, _getSession_decorators, { kind: "method", name: "getSession", static: false, private: false, access: { has: obj => "getSession" in obj, get: obj => obj.getSession }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
834
877
|
__esDecorate(this, null, _logout_decorators, { kind: "method", name: "logout", static: false, private: false, access: { has: obj => "logout" in obj, get: obj => obj.logout }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -836,7 +879,9 @@ let AwikiService = (() => {
|
|
|
836
879
|
__esDecorate(this, null, _inspectIdentityAccess_decorators, { kind: "method", name: "inspectIdentityAccess", static: false, private: false, access: { has: obj => "inspectIdentityAccess" in obj, get: obj => obj.inspectIdentityAccess }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
837
880
|
__esDecorate(this, null, _sendRegistrationOtp_decorators, { kind: "method", name: "sendRegistrationOtp", static: false, private: false, access: { has: obj => "sendRegistrationOtp" in obj, get: obj => obj.sendRegistrationOtp }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
838
881
|
__esDecorate(this, null, _registerIdentity_decorators, { kind: "method", name: "registerIdentity", static: false, private: false, access: { has: obj => "registerIdentity" in obj, get: obj => obj.registerIdentity }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
882
|
+
__esDecorate(this, null, _retireDeviceIdentityForRejoin_decorators, { kind: "method", name: "retireDeviceIdentityForRejoin", static: false, private: false, access: { has: obj => "retireDeviceIdentityForRejoin" in obj, get: obj => obj.retireDeviceIdentityForRejoin }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
839
883
|
__esDecorate(this, null, _beginDeviceJoin_decorators, { kind: "method", name: "beginDeviceJoin", static: false, private: false, access: { has: obj => "beginDeviceJoin" in obj, get: obj => obj.beginDeviceJoin }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
884
|
+
__esDecorate(this, null, _getIdentityAccessState_decorators, { kind: "method", name: "getIdentityAccessState", static: false, private: false, access: { has: obj => "getIdentityAccessState" in obj, get: obj => obj.getIdentityAccessState }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
840
885
|
__esDecorate(this, null, _getDeviceJoinStatus_decorators, { kind: "method", name: "getDeviceJoinStatus", static: false, private: false, access: { has: obj => "getDeviceJoinStatus" in obj, get: obj => obj.getDeviceJoinStatus }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
841
886
|
__esDecorate(this, null, _cancelDeviceJoin_decorators, { kind: "method", name: "cancelDeviceJoin", static: false, private: false, access: { has: obj => "cancelDeviceJoin" in obj, get: obj => obj.cancelDeviceJoin }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
842
887
|
__esDecorate(this, null, _refreshDeviceManagement_decorators, { kind: "method", name: "refreshDeviceManagement", static: false, private: false, access: { has: obj => "refreshDeviceManagement" in obj, get: obj => obj.refreshDeviceManagement }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -844,6 +889,8 @@ let AwikiService = (() => {
|
|
|
844
889
|
__esDecorate(this, null, _approveDeviceJoin_decorators, { kind: "method", name: "approveDeviceJoin", static: false, private: false, access: { has: obj => "approveDeviceJoin" in obj, get: obj => obj.approveDeviceJoin }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
845
890
|
__esDecorate(this, null, _rejectDeviceJoin_decorators, { kind: "method", name: "rejectDeviceJoin", static: false, private: false, access: { has: obj => "rejectDeviceJoin" in obj, get: obj => obj.rejectDeviceJoin }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
846
891
|
__esDecorate(this, null, _revokeDevice_decorators, { kind: "method", name: "revokeDevice", static: false, private: false, access: { has: obj => "revokeDevice" in obj, get: obj => obj.revokeDevice }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
892
|
+
__esDecorate(this, null, _prepareRootTransfer_decorators, { kind: "method", name: "prepareRootTransfer", static: false, private: false, access: { has: obj => "prepareRootTransfer" in obj, get: obj => obj.prepareRootTransfer }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
893
|
+
__esDecorate(this, null, _confirmRootTransfer_decorators, { kind: "method", name: "confirmRootTransfer", static: false, private: false, access: { has: obj => "confirmRootTransfer" in obj, get: obj => obj.confirmRootTransfer }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
847
894
|
__esDecorate(this, null, _updateDisplayName_decorators, { kind: "method", name: "updateDisplayName", static: false, private: false, access: { has: obj => "updateDisplayName" in obj, get: obj => obj.updateDisplayName }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
848
895
|
__esDecorate(this, null, _getProfile_decorators, { kind: "method", name: "getProfile", static: false, private: false, access: { has: obj => "getProfile" in obj, get: obj => obj.getProfile }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
849
896
|
__esDecorate(this, null, _updateProfile_decorators, { kind: "method", name: "updateProfile", static: false, private: false, access: { has: obj => "updateProfile" in obj, get: obj => obj.updateProfile }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -852,12 +899,14 @@ let AwikiService = (() => {
|
|
|
852
899
|
__esDecorate(this, null, _activateRecovery_decorators, { kind: "method", name: "activateRecovery", static: false, private: false, access: { has: obj => "activateRecovery" in obj, get: obj => obj.activateRecovery }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
853
900
|
__esDecorate(this, null, _getRecoveryStatus_decorators, { kind: "method", name: "getRecoveryStatus", static: false, private: false, access: { has: obj => "getRecoveryStatus" in obj, get: obj => obj.getRecoveryStatus }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
854
901
|
__esDecorate(this, null, _resumeRecovery_decorators, { kind: "method", name: "resumeRecovery", static: false, private: false, access: { has: obj => "resumeRecovery" in obj, get: obj => obj.resumeRecovery }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
902
|
+
__esDecorate(this, null, _enterRecoveredSession_decorators, { kind: "method", name: "enterRecoveredSession", static: false, private: false, access: { has: obj => "enterRecoveredSession" in obj, get: obj => obj.enterRecoveredSession }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
855
903
|
__esDecorate(this, null, _discardRecovery_decorators, { kind: "method", name: "discardRecovery", static: false, private: false, access: { has: obj => "discardRecovery" in obj, get: obj => obj.discardRecovery }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
856
904
|
__esDecorate(this, null, _resolvePeer_decorators, { kind: "method", name: "resolvePeer", static: false, private: false, access: { has: obj => "resolvePeer" in obj, get: obj => obj.resolvePeer }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
857
905
|
__esDecorate(this, null, _createGroup_decorators, { kind: "method", name: "createGroup", static: false, private: false, access: { has: obj => "createGroup" in obj, get: obj => obj.createGroup }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
858
906
|
__esDecorate(this, null, _getGroup_decorators, { kind: "method", name: "getGroup", static: false, private: false, access: { has: obj => "getGroup" in obj, get: obj => obj.getGroup }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
859
907
|
__esDecorate(this, null, _joinGroup_decorators, { kind: "method", name: "joinGroup", static: false, private: false, access: { has: obj => "joinGroup" in obj, get: obj => obj.joinGroup }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
860
908
|
__esDecorate(this, null, _leaveGroup_decorators, { kind: "method", name: "leaveGroup", static: false, private: false, access: { has: obj => "leaveGroup" in obj, get: obj => obj.leaveGroup }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
909
|
+
__esDecorate(this, null, _getDisplayProfiles_decorators, { kind: "method", name: "getDisplayProfiles", static: false, private: false, access: { has: obj => "getDisplayProfiles" in obj, get: obj => obj.getDisplayProfiles }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
861
910
|
__esDecorate(this, null, _listGroupMembers_decorators, { kind: "method", name: "listGroupMembers", static: false, private: false, access: { has: obj => "listGroupMembers" in obj, get: obj => obj.listGroupMembers }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
862
911
|
__esDecorate(this, null, _addGroupMember_decorators, { kind: "method", name: "addGroupMember", static: false, private: false, access: { has: obj => "addGroupMember" in obj, get: obj => obj.addGroupMember }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
863
912
|
__esDecorate(this, null, _removeGroupMember_decorators, { kind: "method", name: "removeGroupMember", static: false, private: false, access: { has: obj => "removeGroupMember" in obj, get: obj => obj.removeGroupMember }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -884,11 +933,23 @@ let AwikiService = (() => {
|
|
|
884
933
|
resolved = __runInitializers(this, _instanceExtraInitializers);
|
|
885
934
|
sessionStore;
|
|
886
935
|
imageAttachmentCache;
|
|
887
|
-
sentMailStore;
|
|
888
936
|
conversationPreferenceStore;
|
|
889
937
|
startupUserServiceDomain;
|
|
890
938
|
settingsProvider;
|
|
939
|
+
tenantRegistry;
|
|
940
|
+
activeTenant;
|
|
941
|
+
activeClientOptions;
|
|
942
|
+
clientFactory;
|
|
891
943
|
provider;
|
|
944
|
+
runtimeGeneration = 0;
|
|
945
|
+
tenantSwitching = false;
|
|
946
|
+
tenantParticipants = new Set();
|
|
947
|
+
activeCapabilities;
|
|
948
|
+
updatePolicyStatus;
|
|
949
|
+
updatePolicyRequest;
|
|
950
|
+
updatePolicyTask;
|
|
951
|
+
updatePolicyTaskKey;
|
|
952
|
+
desktopDistribution;
|
|
892
953
|
signedOut;
|
|
893
954
|
sessionMutation = Promise.resolve();
|
|
894
955
|
sessionRevision = 0;
|
|
@@ -899,12 +960,14 @@ let AwikiService = (() => {
|
|
|
899
960
|
requestSessions = new Map();
|
|
900
961
|
deviceRefs = new Map();
|
|
901
962
|
deviceIds = new Map();
|
|
963
|
+
rootTransfers = new Map();
|
|
902
964
|
activeSummaryRequests = new Set();
|
|
903
965
|
summaryProvider;
|
|
904
|
-
recoveryReconciliationTarget;
|
|
905
966
|
hostContext;
|
|
906
967
|
/** Trusted same-process external HTTP authentication dispatcher. Never Remote. */
|
|
907
968
|
externalHttpAuth;
|
|
969
|
+
integrationClient;
|
|
970
|
+
mailListClient;
|
|
908
971
|
workspaceContext;
|
|
909
972
|
/**
|
|
910
973
|
* @param ctx - owning Host context.
|
|
@@ -913,12 +976,8 @@ let AwikiService = (() => {
|
|
|
913
976
|
constructor(ctx, config) {
|
|
914
977
|
super(ctx, 'awiki');
|
|
915
978
|
this.hostContext = ctx;
|
|
916
|
-
this.resolved = resolveConfig(config);
|
|
979
|
+
this.resolved = resolveConfig(ctx, config);
|
|
917
980
|
this.externalHttpAuth = createAwikiExternalHttpAuth(() => this.acquireExternalHttpAuthSession());
|
|
918
|
-
this.sessionStore = new AwikiSessionStore(this.resolved.stateRoot);
|
|
919
|
-
this.imageAttachmentCache = new AwikiImageAttachmentCache(this.resolved.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes);
|
|
920
|
-
this.sentMailStore = new AwikiSentMailStore(this.resolved.stateRoot);
|
|
921
|
-
this.conversationPreferenceStore = new AwikiConversationPreferenceStore(this.resolved.stateRoot);
|
|
922
981
|
this.startupUserServiceDomain = this.resolved.userServiceDomain;
|
|
923
982
|
ctx.inject(['settings'], (settingsCtx) => {
|
|
924
983
|
const provider = settingsCtx.settings;
|
|
@@ -937,7 +996,17 @@ let AwikiService = (() => {
|
|
|
937
996
|
}, 'awiki: release settings namespace');
|
|
938
997
|
});
|
|
939
998
|
ctx.inject(['connection'], (connectionCtx) => {
|
|
940
|
-
connectionCtx.connection.rpc.handle(AWIKI_SETTINGS_RPC_CHANNEL, createAwikiSettingsRpcHandler(() => this.settingsProvider
|
|
999
|
+
connectionCtx.connection.rpc.handle(AWIKI_SETTINGS_RPC_CHANNEL, createAwikiSettingsRpcHandler(() => this.settingsProvider, {
|
|
1000
|
+
describe: () => this.getTenantRegistryView(),
|
|
1001
|
+
create: (displayName, domain) => this.createCustomTenant(displayName, domain),
|
|
1002
|
+
rename: (tenantId, displayName) => this.renameCustomTenant(tenantId, displayName),
|
|
1003
|
+
switch: tenantId => this.switchTenant(tenantId),
|
|
1004
|
+
archive: tenantId => this.archiveCustomTenant(tenantId),
|
|
1005
|
+
describeUpdate: () => this.getUpdatePolicyStatus(),
|
|
1006
|
+
refreshUpdate: () => this.refreshUpdatePolicy(),
|
|
1007
|
+
describeDesktopUpdate: () => this.getDesktopUpdate(),
|
|
1008
|
+
refreshDesktopUpdate: () => this.refreshDesktopUpdate(),
|
|
1009
|
+
}), { authority: 'loopback' });
|
|
941
1010
|
});
|
|
942
1011
|
ctx.inject(['workspaceRegistry'], (workspaceCtx) => {
|
|
943
1012
|
this.workspaceContext = workspaceCtx;
|
|
@@ -953,8 +1022,29 @@ let AwikiService = (() => {
|
|
|
953
1022
|
await this.stopAgentConsumer(current);
|
|
954
1023
|
}, 'awiki: release Workspace listener composition');
|
|
955
1024
|
});
|
|
1025
|
+
ctx.inject(['desktopDistribution'], desktopCtx => {
|
|
1026
|
+
const service = desktopCtx.get('desktopDistribution');
|
|
1027
|
+
if (typeof service !== 'object' || service === null
|
|
1028
|
+
|| !('getSnapshot' in service) || typeof service.getSnapshot !== 'function'
|
|
1029
|
+
|| !('check' in service) || typeof service.check !== 'function')
|
|
1030
|
+
return;
|
|
1031
|
+
const candidate = service;
|
|
1032
|
+
if (decodeDesktopDistribution(candidate.getSnapshot()) === undefined)
|
|
1033
|
+
return;
|
|
1034
|
+
this.desktopDistribution = candidate;
|
|
1035
|
+
desktopCtx.effect(() => () => {
|
|
1036
|
+
if (this.desktopDistribution === candidate)
|
|
1037
|
+
this.desktopDistribution = undefined;
|
|
1038
|
+
});
|
|
1039
|
+
});
|
|
1040
|
+
ctx.effect(() => {
|
|
1041
|
+
const timer = setInterval(() => { void this.refreshUpdatePolicy().catch(() => undefined); }, 6 * 60 * 60 * 1000);
|
|
1042
|
+
timer.unref();
|
|
1043
|
+
return () => { clearInterval(timer); };
|
|
1044
|
+
});
|
|
956
1045
|
registerAwikiTools(ctx, this);
|
|
957
1046
|
ctx.effect(() => async () => {
|
|
1047
|
+
this.updatePolicyRequest?.abort();
|
|
958
1048
|
const provider = this.provider;
|
|
959
1049
|
if (provider !== undefined)
|
|
960
1050
|
await this.disposeProvider(provider);
|
|
@@ -971,32 +1061,334 @@ let AwikiService = (() => {
|
|
|
971
1061
|
* @returns asynchronous disposer for the exact registered client.
|
|
972
1062
|
*/
|
|
973
1063
|
registerClientFactory(factory) {
|
|
974
|
-
if (this.provider !== undefined)
|
|
1064
|
+
if (this.clientFactory !== undefined || this.provider !== undefined) {
|
|
975
1065
|
throw new Error('awiki: a client provider is already registered');
|
|
976
|
-
|
|
977
|
-
this.
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1066
|
+
}
|
|
1067
|
+
this.clientFactory = factory;
|
|
1068
|
+
try {
|
|
1069
|
+
this.openTenantProvider(this.ensureTenantRegistry().active(), factory);
|
|
1070
|
+
}
|
|
1071
|
+
catch (error) {
|
|
1072
|
+
this.clientFactory = undefined;
|
|
1073
|
+
throw error;
|
|
1074
|
+
}
|
|
1075
|
+
let active = true;
|
|
1076
|
+
return async () => {
|
|
1077
|
+
if (!active)
|
|
1078
|
+
return;
|
|
1079
|
+
active = false;
|
|
1080
|
+
this.clientFactory = undefined;
|
|
1081
|
+
const current = this.provider;
|
|
1082
|
+
if (current !== undefined)
|
|
1083
|
+
await this.disposeProvider(current);
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
ensureTenantRegistry() {
|
|
1087
|
+
if (this.tenantRegistry !== undefined)
|
|
1088
|
+
return this.tenantRegistry;
|
|
1089
|
+
const domain = this.startupUserServiceDomain;
|
|
1090
|
+
const configuredOrigin = `https://${this.resolved.userServiceDomain}`;
|
|
1091
|
+
const tenantOrigin = `https://${domain}`;
|
|
1092
|
+
const derive = (value) => value.replace(/\/$/u, '') === configuredOrigin ? tenantOrigin : value;
|
|
1093
|
+
const seed = {
|
|
1094
|
+
domain,
|
|
1095
|
+
configured: domain !== DEFAULT_AWIKI_DOMAIN
|
|
1096
|
+
|| this.resolved.userServiceUrl.replace(/\/$/u, '') !== configuredOrigin
|
|
1097
|
+
|| this.resolved.messageServiceUrl.replace(/\/$/u, '') !== configuredOrigin,
|
|
1098
|
+
...this.resolved.legacyTenantSlot === undefined
|
|
1099
|
+
? {}
|
|
1100
|
+
: { legacyTenantSlot: this.resolved.legacyTenantSlot },
|
|
1101
|
+
userServiceUrl: derive(this.resolved.userServiceUrl),
|
|
1102
|
+
messageServiceUrl: derive(this.resolved.messageServiceUrl),
|
|
1103
|
+
mailServiceUrl: derive(this.resolved.mailServiceUrl),
|
|
1104
|
+
messageServicePublicUrl: derive(this.resolved.messageServicePublicUrl),
|
|
1105
|
+
messageServiceDid: this.resolved.messageServiceDid === `did:wba:${this.resolved.userServiceDomain}`
|
|
1106
|
+
? `did:wba:${domain}`
|
|
1107
|
+
: this.resolved.messageServiceDid,
|
|
1108
|
+
};
|
|
1109
|
+
this.tenantRegistry = AwikiTenantRegistry.open(this.resolved.stateRoot, seed);
|
|
1110
|
+
return this.tenantRegistry;
|
|
1111
|
+
}
|
|
1112
|
+
optionsForTenant(tenant) {
|
|
1113
|
+
const endpoints = tenant.endpoints;
|
|
1114
|
+
const messageServicePublicOrigin = new URL(endpoints.messageServicePublicUrl).origin;
|
|
1115
|
+
return {
|
|
1116
|
+
...endpoints,
|
|
1117
|
+
userServiceDomain: tenant.didHost,
|
|
1118
|
+
allowedAttachmentOrigins: tenant.storageLayout === 'legacy-base'
|
|
1119
|
+
? this.resolved.allowedAttachmentOrigins
|
|
1120
|
+
: [messageServicePublicOrigin],
|
|
986
1121
|
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
987
1122
|
allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
|
|
988
|
-
stateRoot: this.
|
|
989
|
-
}
|
|
1123
|
+
stateRoot: this.ensureTenantRegistry().stateRoot(tenant),
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
ensureTenantOptions() {
|
|
1127
|
+
if (this.activeClientOptions !== undefined)
|
|
1128
|
+
return this.activeClientOptions;
|
|
1129
|
+
const tenant = this.ensureTenantRegistry().active();
|
|
1130
|
+
const options = this.optionsForTenant(tenant);
|
|
1131
|
+
this.bindTenantState(tenant, options);
|
|
1132
|
+
return options;
|
|
1133
|
+
}
|
|
1134
|
+
bindTenantState(tenant, options) {
|
|
1135
|
+
this.updatePolicyRequest?.abort();
|
|
1136
|
+
this.updatePolicyStatus = undefined;
|
|
1137
|
+
this.updatePolicyTask = undefined;
|
|
1138
|
+
this.activeTenant = tenant;
|
|
1139
|
+
this.activeClientOptions = options;
|
|
1140
|
+
this.startupUserServiceDomain = tenant.didHost;
|
|
1141
|
+
this.sessionStore = new AwikiSessionStore(options.stateRoot);
|
|
1142
|
+
this.imageAttachmentCache = new AwikiImageAttachmentCache(options.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes);
|
|
1143
|
+
this.conversationPreferenceStore = new AwikiConversationPreferenceStore(options.stateRoot);
|
|
1144
|
+
this.mailListClient = new AwikiMailListClient(options.mailServiceUrl, this.externalHttpAuth);
|
|
1145
|
+
this.integrationClient = undefined;
|
|
1146
|
+
this.activeCapabilities = {
|
|
1147
|
+
tenantId: tenant.tenantId,
|
|
1148
|
+
generation: this.runtimeGeneration,
|
|
1149
|
+
online: false,
|
|
1150
|
+
handleRecoveryPhoneEnabled: false,
|
|
1151
|
+
};
|
|
1152
|
+
this.signedOut = undefined;
|
|
1153
|
+
this.activeIdentityDid = undefined;
|
|
1154
|
+
this.pendingDeviceJoin = undefined;
|
|
1155
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
1156
|
+
}
|
|
1157
|
+
openTenantProvider(tenant, factory, startRuntime = true) {
|
|
1158
|
+
const options = this.optionsForTenant(tenant);
|
|
1159
|
+
const client = factory(options);
|
|
990
1160
|
const provider = {
|
|
991
1161
|
client,
|
|
992
1162
|
realtimeGeneration: 0,
|
|
993
1163
|
agentConsumerGeneration: 0,
|
|
994
1164
|
localDeviceJoinRequestCountAfterSync: 0,
|
|
995
1165
|
};
|
|
1166
|
+
this.bindTenantState(tenant, options);
|
|
996
1167
|
this.provider = provider;
|
|
997
|
-
this.
|
|
998
|
-
|
|
999
|
-
|
|
1168
|
+
void this.refreshUpdatePolicy().catch(() => undefined);
|
|
1169
|
+
if (startRuntime)
|
|
1170
|
+
this.ensureProviderRuntime(provider);
|
|
1171
|
+
return provider;
|
|
1172
|
+
}
|
|
1173
|
+
/** Read the Host-owned catalog for trusted loopback settings surfaces. */
|
|
1174
|
+
getTenantRegistryView() {
|
|
1175
|
+
return this.ensureTenantRegistry().snapshot(this.tenantSwitching);
|
|
1176
|
+
}
|
|
1177
|
+
/** Browser-safe, same-process update state for Desktop and loopback settings. */
|
|
1178
|
+
getUpdatePolicyStatus() {
|
|
1179
|
+
const tenant = this.activeTenant ?? this.ensureTenantRegistry().active();
|
|
1180
|
+
const modelProxyVersion = this.currentModelProxyVersion();
|
|
1181
|
+
return this.updatePolicyStatus ?? readAwikiUpdatePolicyStatus({
|
|
1182
|
+
tenant, generation: this.runtimeGeneration, stateRoot: this.resolved.stateRoot,
|
|
1183
|
+
currentPluginVersion: DSH_AWIKI_VERSION,
|
|
1184
|
+
...modelProxyVersion === undefined ? {} : { currentModelProxyVersion: modelProxyVersion },
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
/** Only the Desktop provider owns its release source; tenants cannot override it. */
|
|
1188
|
+
getDesktopUpdate() {
|
|
1189
|
+
return decodeDesktopDistribution(this.desktopDistribution?.getSnapshot());
|
|
1190
|
+
}
|
|
1191
|
+
async refreshDesktopUpdate() {
|
|
1192
|
+
const provider = this.desktopDistribution;
|
|
1193
|
+
if (provider === undefined)
|
|
1194
|
+
return undefined;
|
|
1195
|
+
const value = await provider.check();
|
|
1196
|
+
return provider === this.desktopDistribution ? decodeDesktopDistribution(value) : this.getDesktopUpdate();
|
|
1197
|
+
}
|
|
1198
|
+
/** Refresh only the active generation; late results from old tenants are discarded. */
|
|
1199
|
+
refreshUpdatePolicy() {
|
|
1200
|
+
const key = JSON.stringify([this.runtimeGeneration, this.currentModelProxyVersion()]);
|
|
1201
|
+
if (this.updatePolicyTask !== undefined && this.updatePolicyTaskKey === key)
|
|
1202
|
+
return this.updatePolicyTask;
|
|
1203
|
+
const task = this.performUpdatePolicyRefresh().finally(() => {
|
|
1204
|
+
if (this.updatePolicyTask === task)
|
|
1205
|
+
this.updatePolicyTask = undefined;
|
|
1206
|
+
});
|
|
1207
|
+
this.updatePolicyTask = task;
|
|
1208
|
+
this.updatePolicyTaskKey = key;
|
|
1209
|
+
return task;
|
|
1210
|
+
}
|
|
1211
|
+
async performUpdatePolicyRefresh() {
|
|
1212
|
+
const tenant = this.activeTenant ?? this.ensureTenantRegistry().active();
|
|
1213
|
+
const generation = this.runtimeGeneration;
|
|
1214
|
+
this.updatePolicyRequest?.abort();
|
|
1215
|
+
const request = new AbortController();
|
|
1216
|
+
this.updatePolicyRequest = request;
|
|
1217
|
+
const currentModelProxyVersion = this.currentModelProxyVersion();
|
|
1218
|
+
const status = await checkAwikiUpdatePolicy({
|
|
1219
|
+
tenant,
|
|
1220
|
+
generation,
|
|
1221
|
+
stateRoot: this.resolved.stateRoot,
|
|
1222
|
+
currentPluginVersion: DSH_AWIKI_VERSION,
|
|
1223
|
+
...currentModelProxyVersion === undefined ? {} : { currentModelProxyVersion },
|
|
1224
|
+
allowInsecureLoopback: this.resolved.allowInsecureLoopbackForTesting,
|
|
1225
|
+
signal: request.signal,
|
|
1226
|
+
});
|
|
1227
|
+
if (this.updatePolicyRequest === request
|
|
1228
|
+
&& this.runtimeGeneration === generation
|
|
1229
|
+
&& this.activeTenant?.tenantId === tenant.tenantId) {
|
|
1230
|
+
const wasRestricted = this.getUpdatePolicyStatus().restricted;
|
|
1231
|
+
this.updatePolicyStatus = status;
|
|
1232
|
+
this.hostContext.emit('awiki/update-policy', status);
|
|
1233
|
+
if (status.restricted) {
|
|
1234
|
+
this.invalidateSummaries();
|
|
1235
|
+
const provider = this.provider;
|
|
1236
|
+
if (provider !== undefined) {
|
|
1237
|
+
try {
|
|
1238
|
+
await this.stopProviderRuntime(provider);
|
|
1239
|
+
}
|
|
1240
|
+
catch (error) {
|
|
1241
|
+
this.ctx.logger('awiki-update').warn('AWiki restricted-version runtime shutdown failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
else if (wasRestricted && this.provider !== undefined && !this.tenantSwitching) {
|
|
1246
|
+
this.ensureProviderRuntime(this.provider);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
return this.getUpdatePolicyStatus();
|
|
1250
|
+
}
|
|
1251
|
+
currentModelProxyVersion() {
|
|
1252
|
+
for (const participant of this.tenantParticipants) {
|
|
1253
|
+
if (participant.component?.product === 'dsh-awiki-model-proxy')
|
|
1254
|
+
return participant.component.version;
|
|
1255
|
+
}
|
|
1256
|
+
return undefined;
|
|
1257
|
+
}
|
|
1258
|
+
createCustomTenant(displayName, domain) {
|
|
1259
|
+
this.assertVersionSupported();
|
|
1260
|
+
this.ensureTenantRegistry().createCustom(displayName, domain);
|
|
1261
|
+
return this.getTenantRegistryView();
|
|
1262
|
+
}
|
|
1263
|
+
renameCustomTenant(tenantId, displayName) {
|
|
1264
|
+
this.assertVersionSupported();
|
|
1265
|
+
this.ensureTenantRegistry().renameCustom(tenantId, displayName);
|
|
1266
|
+
return this.getTenantRegistryView();
|
|
1267
|
+
}
|
|
1268
|
+
archiveCustomTenant(tenantId) {
|
|
1269
|
+
this.assertVersionSupported();
|
|
1270
|
+
this.ensureTenantRegistry().archiveCustom(tenantId);
|
|
1271
|
+
return this.getTenantRegistryView();
|
|
1272
|
+
}
|
|
1273
|
+
/** Register one same-process resource owner in every transactional tenant change. */
|
|
1274
|
+
registerTenantLifecycleParticipant(participant) {
|
|
1275
|
+
if (this.tenantParticipants.has(participant))
|
|
1276
|
+
throw new Error('awiki: tenant lifecycle participant is already registered');
|
|
1277
|
+
this.tenantParticipants.add(participant);
|
|
1278
|
+
if (participant.component !== undefined)
|
|
1279
|
+
this.updatePolicyStatus = undefined;
|
|
1280
|
+
void this.refreshUpdatePolicy().catch(() => undefined);
|
|
1281
|
+
let active = true;
|
|
1282
|
+
return () => {
|
|
1283
|
+
if (!active)
|
|
1284
|
+
return;
|
|
1285
|
+
active = false;
|
|
1286
|
+
this.tenantParticipants.delete(participant);
|
|
1287
|
+
if (participant.component !== undefined)
|
|
1288
|
+
this.updatePolicyStatus = undefined;
|
|
1289
|
+
void this.refreshUpdatePolicy().catch(() => undefined);
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
/** Replace the complete Core runtime and commit the active tenant only after opening succeeds. */
|
|
1293
|
+
async switchTenant(tenantId) {
|
|
1294
|
+
if (this.tenantSwitching)
|
|
1295
|
+
throw new Error('awiki: a tenant switch is already in progress');
|
|
1296
|
+
const registry = this.ensureTenantRegistry();
|
|
1297
|
+
const previousTenant = registry.active();
|
|
1298
|
+
if (previousTenant.tenantId === tenantId)
|
|
1299
|
+
return this.getTenantRegistryView();
|
|
1300
|
+
const target = registry.find(tenantId);
|
|
1301
|
+
if (target === undefined || target.lifecycle === 'archived')
|
|
1302
|
+
throw new Error('awiki: target tenant is unavailable');
|
|
1303
|
+
const factory = this.clientFactory;
|
|
1304
|
+
const previousProvider = this.provider;
|
|
1305
|
+
if (factory === undefined || previousProvider === undefined)
|
|
1306
|
+
throw new Error('awiki: client provider is unavailable');
|
|
1307
|
+
this.tenantSwitching = true;
|
|
1308
|
+
this.updatePolicyRequest?.abort();
|
|
1309
|
+
this.updatePolicyTask = undefined;
|
|
1310
|
+
const context = {
|
|
1311
|
+
from: previousTenant,
|
|
1312
|
+
to: target,
|
|
1313
|
+
generation: ++this.runtimeGeneration,
|
|
1314
|
+
};
|
|
1315
|
+
const prepared = [];
|
|
1316
|
+
let targetOpened = false;
|
|
1317
|
+
let committed = false;
|
|
1318
|
+
try {
|
|
1319
|
+
await this.sessionMutation;
|
|
1320
|
+
this.invalidateSummaries();
|
|
1321
|
+
for (const participant of this.tenantParticipants) {
|
|
1322
|
+
await participant.prepareSwitch(context);
|
|
1323
|
+
prepared.push(participant);
|
|
1324
|
+
}
|
|
1325
|
+
await this.disposeProvider(previousProvider);
|
|
1326
|
+
const targetProvider = this.openTenantProvider(target, factory, false);
|
|
1327
|
+
targetOpened = true;
|
|
1328
|
+
// The factory returns an adapter immediately; its native Core opens asynchronously.
|
|
1329
|
+
// A local identity read proves both open and Registry loading before committing the switch.
|
|
1330
|
+
await targetProvider.client.getIdentity();
|
|
1331
|
+
await Promise.all([this.discoverTenantCapabilities(3_000), this.refreshUpdatePolicy()]);
|
|
1332
|
+
registry.commitActive(target.tenantId);
|
|
1333
|
+
committed = true;
|
|
1334
|
+
for (const participant of prepared)
|
|
1335
|
+
await participant.commitSwitch?.(context);
|
|
1336
|
+
this.tenantSwitching = false;
|
|
1337
|
+
this.ensureProviderRuntime(targetProvider);
|
|
1338
|
+
const view = this.getTenantRegistryView();
|
|
1339
|
+
this.hostContext.emit('awiki/tenant', view);
|
|
1340
|
+
return view;
|
|
1341
|
+
}
|
|
1342
|
+
catch (error) {
|
|
1343
|
+
const rollbackFailures = [];
|
|
1344
|
+
if (targetOpened) {
|
|
1345
|
+
const current = this.provider;
|
|
1346
|
+
if (current !== undefined) {
|
|
1347
|
+
try {
|
|
1348
|
+
await this.disposeProvider(current);
|
|
1349
|
+
}
|
|
1350
|
+
catch (rollbackError) {
|
|
1351
|
+
rollbackFailures.push(rollbackError);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
if (committed) {
|
|
1356
|
+
try {
|
|
1357
|
+
registry.commitActive(previousTenant.tenantId);
|
|
1358
|
+
}
|
|
1359
|
+
catch (rollbackError) {
|
|
1360
|
+
rollbackFailures.push(rollbackError);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
if (this.provider === undefined) {
|
|
1364
|
+
try {
|
|
1365
|
+
const restored = this.openTenantProvider(previousTenant, factory, false);
|
|
1366
|
+
await restored.client.getIdentity();
|
|
1367
|
+
this.ensureProviderRuntime(restored);
|
|
1368
|
+
}
|
|
1369
|
+
catch (rollbackError) {
|
|
1370
|
+
rollbackFailures.push(rollbackError);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
for (const participant of prepared.reverse()) {
|
|
1374
|
+
try {
|
|
1375
|
+
await participant.rollbackSwitch?.(context);
|
|
1376
|
+
}
|
|
1377
|
+
catch (rollbackError) {
|
|
1378
|
+
rollbackFailures.push(rollbackError);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
if (rollbackFailures.length > 0) {
|
|
1382
|
+
throw new AggregateError([error, ...rollbackFailures], 'awiki: tenant switch failed and rollback was incomplete');
|
|
1383
|
+
}
|
|
1384
|
+
this.tenantSwitching = false;
|
|
1385
|
+
if (this.provider !== undefined)
|
|
1386
|
+
this.ensureProviderRuntime(this.provider);
|
|
1387
|
+
throw new Error('awiki: tenant switch failed; the previous tenant was restored', { cause: error });
|
|
1388
|
+
}
|
|
1389
|
+
finally {
|
|
1390
|
+
this.tenantSwitching = false;
|
|
1391
|
+
}
|
|
1000
1392
|
}
|
|
1001
1393
|
/** Safe same-process diagnostics for focused E2E. Never exposed through Typert Remote. */
|
|
1002
1394
|
getRealtimeDiagnostics() {
|
|
@@ -1016,25 +1408,6 @@ let AwikiService = (() => {
|
|
|
1016
1408
|
localDeviceJoinRequestCountAfterSync: provider?.localDeviceJoinRequestCountAfterSync ?? 0,
|
|
1017
1409
|
};
|
|
1018
1410
|
}
|
|
1019
|
-
/** Register the optional Model Proxy recovery target without exposing an arbitrary callback or token. */
|
|
1020
|
-
registerRecoveryReconciliationTarget(target) {
|
|
1021
|
-
if (this.recoveryReconciliationTarget !== undefined) {
|
|
1022
|
-
throw new Error('awiki: a recovery reconciliation target is already registered');
|
|
1023
|
-
}
|
|
1024
|
-
const registered = Object.freeze({
|
|
1025
|
-
endpoint: recoveryReconciliationEndpoint(target, this.resolved.allowInsecureLoopbackForTesting),
|
|
1026
|
-
});
|
|
1027
|
-
this.recoveryReconciliationTarget = registered;
|
|
1028
|
-
let active = true;
|
|
1029
|
-
return () => {
|
|
1030
|
-
if (!active)
|
|
1031
|
-
return;
|
|
1032
|
-
active = false;
|
|
1033
|
-
if (this.recoveryReconciliationTarget === registered) {
|
|
1034
|
-
this.recoveryReconciliationTarget = undefined;
|
|
1035
|
-
}
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
1411
|
/** Register one replaceable conversation-summary provider for this deployment. */
|
|
1039
1412
|
registerSummaryProvider(provider) {
|
|
1040
1413
|
if (this.summaryProvider !== undefined)
|
|
@@ -1054,42 +1427,144 @@ let AwikiService = (() => {
|
|
|
1054
1427
|
* @returns Browser-safe polling configuration without SDK endpoints or state paths.
|
|
1055
1428
|
*/
|
|
1056
1429
|
async getConfig() {
|
|
1057
|
-
|
|
1430
|
+
this.ensureTenantOptions();
|
|
1431
|
+
const capabilities = await this.discoverTenantCapabilities(10_000);
|
|
1432
|
+
if (this.tenantSwitching
|
|
1433
|
+
|| capabilities.generation !== this.runtimeGeneration
|
|
1434
|
+
|| capabilities.tenantId !== this.activeTenant?.tenantId) {
|
|
1435
|
+
return { ok: false, error: failure('conflict') };
|
|
1436
|
+
}
|
|
1437
|
+
return {
|
|
1438
|
+
ok: true,
|
|
1439
|
+
value: {
|
|
1440
|
+
pollIntervalMs: this.resolved.pollIntervalMs,
|
|
1441
|
+
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
1442
|
+
handleRecoveryPhoneEnabled: capabilities.handleRecoveryPhoneEnabled,
|
|
1443
|
+
...capabilities.guestGatewayBaseUrl === undefined
|
|
1444
|
+
? {}
|
|
1445
|
+
: { integrationGuideUrl: new URL('/guest/guide/integration', capabilities.guestGatewayBaseUrl).toString() },
|
|
1446
|
+
tenantId: capabilities.tenantId,
|
|
1447
|
+
tenantGeneration: capabilities.generation,
|
|
1448
|
+
tenantOnline: capabilities.online,
|
|
1449
|
+
services: {
|
|
1450
|
+
modelProxy: { enabled: capabilities.modelProxyBaseUrl !== undefined },
|
|
1451
|
+
guestGateway: { enabled: capabilities.guestGatewayBaseUrl !== undefined },
|
|
1452
|
+
},
|
|
1453
|
+
},
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
/** Trusted same-process capability binding for optional tenant participants. */
|
|
1457
|
+
getTenantCapabilities() {
|
|
1458
|
+
const capabilities = this.activeCapabilities;
|
|
1459
|
+
if (capabilities === undefined)
|
|
1460
|
+
throw new Error('awiki: tenant capabilities are unavailable');
|
|
1461
|
+
return { ...capabilities };
|
|
1462
|
+
}
|
|
1463
|
+
/** Refresh the active tenant's optional service advertisement without affecting Core availability. */
|
|
1464
|
+
refreshTenantCapabilities() {
|
|
1465
|
+
this.ensureTenantOptions();
|
|
1466
|
+
return this.discoverTenantCapabilities(10_000);
|
|
1467
|
+
}
|
|
1468
|
+
async discoverTenantCapabilities(timeoutMs) {
|
|
1469
|
+
const options = this.activeClientOptions;
|
|
1470
|
+
const tenant = this.activeTenant;
|
|
1471
|
+
if (options === undefined || tenant === undefined)
|
|
1472
|
+
throw new ProviderUnavailableError();
|
|
1473
|
+
const generation = this.runtimeGeneration;
|
|
1474
|
+
const fallback = {
|
|
1475
|
+
tenantId: tenant.tenantId,
|
|
1476
|
+
generation,
|
|
1477
|
+
online: false,
|
|
1478
|
+
handleRecoveryPhoneEnabled: false,
|
|
1479
|
+
};
|
|
1480
|
+
const publish = (capabilities) => {
|
|
1481
|
+
if (this.runtimeGeneration === generation && this.activeTenant?.tenantId === tenant.tenantId) {
|
|
1482
|
+
this.activeCapabilities = capabilities;
|
|
1483
|
+
this.integrationClient = capabilities.guestGatewayBaseUrl === undefined
|
|
1484
|
+
? undefined
|
|
1485
|
+
: new AwikiIntegrationClient(capabilities.guestGatewayBaseUrl, this.externalHttpAuth);
|
|
1486
|
+
}
|
|
1487
|
+
return capabilities;
|
|
1488
|
+
};
|
|
1058
1489
|
const abort = new AbortController();
|
|
1059
|
-
const timeout = setTimeout(() => { abort.abort(); },
|
|
1490
|
+
const timeout = setTimeout(() => { abort.abort(); }, timeoutMs);
|
|
1060
1491
|
try {
|
|
1061
|
-
const
|
|
1492
|
+
const endpoint = new URL('/user-service/v1/server-info', options.userServiceUrl);
|
|
1493
|
+
endpoint.searchParams.set('client_platform', 'dsh');
|
|
1494
|
+
const response = await fetch(endpoint, {
|
|
1062
1495
|
method: 'GET',
|
|
1063
1496
|
headers: { accept: 'application/json', 'cache-control': 'no-store' },
|
|
1064
1497
|
cache: 'no-store',
|
|
1065
1498
|
redirect: 'error',
|
|
1066
1499
|
signal: abort.signal,
|
|
1067
1500
|
});
|
|
1068
|
-
if (response.ok)
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1501
|
+
if (!response.ok)
|
|
1502
|
+
return publish(fallback);
|
|
1503
|
+
const text = await readBoundedResponseText(response, SERVER_INFO_RESPONSE_MAX_BYTES);
|
|
1504
|
+
const value = text === undefined ? undefined : JSON.parse(text);
|
|
1505
|
+
if (typeof value !== 'object' || value === null || value.schema_version !== 1)
|
|
1506
|
+
return publish(fallback);
|
|
1507
|
+
const methods = value.identity?.handle_recovery?.methods;
|
|
1508
|
+
const handleRecoveryPhoneEnabled = Array.isArray(methods) && methods.some((method) => (typeof method === 'object' && method !== null
|
|
1509
|
+
&& method.id === 'phone'
|
|
1510
|
+
&& method.enabled === true
|
|
1511
|
+
&& method.verification?.required === true
|
|
1512
|
+
&& method.verification?.type === 'sms_otp'));
|
|
1513
|
+
const services = value.services;
|
|
1514
|
+
const modelProxyBaseUrl = publishedServiceBaseUrl(services, 'model_proxy', this.resolved.allowInsecureLoopbackForTesting);
|
|
1515
|
+
const guestGatewayBaseUrl = publishedServiceBaseUrl(services, 'guest_gateway', false);
|
|
1516
|
+
const capabilities = {
|
|
1517
|
+
tenantId: tenant.tenantId,
|
|
1518
|
+
generation,
|
|
1519
|
+
online: true,
|
|
1520
|
+
handleRecoveryPhoneEnabled,
|
|
1521
|
+
...modelProxyBaseUrl === undefined ? {} : { modelProxyBaseUrl },
|
|
1522
|
+
...guestGatewayBaseUrl === undefined ? {} : { guestGatewayBaseUrl },
|
|
1523
|
+
};
|
|
1524
|
+
return publish(capabilities);
|
|
1525
|
+
}
|
|
1526
|
+
catch {
|
|
1527
|
+
return publish(fallback);
|
|
1080
1528
|
}
|
|
1081
|
-
catch { }
|
|
1082
1529
|
finally {
|
|
1083
1530
|
clearTimeout(timeout);
|
|
1084
1531
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1532
|
+
}
|
|
1533
|
+
/** Read the Integration owned by the active full Handle through the fixed Host client. */
|
|
1534
|
+
getIntegration() {
|
|
1535
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
1536
|
+
return Promise.resolve(integrationUnavailable());
|
|
1537
|
+
return this.integrationClient?.read() ?? Promise.resolve(integrationUnavailable());
|
|
1538
|
+
}
|
|
1539
|
+
/** Create the active full Handle's only Integration. */
|
|
1540
|
+
createIntegration(request) {
|
|
1541
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
1542
|
+
return Promise.resolve(integrationUnavailable());
|
|
1543
|
+
return this.integrationClient?.create(request) ?? Promise.resolve(integrationUnavailable());
|
|
1544
|
+
}
|
|
1545
|
+
/** Update editable Integration fields with optimistic concurrency. */
|
|
1546
|
+
updateIntegration(request) {
|
|
1547
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
1548
|
+
return Promise.resolve(integrationUnavailable());
|
|
1549
|
+
return this.integrationClient?.update(request) ?? Promise.resolve(integrationUnavailable());
|
|
1550
|
+
}
|
|
1551
|
+
/** Atomically replace the current public Integration id. */
|
|
1552
|
+
rotateIntegrationId(request) {
|
|
1553
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
1554
|
+
return Promise.resolve(integrationUnavailable());
|
|
1555
|
+
return this.integrationClient?.rotate(request) ?? Promise.resolve(integrationUnavailable());
|
|
1556
|
+
}
|
|
1557
|
+
/** Close the Integration and revoke its current public id. */
|
|
1558
|
+
closeIntegration(request) {
|
|
1559
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
1560
|
+
return Promise.resolve(integrationUnavailable());
|
|
1561
|
+
return this.integrationClient?.close(request) ?? Promise.resolve(integrationUnavailable());
|
|
1562
|
+
}
|
|
1563
|
+
/** Revalidate one closed Integration and issue a new public id. */
|
|
1564
|
+
reopenIntegration(request) {
|
|
1565
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
1566
|
+
return Promise.resolve(integrationUnavailable());
|
|
1567
|
+
return this.integrationClient?.reopen(request) ?? Promise.resolve(integrationUnavailable());
|
|
1093
1568
|
}
|
|
1094
1569
|
/**
|
|
1095
1570
|
* Read the deployment's identity status.
|
|
@@ -1123,6 +1598,8 @@ let AwikiService = (() => {
|
|
|
1123
1598
|
if (request?.confirmation !== AWIKI_LOGOUT_CONFIRMATION) {
|
|
1124
1599
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1125
1600
|
}
|
|
1601
|
+
// Fence pending post-Recovery work before the serialized sign-out reaches the head of the queue.
|
|
1602
|
+
this.sessionRevision += 1;
|
|
1126
1603
|
return this.mutateSession(async () => {
|
|
1127
1604
|
if (await this.isSignedOut())
|
|
1128
1605
|
return { ok: true, value: { status: 'signed-out' } };
|
|
@@ -1175,10 +1652,13 @@ let AwikiService = (() => {
|
|
|
1175
1652
|
}
|
|
1176
1653
|
/** Classify one configured-domain Handle before selecting the registration or recovery OTP purpose. */
|
|
1177
1654
|
async inspectIdentityAccess(request) {
|
|
1178
|
-
const
|
|
1655
|
+
const options = this.activeClientOptions;
|
|
1656
|
+
if (options === undefined)
|
|
1657
|
+
return { ok: false, error: normalizeFailure(new ProviderUnavailableError()) };
|
|
1658
|
+
const target = identityAccessTarget(request, options.userServiceDomain);
|
|
1179
1659
|
if (target === undefined)
|
|
1180
1660
|
return { ok: false, error: failure('invalid-request') };
|
|
1181
|
-
const endpoint = new URL(`/.well-known/handle/${encodeURIComponent(target.localPart)}`,
|
|
1661
|
+
const endpoint = new URL(`/.well-known/handle/${encodeURIComponent(target.localPart)}`, options.userServiceUrl);
|
|
1182
1662
|
const abort = new AbortController();
|
|
1183
1663
|
const timeout = setTimeout(() => { abort.abort(); }, 10_000);
|
|
1184
1664
|
let response;
|
|
@@ -1267,25 +1747,88 @@ let AwikiService = (() => {
|
|
|
1267
1747
|
},
|
|
1268
1748
|
};
|
|
1269
1749
|
}
|
|
1750
|
+
/** Retire only this revoked device credential; ordinary local messages remain for the rejoined identity. */
|
|
1751
|
+
retireDeviceIdentityForRejoin() {
|
|
1752
|
+
return this.mutateSession(async () => {
|
|
1753
|
+
const provider = this.provider;
|
|
1754
|
+
if (provider !== undefined)
|
|
1755
|
+
await this.stopProviderRuntime(provider);
|
|
1756
|
+
const result = await this.run(client => client.retireDefaultIdentityForRejoin(), { allowSignedOut: true });
|
|
1757
|
+
if (!result.ok) {
|
|
1758
|
+
if (provider !== undefined && this.provider === provider)
|
|
1759
|
+
this.ensureProviderRuntime(provider);
|
|
1760
|
+
return result;
|
|
1761
|
+
}
|
|
1762
|
+
try {
|
|
1763
|
+
await this.sessionStore.signIn();
|
|
1764
|
+
}
|
|
1765
|
+
catch {
|
|
1766
|
+
try {
|
|
1767
|
+
await this.sessionStore.signIn();
|
|
1768
|
+
}
|
|
1769
|
+
catch {
|
|
1770
|
+
this.ctx.logger('awiki').warn('AWiki local session marker could not be refreshed after device retirement');
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
this.signedOut = false;
|
|
1774
|
+
this.activeIdentityDid = undefined;
|
|
1775
|
+
this.pendingDeviceJoin = undefined;
|
|
1776
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
1777
|
+
this.invalidateSummaries();
|
|
1778
|
+
this.publishSession({ status: 'unregistered' });
|
|
1779
|
+
return { ok: true, value: { completed: true } };
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1270
1782
|
/** Consume the exact in-memory continuation; ordinary Join never claims rebind user presence. */
|
|
1271
1783
|
async beginDeviceJoin() {
|
|
1272
1784
|
const continuation = this.pendingDeviceJoin;
|
|
1273
1785
|
if (continuation === undefined)
|
|
1274
1786
|
return { ok: false, error: failure('conflict') };
|
|
1275
|
-
if (continuation.mode
|
|
1787
|
+
if ((continuation.mode === 'ordinary' && continuation.requiresUserPresence)
|
|
1788
|
+
|| (continuation.mode === 'handle-recovery-rebind' && !continuation.requiresUserPresence)) {
|
|
1276
1789
|
return { ok: false, error: failure('forbidden') };
|
|
1277
1790
|
}
|
|
1278
|
-
this.
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1791
|
+
const result = await this.run(async (client) => {
|
|
1792
|
+
let userPresenceConfirmed = false;
|
|
1793
|
+
if (continuation.mode === 'handle-recovery-rebind') {
|
|
1794
|
+
if (!client.trustedUserPresenceSupported) {
|
|
1795
|
+
throw Object.assign(new Error('trusted user presence unsupported'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1796
|
+
}
|
|
1797
|
+
userPresenceConfirmed = await client.confirmUserPresence('Rejoin your recovered AWiki identity on this device');
|
|
1798
|
+
if (!userPresenceConfirmed) {
|
|
1799
|
+
throw Object.assign(new Error('user presence denied'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
this.pendingDeviceJoin = undefined;
|
|
1803
|
+
return client.beginDeviceJoin({
|
|
1804
|
+
continuationId: continuation.continuationId,
|
|
1805
|
+
operationId: `dsh-device-join-${randomUUID()}`,
|
|
1806
|
+
userPresenceConfirmed,
|
|
1807
|
+
});
|
|
1808
|
+
});
|
|
1284
1809
|
if (!result.ok)
|
|
1285
1810
|
return result;
|
|
1286
1811
|
this.activeDeviceJoinSessionId = result.value.joinSessionId;
|
|
1287
1812
|
return this.applyCandidateJoinProgress(result.value);
|
|
1288
1813
|
}
|
|
1814
|
+
/** Read local workflow authority before offering a new login. No remote mutations. */
|
|
1815
|
+
getIdentityAccessState() {
|
|
1816
|
+
return this.run(async (client) => {
|
|
1817
|
+
const joining = await this.selectDeviceJoinSession(client) !== null;
|
|
1818
|
+
const recoveries = await client.listPendingRecoveries();
|
|
1819
|
+
const domain = this.activeClientOptions?.userServiceDomain;
|
|
1820
|
+
const choice = this.pendingDeviceJoin;
|
|
1821
|
+
return {
|
|
1822
|
+
choice: joining || choice === undefined ? null : {
|
|
1823
|
+
status: 'join-required',
|
|
1824
|
+
fullHandle: choice.fullHandle,
|
|
1825
|
+
mode: choice.mode, requiresUserPresence: choice.requiresUserPresence,
|
|
1826
|
+
},
|
|
1827
|
+
joining,
|
|
1828
|
+
recoveries: recoveries.filter(value => domain !== undefined && value.fullHandle.slice(value.fullHandle.indexOf('.') + 1) === domain),
|
|
1829
|
+
};
|
|
1830
|
+
}, { allowSignedOut: true });
|
|
1831
|
+
}
|
|
1289
1832
|
/** Restore from Core local_sessions and advance only the exact resumable Join. */
|
|
1290
1833
|
async getDeviceJoinStatus() {
|
|
1291
1834
|
const result = await this.run(async (client) => {
|
|
@@ -1376,7 +1919,26 @@ let AwikiService = (() => {
|
|
|
1376
1919
|
throw Object.assign(new Error('sas mismatch'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1377
1920
|
}
|
|
1378
1921
|
const prompt = await client.prepareDeviceJoinApproval(joinSessionId);
|
|
1379
|
-
|
|
1922
|
+
try {
|
|
1923
|
+
return await client.confirmDeviceJoinApproval(prompt.approvalHandle);
|
|
1924
|
+
}
|
|
1925
|
+
catch (error) {
|
|
1926
|
+
// Approval is a remote mutation: its response may fail after Core has
|
|
1927
|
+
// already committed both the provider document and the local terminal
|
|
1928
|
+
// Join state. Re-read the authoritative terminal state before exposing
|
|
1929
|
+
// a false failure to the administrator.
|
|
1930
|
+
try {
|
|
1931
|
+
await client.syncDeviceManagement();
|
|
1932
|
+
}
|
|
1933
|
+
catch { }
|
|
1934
|
+
try {
|
|
1935
|
+
const recovered = await client.getLocalDeviceJoinVerificationProgress(joinSessionId);
|
|
1936
|
+
if (adminJoinPhase(recovered) === 'authorized')
|
|
1937
|
+
return recovered;
|
|
1938
|
+
}
|
|
1939
|
+
catch { }
|
|
1940
|
+
throw error;
|
|
1941
|
+
}
|
|
1380
1942
|
});
|
|
1381
1943
|
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
1382
1944
|
}
|
|
@@ -1410,6 +1972,53 @@ let AwikiService = (() => {
|
|
|
1410
1972
|
return this.deviceManagementSnapshot(client);
|
|
1411
1973
|
});
|
|
1412
1974
|
}
|
|
1975
|
+
/** Prepare a short-lived Core authorization without exposing it to Browser. */
|
|
1976
|
+
async prepareRootTransfer(request) {
|
|
1977
|
+
const deviceId = this.deviceIds.get(request?.deviceRef);
|
|
1978
|
+
if (deviceId === undefined)
|
|
1979
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1980
|
+
return this.run(async (client) => {
|
|
1981
|
+
const did = await this.requireRootTransferRecipient(client, deviceId);
|
|
1982
|
+
const preparation = await client.prepareRootKeyTransfer(deviceId);
|
|
1983
|
+
if (preparation.recipient.did !== did || preparation.recipient.deviceId !== deviceId) {
|
|
1984
|
+
throw Object.assign(new Error('root transfer recipient mismatch'), { name: 'AwikiSdkError', code: 'remote' });
|
|
1985
|
+
}
|
|
1986
|
+
for (const [reference, pending] of this.rootTransfers) {
|
|
1987
|
+
if (pending.deviceId === deviceId)
|
|
1988
|
+
this.rootTransfers.delete(reference);
|
|
1989
|
+
}
|
|
1990
|
+
const transferRef = `root-transfer-${randomUUID()}`;
|
|
1991
|
+
this.rootTransfers.set(transferRef, {
|
|
1992
|
+
authorizationHandle: preparation.authorizationHandle,
|
|
1993
|
+
deviceId,
|
|
1994
|
+
deviceRef: request.deviceRef,
|
|
1995
|
+
did,
|
|
1996
|
+
});
|
|
1997
|
+
return { transferRef, deviceRef: request.deviceRef, expiresAt: preparation.expiresAt };
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
/** Authenticate locally, recheck fresh context, then consume one exact Core authorization. */
|
|
2001
|
+
async confirmRootTransfer(request) {
|
|
2002
|
+
const pending = this.rootTransfers.get(request?.transferRef);
|
|
2003
|
+
if (pending === undefined)
|
|
2004
|
+
return { ok: false, error: failure('invalid-request') };
|
|
2005
|
+
this.rootTransfers.delete(request.transferRef);
|
|
2006
|
+
return this.run(async (client) => {
|
|
2007
|
+
const confirmed = await client.confirmUserPresence('Grant AWiki device management access');
|
|
2008
|
+
if (!confirmed) {
|
|
2009
|
+
throw Object.assign(new Error('user presence denied'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2010
|
+
}
|
|
2011
|
+
const did = await this.requireRootTransferRecipient(client, pending.deviceId);
|
|
2012
|
+
if (did !== pending.did) {
|
|
2013
|
+
throw Object.assign(new Error('root transfer identity changed'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2014
|
+
}
|
|
2015
|
+
const sent = await client.confirmAndSendRootKeyTransfer(pending.authorizationHandle);
|
|
2016
|
+
if (sent.recipientDeviceId !== pending.deviceId) {
|
|
2017
|
+
throw Object.assign(new Error('root transfer receipt mismatch'), { name: 'AwikiSdkError', code: 'remote' });
|
|
2018
|
+
}
|
|
2019
|
+
return { deviceRef: pending.deviceRef, acceptedAt: sent.acceptedAt };
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
1413
2022
|
/**
|
|
1414
2023
|
* Update the deployment identity's public WNS display name.
|
|
1415
2024
|
* @param request - replacement display name selected by the user.
|
|
@@ -1457,33 +2066,35 @@ let AwikiService = (() => {
|
|
|
1457
2066
|
const normalized = normalizeRecoveryOperation(request);
|
|
1458
2067
|
if (normalized === undefined)
|
|
1459
2068
|
return { ok: false, error: failure('invalid-request') };
|
|
1460
|
-
|
|
1461
|
-
if (result.ok && !await this.applyRecoveredSession(result.value)) {
|
|
1462
|
-
return { ok: false, error: failure('remote') };
|
|
1463
|
-
}
|
|
1464
|
-
return result;
|
|
2069
|
+
return this.run(client => client.activateRecovery(normalized), { allowSignedOut: true });
|
|
1465
2070
|
}
|
|
1466
2071
|
/** Read durable recovery state before deciding whether a retry is valid. */
|
|
1467
2072
|
async getRecoveryStatus(request) {
|
|
1468
2073
|
const normalized = normalizeRecoveryOperation(request);
|
|
1469
2074
|
if (normalized === undefined)
|
|
1470
2075
|
return { ok: false, error: failure('invalid-request') };
|
|
1471
|
-
|
|
1472
|
-
if (result.ok && !await this.applyRecoveredSession(result.value)) {
|
|
1473
|
-
return { ok: false, error: failure('remote') };
|
|
1474
|
-
}
|
|
1475
|
-
return result;
|
|
2076
|
+
return this.run(client => client.getRecoveryStatus(normalized), { allowSignedOut: true });
|
|
1476
2077
|
}
|
|
1477
2078
|
/** Resume only the exact Core-owned operation selected by the browser. */
|
|
1478
2079
|
async resumeRecovery(request) {
|
|
1479
2080
|
const normalized = normalizeRecoveryOperation(request);
|
|
1480
2081
|
if (normalized === undefined)
|
|
1481
2082
|
return { ok: false, error: failure('invalid-request') };
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
2083
|
+
return this.run(client => client.resumeRecovery(normalized), { allowSignedOut: true });
|
|
2084
|
+
}
|
|
2085
|
+
/** Explicitly enter an applied identity; reads and late Core completions never sign in. */
|
|
2086
|
+
async enterRecoveredSession(request) {
|
|
2087
|
+
const normalized = normalizeRecoveryOperation(request);
|
|
2088
|
+
if (normalized === undefined)
|
|
2089
|
+
return { ok: false, error: failure('invalid-request') };
|
|
2090
|
+
const requestGeneration = this.sessionRevision;
|
|
2091
|
+
const result = await this.run(client => client.getRecoveryStatus(normalized), { allowSignedOut: true });
|
|
2092
|
+
if (!result.ok)
|
|
2093
|
+
return result;
|
|
2094
|
+
if (result.value.phase !== 'applied' || !result.value.allowedActions?.includes('activate_identity')) {
|
|
2095
|
+
return { ok: false, error: failure('conflict') };
|
|
1485
2096
|
}
|
|
1486
|
-
return result;
|
|
2097
|
+
return this.applyRecoveredSession(result.value, requestGeneration);
|
|
1487
2098
|
}
|
|
1488
2099
|
/** Discard only a pre-attempt operation; Core rejects post-attempt deletion. */
|
|
1489
2100
|
async discardRecovery(request) {
|
|
@@ -1550,6 +2161,12 @@ let AwikiService = (() => {
|
|
|
1550
2161
|
return result.ok ? { ok: true, value: { completed: true } } : result;
|
|
1551
2162
|
}
|
|
1552
2163
|
/** Read one authoritative versioned member page. */
|
|
2164
|
+
getDisplayProfiles(peers) {
|
|
2165
|
+
if (!Array.isArray(peers) || peers.length > 100 || peers.some(peer => typeof peer !== 'string' || !peer.startsWith('did:'))) {
|
|
2166
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
2167
|
+
}
|
|
2168
|
+
return this.run(client => client.getDisplayProfiles(peers));
|
|
2169
|
+
}
|
|
1553
2170
|
listGroupMembers(request) {
|
|
1554
2171
|
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
1555
2172
|
if (groupDid === undefined
|
|
@@ -1812,10 +2429,14 @@ let AwikiService = (() => {
|
|
|
1812
2429
|
catch {
|
|
1813
2430
|
return { ok: false, error: failure('invalid-request') };
|
|
1814
2431
|
}
|
|
2432
|
+
const requestGeneration = this.sessionRevision;
|
|
1815
2433
|
return this.run(async (client) => {
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
2434
|
+
const page = normalized.folder === 'sent'
|
|
2435
|
+
? await this.mailListClient.listOutbound(normalized)
|
|
2436
|
+
: await client.listMailInbox(normalized);
|
|
2437
|
+
if (this.sessionRevision !== requestGeneration)
|
|
2438
|
+
throw new AwikiSdkError('network');
|
|
2439
|
+
return page;
|
|
1819
2440
|
});
|
|
1820
2441
|
}
|
|
1821
2442
|
/** Read one bounded plain-text mail message. */
|
|
@@ -1827,15 +2448,7 @@ let AwikiService = (() => {
|
|
|
1827
2448
|
catch {
|
|
1828
2449
|
return { ok: false, error: failure('invalid-request') };
|
|
1829
2450
|
}
|
|
1830
|
-
return this.run(
|
|
1831
|
-
if (!isLocalSentMailId(normalized.messageId))
|
|
1832
|
-
return client.readMail(normalized);
|
|
1833
|
-
const local = await this.sentMailStore.read(await this.ownerDid(client), normalized.messageId);
|
|
1834
|
-
if (local === undefined) {
|
|
1835
|
-
throw Object.assign(new Error('sent mail not found'), { name: 'AwikiSdkError', code: 'not-found' });
|
|
1836
|
-
}
|
|
1837
|
-
return local;
|
|
1838
|
-
});
|
|
2451
|
+
return this.run(client => client.readMail(normalized));
|
|
1839
2452
|
}
|
|
1840
2453
|
/** Mark explicitly selected mail messages read. Browser callers require an explicit click. */
|
|
1841
2454
|
markMailRead(request) {
|
|
@@ -1850,22 +2463,7 @@ let AwikiService = (() => {
|
|
|
1850
2463
|
catch {
|
|
1851
2464
|
return { ok: false, error: failure('invalid-request') };
|
|
1852
2465
|
}
|
|
1853
|
-
return this.run(
|
|
1854
|
-
const result = await client.sendMail(normalized);
|
|
1855
|
-
if (!result.accepted)
|
|
1856
|
-
return result;
|
|
1857
|
-
try {
|
|
1858
|
-
const ownerDid = await this.ownerDid(client);
|
|
1859
|
-
const account = await client.getMailAccount().catch(() => undefined);
|
|
1860
|
-
await this.sentMailStore.append(ownerDid, normalized, result, account);
|
|
1861
|
-
return result;
|
|
1862
|
-
}
|
|
1863
|
-
catch {
|
|
1864
|
-
return result.warnings.length >= 100
|
|
1865
|
-
? result
|
|
1866
|
-
: { ...result, warnings: [...result.warnings, 'Sent history could not be saved locally.'] };
|
|
1867
|
-
}
|
|
1868
|
-
});
|
|
2466
|
+
return this.run(client => client.sendMail(normalized));
|
|
1869
2467
|
}
|
|
1870
2468
|
/**
|
|
1871
2469
|
* Permanently remove the exact SDK-owned local state after an explicit browser acknowledgement.
|
|
@@ -1877,11 +2475,19 @@ let AwikiService = (() => {
|
|
|
1877
2475
|
if (request?.confirmation !== AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION) {
|
|
1878
2476
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1879
2477
|
}
|
|
2478
|
+
// Fence pending post-Recovery work before the serialized clear reaches the head of the queue.
|
|
2479
|
+
this.sessionRevision += 1;
|
|
1880
2480
|
return this.mutateSession(async () => {
|
|
1881
2481
|
const provider = this.provider;
|
|
1882
2482
|
if (provider !== undefined)
|
|
1883
2483
|
await this.stopProviderRuntime(provider);
|
|
1884
|
-
|
|
2484
|
+
let result = await this.run(client => client.clearLocalData(), { allowSignedOut: true });
|
|
2485
|
+
// Core's clear is local and idempotent. A provider mutation that was still
|
|
2486
|
+
// settling can surface once as the deliberately opaque `remote` failure.
|
|
2487
|
+
// Retry only that class once after the runtime has already been stopped.
|
|
2488
|
+
if (!result.ok && result.error.code === 'remote') {
|
|
2489
|
+
result = await this.run(client => client.clearLocalData(), { allowSignedOut: true });
|
|
2490
|
+
}
|
|
1885
2491
|
if (!result.ok) {
|
|
1886
2492
|
if (provider !== undefined && this.provider === provider)
|
|
1887
2493
|
this.ensureProviderRuntime(provider);
|
|
@@ -1889,7 +2495,7 @@ let AwikiService = (() => {
|
|
|
1889
2495
|
}
|
|
1890
2496
|
try {
|
|
1891
2497
|
await this.imageAttachmentCache.clear();
|
|
1892
|
-
await this.
|
|
2498
|
+
await clearLegacySentMailCache(this.activeClientOptions?.stateRoot ?? this.resolved.stateRoot);
|
|
1893
2499
|
await this.conversationPreferenceStore.clear();
|
|
1894
2500
|
await this.sessionStore.signIn();
|
|
1895
2501
|
this.signedOut = false;
|
|
@@ -1906,81 +2512,102 @@ let AwikiService = (() => {
|
|
|
1906
2512
|
});
|
|
1907
2513
|
}
|
|
1908
2514
|
/** Re-enter only after Core confirms that the exact recovered identity is applied locally. */
|
|
1909
|
-
async applyRecoveredSession(progress) {
|
|
2515
|
+
async applyRecoveredSession(progress, requestGeneration) {
|
|
1910
2516
|
if (progress.phase !== 'applied')
|
|
1911
|
-
return true;
|
|
2517
|
+
return { ok: true, value: progress };
|
|
1912
2518
|
return this.mutateSession(async () => {
|
|
1913
2519
|
const provider = this.provider;
|
|
1914
2520
|
if (provider === undefined)
|
|
1915
|
-
return false;
|
|
2521
|
+
return { ok: false, error: failure('remote') };
|
|
2522
|
+
if (this.sessionRevision !== requestGeneration) {
|
|
2523
|
+
return {
|
|
2524
|
+
ok: false,
|
|
2525
|
+
error: {
|
|
2526
|
+
...failure('remote'),
|
|
2527
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(this.activeIdentityDid === progress.currentDid, 'replaced'),
|
|
2528
|
+
},
|
|
2529
|
+
};
|
|
2530
|
+
}
|
|
1916
2531
|
try {
|
|
1917
2532
|
const identity = await provider.client.getIdentity();
|
|
1918
|
-
|
|
1919
|
-
|
|
2533
|
+
const currentPrincipalMatchesRecovery = identity !== null && identity.did === progress.currentDid;
|
|
2534
|
+
if (this.provider !== provider || this.sessionRevision !== requestGeneration) {
|
|
2535
|
+
return {
|
|
2536
|
+
ok: false,
|
|
2537
|
+
error: {
|
|
2538
|
+
...failure('remote'),
|
|
2539
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(currentPrincipalMatchesRecovery, 'replaced'),
|
|
2540
|
+
},
|
|
2541
|
+
};
|
|
2542
|
+
}
|
|
2543
|
+
if (!currentPrincipalMatchesRecovery) {
|
|
2544
|
+
return {
|
|
2545
|
+
ok: false,
|
|
2546
|
+
error: {
|
|
2547
|
+
...failure('remote'),
|
|
2548
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(false, 'current'),
|
|
2549
|
+
},
|
|
2550
|
+
};
|
|
2551
|
+
}
|
|
1920
2552
|
const alreadyActive = this.signedOut === false && this.activeIdentityDid === identity.did;
|
|
1921
2553
|
if (!alreadyActive) {
|
|
1922
2554
|
await this.sessionStore.signIn();
|
|
2555
|
+
if (this.provider !== provider || this.sessionRevision !== requestGeneration) {
|
|
2556
|
+
return {
|
|
2557
|
+
ok: false,
|
|
2558
|
+
error: {
|
|
2559
|
+
...failure('remote'),
|
|
2560
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(true, 'replaced'),
|
|
2561
|
+
},
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
1923
2564
|
this.signedOut = false;
|
|
1924
2565
|
this.activeIdentityDid = identity.did;
|
|
1925
2566
|
this.invalidateSummaries();
|
|
2567
|
+
requestGeneration = this.sessionRevision;
|
|
2568
|
+
}
|
|
2569
|
+
const mailRecoveryObservability = await this.reconcileRecoveredMailbox(provider);
|
|
2570
|
+
if (this.provider !== provider || this.sessionRevision !== requestGeneration) {
|
|
2571
|
+
return {
|
|
2572
|
+
ok: false,
|
|
2573
|
+
error: {
|
|
2574
|
+
...failure('remote'),
|
|
2575
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(true, 'replaced'),
|
|
2576
|
+
},
|
|
2577
|
+
};
|
|
1926
2578
|
}
|
|
1927
|
-
const reconciled = await this.reconcileRecoveredIdentity(provider, progress.operationId);
|
|
1928
|
-
if (this.provider !== provider)
|
|
1929
|
-
return false;
|
|
1930
2579
|
if (!alreadyActive) {
|
|
1931
2580
|
const session = { status: 'active', identity };
|
|
1932
2581
|
this.publishSession(session);
|
|
1933
2582
|
this.ensureProviderRuntime(provider);
|
|
1934
2583
|
}
|
|
1935
|
-
return
|
|
2584
|
+
return { ok: true, value: { ...progress, mailRecoveryObservability } };
|
|
1936
2585
|
}
|
|
1937
2586
|
catch {
|
|
1938
|
-
return false;
|
|
2587
|
+
return { ok: false, error: failure('remote') };
|
|
1939
2588
|
}
|
|
1940
2589
|
});
|
|
1941
2590
|
}
|
|
1942
|
-
/** Rebind Mail first-use ownership and
|
|
1943
|
-
async
|
|
1944
|
-
const logger = this.ctx.logger('awiki-recovery');
|
|
1945
|
-
let mailboxRestored = false;
|
|
2591
|
+
/** Rebind Mail first-use ownership and retain only its closed, secret-free classification. */
|
|
2592
|
+
async reconcileRecoveredMailbox(provider) {
|
|
1946
2593
|
try {
|
|
1947
2594
|
await provider.client.getMailAccount();
|
|
1948
|
-
|
|
2595
|
+
return successfulMailRecoveryObservability();
|
|
1949
2596
|
}
|
|
1950
|
-
catch {
|
|
1951
|
-
|
|
1952
|
-
}
|
|
1953
|
-
const target = this.recoveryReconciliationTarget;
|
|
1954
|
-
if (target === undefined)
|
|
1955
|
-
return mailboxRestored;
|
|
1956
|
-
try {
|
|
1957
|
-
const authority = await provider.client.issueRecoveryAttestation({ operationId });
|
|
1958
|
-
const response = await this.externalHttpAuth.dispatch(new Request(target.endpoint, {
|
|
1959
|
-
method: 'POST',
|
|
1960
|
-
headers: { 'content-type': 'application/json' },
|
|
1961
|
-
body: JSON.stringify({ attestation: authority.attestation }),
|
|
1962
|
-
}), request => fetch(request));
|
|
1963
|
-
if (!await acceptsRecoveryReconciliation(response)) {
|
|
1964
|
-
logger.warn('awiki: recovered model account reconciliation is pending');
|
|
1965
|
-
return false;
|
|
1966
|
-
}
|
|
1967
|
-
return mailboxRestored;
|
|
1968
|
-
}
|
|
1969
|
-
catch {
|
|
1970
|
-
logger.warn('awiki: recovered model account reconciliation is pending');
|
|
1971
|
-
return false;
|
|
2597
|
+
catch (error) {
|
|
2598
|
+
return failedMailRecoveryObservability(error);
|
|
1972
2599
|
}
|
|
1973
2600
|
}
|
|
1974
2601
|
/** Select the only resumable new-device session; Core local_sessions is the sole restart SoT. */
|
|
1975
2602
|
async selectDeviceJoinSession(client) {
|
|
1976
2603
|
const sessions = await client.listLocalDeviceJoinSessions();
|
|
2604
|
+
const resumable = sessions.filter(value => (value.side === 'new_device' && RESUMABLE_JOIN_PHASES.has(value.localPhase)));
|
|
1977
2605
|
if (this.activeDeviceJoinSessionId !== undefined) {
|
|
1978
|
-
const tracked =
|
|
2606
|
+
const tracked = resumable.find(value => value.joinSessionId === this.activeDeviceJoinSessionId);
|
|
1979
2607
|
if (tracked !== undefined)
|
|
1980
2608
|
return tracked.joinSessionId;
|
|
1981
2609
|
this.activeDeviceJoinSessionId = undefined;
|
|
1982
2610
|
}
|
|
1983
|
-
const resumable = sessions.filter(value => (value.side === 'new_device' && RESUMABLE_JOIN_PHASES.has(value.localPhase)));
|
|
1984
2611
|
if (resumable.length > 1) {
|
|
1985
2612
|
throw Object.assign(new Error('multiple local joins'), { name: 'AwikiSdkError', code: 'conflict' });
|
|
1986
2613
|
}
|
|
@@ -2048,6 +2675,26 @@ let AwikiService = (() => {
|
|
|
2048
2675
|
throw Object.assign(new Error('device manager required'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2049
2676
|
}
|
|
2050
2677
|
}
|
|
2678
|
+
async requireRootTransferRecipient(client, deviceId) {
|
|
2679
|
+
if (!client.trustedUserPresenceSupported) {
|
|
2680
|
+
throw Object.assign(new Error('trusted user presence unsupported'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2681
|
+
}
|
|
2682
|
+
await this.requireDeviceManager(client);
|
|
2683
|
+
await client.syncDeviceManagement();
|
|
2684
|
+
const identity = await client.getIdentity();
|
|
2685
|
+
if (identity === null) {
|
|
2686
|
+
throw Object.assign(new Error('identity required'), { name: 'AwikiSdkError', code: 'not-registered' });
|
|
2687
|
+
}
|
|
2688
|
+
const target = (await client.getDeviceRegistry()).find(device => device.deviceId === deviceId);
|
|
2689
|
+
if (target === undefined
|
|
2690
|
+
|| target.isCurrent
|
|
2691
|
+
|| target.status !== 'active'
|
|
2692
|
+
|| target.role !== 'member'
|
|
2693
|
+
|| target.managementReady) {
|
|
2694
|
+
throw Object.assign(new Error('root transfer recipient unavailable'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2695
|
+
}
|
|
2696
|
+
return identity.did;
|
|
2697
|
+
}
|
|
2051
2698
|
requestRef(joinSessionId) {
|
|
2052
2699
|
const existing = this.requestRefs.get(joinSessionId);
|
|
2053
2700
|
if (existing !== undefined)
|
|
@@ -2074,8 +2721,10 @@ let AwikiService = (() => {
|
|
|
2074
2721
|
this.requestSessions.clear();
|
|
2075
2722
|
this.deviceRefs.clear();
|
|
2076
2723
|
this.deviceIds.clear();
|
|
2724
|
+
this.rootTransfers.clear();
|
|
2077
2725
|
return {
|
|
2078
2726
|
canManage: false,
|
|
2727
|
+
rootTransferSupported: client.trustedUserPresenceSupported,
|
|
2079
2728
|
...current.role === undefined ? {} : { role: current.role },
|
|
2080
2729
|
readiness: current.readiness,
|
|
2081
2730
|
devices: [],
|
|
@@ -2087,11 +2736,25 @@ let AwikiService = (() => {
|
|
|
2087
2736
|
.filter(request => request.claimedByCurrentDevice)
|
|
2088
2737
|
.map(request => client.getLocalDeviceJoinVerificationProgress(request.joinSessionId).catch(() => undefined)));
|
|
2089
2738
|
const devices = await client.getDeviceRegistry();
|
|
2739
|
+
const identity = await client.getIdentity();
|
|
2740
|
+
const joinedAtByDeviceId = new Map();
|
|
2741
|
+
for (const request of requests) {
|
|
2742
|
+
const previous = joinedAtByDeviceId.get(request.protocolDeviceId);
|
|
2743
|
+
if (previous === undefined || request.issuedAt > previous) {
|
|
2744
|
+
joinedAtByDeviceId.set(request.protocolDeviceId, request.issuedAt);
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
const initialDeviceJoinedAt = identity === null
|
|
2748
|
+
? undefined
|
|
2749
|
+
: new Date(identity.registeredAt).toISOString();
|
|
2090
2750
|
return {
|
|
2091
2751
|
canManage: true,
|
|
2752
|
+
rootTransferSupported: client.trustedUserPresenceSupported,
|
|
2092
2753
|
role: 'admin',
|
|
2093
2754
|
readiness: 'admin_ready',
|
|
2094
|
-
devices: devices
|
|
2755
|
+
devices: devices
|
|
2756
|
+
.filter(device => device.status === 'active')
|
|
2757
|
+
.map(device => this.publicDevice(device, joinedAtByDeviceId.get(device.deviceId) ?? (device.isCurrent ? initialDeviceJoinedAt : undefined))),
|
|
2095
2758
|
requests: requests.map(request => ({
|
|
2096
2759
|
requestRef: this.requestRef(request.joinSessionId),
|
|
2097
2760
|
candidateKeyFingerprint: request.candidateKeyFingerprint,
|
|
@@ -2103,9 +2766,12 @@ let AwikiService = (() => {
|
|
|
2103
2766
|
})),
|
|
2104
2767
|
};
|
|
2105
2768
|
}
|
|
2106
|
-
publicDevice(device) {
|
|
2769
|
+
publicDevice(device, joinedAt) {
|
|
2770
|
+
const fingerprint = createHash('sha256').update(device.deviceId).digest('hex').slice(0, 8).toUpperCase();
|
|
2107
2771
|
return {
|
|
2108
2772
|
deviceRef: this.deviceRef(device.deviceId),
|
|
2773
|
+
displayId: `${fingerprint.slice(0, 4)}-${fingerprint.slice(4)}`,
|
|
2774
|
+
...joinedAt === undefined ? {} : { joinedAt },
|
|
2109
2775
|
status: device.status,
|
|
2110
2776
|
role: device.role,
|
|
2111
2777
|
managementReady: device.managementReady,
|
|
@@ -2129,6 +2795,7 @@ let AwikiService = (() => {
|
|
|
2129
2795
|
this.requestSessions.clear();
|
|
2130
2796
|
this.deviceRefs.clear();
|
|
2131
2797
|
this.deviceIds.clear();
|
|
2798
|
+
this.rootTransfers.clear();
|
|
2132
2799
|
for (const controller of this.activeSummaryRequests)
|
|
2133
2800
|
controller.abort();
|
|
2134
2801
|
this.activeSummaryRequests.clear();
|
|
@@ -2150,6 +2817,10 @@ let AwikiService = (() => {
|
|
|
2150
2817
|
/** Invoke the current client and normalize every rejection to a public result. */
|
|
2151
2818
|
async run(operation, options = {}) {
|
|
2152
2819
|
try {
|
|
2820
|
+
const generation = this.runtimeGeneration;
|
|
2821
|
+
if (this.getUpdatePolicyStatus().restricted) {
|
|
2822
|
+
return { ok: false, error: failure('client-version-unsupported') };
|
|
2823
|
+
}
|
|
2153
2824
|
if (options.allowSignedOut !== true && await this.isSignedOut()) {
|
|
2154
2825
|
return { ok: false, error: failure('signed-out') };
|
|
2155
2826
|
}
|
|
@@ -2157,6 +2828,9 @@ let AwikiService = (() => {
|
|
|
2157
2828
|
if (provider === undefined)
|
|
2158
2829
|
throw new ProviderUnavailableError();
|
|
2159
2830
|
const value = await operation(provider.client);
|
|
2831
|
+
if (this.runtimeGeneration !== generation || this.provider !== provider) {
|
|
2832
|
+
return { ok: false, error: failure('conflict') };
|
|
2833
|
+
}
|
|
2160
2834
|
if (options.skipAttachmentByteValidation !== true && containsUnexpectedBinary(value, new Set())) {
|
|
2161
2835
|
return { ok: false, error: failure('remote') };
|
|
2162
2836
|
}
|
|
@@ -2166,6 +2840,11 @@ let AwikiService = (() => {
|
|
|
2166
2840
|
return { ok: false, error: normalizeFailure(error) };
|
|
2167
2841
|
}
|
|
2168
2842
|
}
|
|
2843
|
+
assertVersionSupported() {
|
|
2844
|
+
if (this.getUpdatePolicyStatus().restricted) {
|
|
2845
|
+
throw new Error('awiki: this plugin version must be updated before using the active tenant');
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2169
2848
|
/** Validate mail input before entering the provider and preserve fixed public failures. */
|
|
2170
2849
|
runValidatedMail(validate, operation) {
|
|
2171
2850
|
let request;
|
|
@@ -2226,12 +2905,20 @@ let AwikiService = (() => {
|
|
|
2226
2905
|
}
|
|
2227
2906
|
/** Serialize sign-in, sign-out, and destructive clear transitions. */
|
|
2228
2907
|
mutateSession(operation) {
|
|
2229
|
-
const
|
|
2908
|
+
const generation = this.runtimeGeneration;
|
|
2909
|
+
if (this.tenantSwitching)
|
|
2910
|
+
return Promise.resolve({ ok: false, error: failure('conflict') });
|
|
2911
|
+
const invoke = () => this.tenantSwitching || generation !== this.runtimeGeneration
|
|
2912
|
+
? Promise.resolve({ ok: false, error: failure('conflict') })
|
|
2913
|
+
: operation();
|
|
2914
|
+
const pending = this.sessionMutation.then(invoke, invoke);
|
|
2230
2915
|
this.sessionMutation = pending.then(() => undefined, () => undefined);
|
|
2231
2916
|
return pending;
|
|
2232
2917
|
}
|
|
2233
2918
|
/** Start identity realtime and the optional Agent consumer without blocking identity success. */
|
|
2234
2919
|
ensureProviderRuntime(provider) {
|
|
2920
|
+
if (this.tenantSwitching)
|
|
2921
|
+
return;
|
|
2235
2922
|
const identityDid = this.activeIdentityDid;
|
|
2236
2923
|
if (provider.runtimeReplacement !== undefined)
|
|
2237
2924
|
return;
|
|
@@ -2267,6 +2954,8 @@ let AwikiService = (() => {
|
|
|
2267
2954
|
provider.runtimeReplacement = observed;
|
|
2268
2955
|
}
|
|
2269
2956
|
ensureRealtimeSupervisor(provider) {
|
|
2957
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
2958
|
+
return;
|
|
2270
2959
|
if (!this.resolved.realtimeEnabled
|
|
2271
2960
|
|| provider.realtimeSupervisor !== undefined
|
|
2272
2961
|
|| provider.realtimeStartup !== undefined
|
|
@@ -2326,6 +3015,8 @@ let AwikiService = (() => {
|
|
|
2326
3015
|
await provider.agentConsumer?.reconcileOnce();
|
|
2327
3016
|
}
|
|
2328
3017
|
ensureAgentConsumer(provider) {
|
|
3018
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
3019
|
+
return;
|
|
2329
3020
|
const workspaceContext = this.workspaceContext;
|
|
2330
3021
|
const source = provider.client.agentInbox ?? provider.client.listener;
|
|
2331
3022
|
const identityDid = this.activeIdentityDid;
|
|
@@ -2348,6 +3039,7 @@ let AwikiService = (() => {
|
|
|
2348
3039
|
const agents = new DshAwikiListenerAgentRuntime(workspaceContext, this.resolved.listener.workspacePath);
|
|
2349
3040
|
const consumer = new AwikiAgentListener(source, agents, {
|
|
2350
3041
|
...this.resolved.listener,
|
|
3042
|
+
stateRoot: this.activeClientOptions?.stateRoot ?? this.resolved.listener.stateRoot,
|
|
2351
3043
|
identityScope: identityDid,
|
|
2352
3044
|
}, logger);
|
|
2353
3045
|
provider.agentConsumer = consumer;
|