@awiki/dsh-plugin 0.3.7 → 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 +109 -71
- package/README.zh.md +89 -61
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/cordis.patch.yml +6 -5
- package/lib/builtin-tenants.json +22 -0
- package/lib/builtin-tenants.sha256 +1 -0
- package/lib/client.js +7973 -2264
- package/lib/client.js.map +1 -1
- package/lib/index.js +3481 -1145
- package/lib/model-proxy-contract.js +11 -3
- package/lib/provider.js +73 -17
- package/lib/{sdk-adapter--trNlr-T.mjs → sdk-adapter-CYcJH7sM.mjs} +374 -101
- package/lib/typert.host.js +1595 -168
- package/lib/typert.remote-client.d.ts +43 -7
- package/lib/typert.remote-client.d.ts.map +1 -1
- package/lib/typert.remote-client.js +1595 -168
- 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 +33 -0
- package/lib/types/client/AwikiDevices.d.ts.map +1 -0
- package/lib/types/client/AwikiDevices.js +141 -0
- package/lib/types/client/AwikiDevices.js.map +1 -0
- 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 +15 -8
- package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
- package/lib/types/client/AwikiIdentityAccess.js +203 -39
- 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 +1 -1
- package/lib/types/client/AwikiMail.d.ts.map +1 -1
- package/lib/types/client/AwikiMail.js +17 -137
- 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 -28
- 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 +84 -44
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +515 -309
- 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 +63 -19
- 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 +192 -14
- package/lib/types/client/settings-locales.js.map +1 -1
- package/lib/types/client/slots.d.ts +20 -10
- 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/conversation-preferences.d.ts.map +1 -1
- package/lib/types/conversation-preferences.js +0 -17
- package/lib/types/conversation-preferences.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 +164 -41
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1562 -529
- 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/listener-state.d.ts +5 -3
- package/lib/types/listener-state.d.ts.map +1 -1
- package/lib/types/listener-state.js +28 -13
- package/lib/types/listener-state.js.map +1 -1
- package/lib/types/listener.d.ts +9 -32
- package/lib/types/listener.d.ts.map +1 -1
- package/lib/types/listener.js +11 -127
- package/lib/types/listener.js.map +1 -1
- 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/mail.d.ts +2 -28
- package/lib/types/mail.d.ts.map +1 -1
- package/lib/types/mail.js +2 -79
- package/lib/types/mail.js.map +1 -1
- 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 +2 -13
- package/lib/types/profile-state.d.ts.map +1 -1
- package/lib/types/profile-state.js +3 -20
- package/lib/types/profile-state.js.map +1 -1
- package/lib/types/provider-api.d.ts +134 -43
- package/lib/types/provider-api.d.ts.map +1 -1
- package/lib/types/provider.d.ts +8 -2
- package/lib/types/provider.d.ts.map +1 -1
- package/lib/types/provider.js +76 -16
- package/lib/types/provider.js.map +1 -1
- package/lib/types/realtime-supervisor.d.ts +60 -0
- package/lib/types/realtime-supervisor.d.ts.map +1 -0
- package/lib/types/realtime-supervisor.js +255 -0
- package/lib/types/realtime-supervisor.js.map +1 -0
- package/lib/types/sdk-adapter.d.ts +62 -11
- package/lib/types/sdk-adapter.d.ts.map +1 -1
- package/lib/types/sdk-adapter.js +285 -123
- 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/tools.d.ts +1 -1
- package/lib/types/tools.d.ts.map +1 -1
- package/lib/types/tools.js +4 -20
- package/lib/types/tools.js.map +1 -1
- package/lib/types/types.d.ts +249 -66
- 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 +26 -8
- package/lib/types/base64.d.ts +0 -4
- package/lib/types/base64.d.ts.map +0 -1
- package/lib/types/base64.js +0 -28
- package/lib/types/base64.js.map +0 -1
- package/lib/types/client/mail-attachment.d.ts +0 -40
- package/lib/types/client/mail-attachment.d.ts.map +0 -1
- package/lib/types/client/mail-attachment.js +0 -187
- package/lib/types/client/mail-attachment.js.map +0 -1
- package/lib/types/sent-mail-store.d.ts +0 -27
- package/lib/types/sent-mail-store.d.ts.map +0 -1
- package/lib/types/sent-mail-store.js +0 -348
- 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,9 +32,10 @@ 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
|
-
import { createHash } from 'node:crypto';
|
|
38
|
-
import { existsSync } from 'node:fs';
|
|
38
|
+
import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
39
39
|
import { homedir } from 'node:os';
|
|
40
40
|
import { isAbsolute, join } from 'node:path';
|
|
41
41
|
import z from '@deepseek-ai/schemastery';
|
|
@@ -43,21 +43,29 @@ import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
|
43
43
|
import { settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
44
44
|
import { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "./types.js";
|
|
45
45
|
import { AwikiExternalHttpAuthError, createAwikiExternalHttpAuth, externalHttpAuthError, mapProviderError as mapExternalHttpProviderError, } from "./external-http-auth.js";
|
|
46
|
-
import {
|
|
47
|
-
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";
|
|
48
51
|
import { registerAwikiTools } from "./tools.js";
|
|
49
|
-
import {
|
|
52
|
+
import { mailInboxRequest, mailMarkReadRequest, mailReadRequest, mailSendRequest, } from "./mail.js";
|
|
50
53
|
import { AwikiSettingsSchema, validateAwikiSettings, } from "./settings.js";
|
|
51
54
|
import { AWIKI_SETTINGS_NAMESPACE, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain } from "./domain.js";
|
|
55
|
+
import { AWIKI_DEFAULT_BUILTIN_TENANT, } from "./builtin-tenant-config.js";
|
|
52
56
|
import { AWIKI_SETTINGS_RPC_CHANNEL } from "./settings-rpc-contract.js";
|
|
53
57
|
import { createAwikiSettingsRpcHandler } from "./settings-rpc.js";
|
|
54
58
|
import { AwikiSessionStore } from "./session.js";
|
|
55
59
|
import { AwikiImageAttachmentCache, minimumImageAttachmentCacheMaxBytes } from "./attachment-cache.js";
|
|
56
|
-
import { AwikiSentMailStore, isLocalSentMailId } from "./sent-mail-store.js";
|
|
57
60
|
import { AwikiConversationPreferenceStore, normalizeConversationPreferenceMutation, } from "./conversation-preferences.js";
|
|
58
61
|
import { AwikiAgentListener, DshAwikiListenerAgentRuntime, } from "./listener.js";
|
|
59
|
-
import {
|
|
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";
|
|
60
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";
|
|
61
69
|
export { AWIKI_EXTERNAL_HTTP_MAX_BODY_BYTES, AwikiExternalHttpAuthError, } from "./external-http-auth.js";
|
|
62
70
|
export { AWIKI_DOMAIN_FIELD, AWIKI_SETTINGS_NAMESPACE, AwikiSettingsSchema, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain, validateAwikiSettings, } from "./settings.js";
|
|
63
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";
|
|
@@ -67,37 +75,37 @@ export const DEFAULT_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
|
67
75
|
export const DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES = 64 * 1024 * 1024;
|
|
68
76
|
/** Default browser polling interval while the AWiki drawer is open. */
|
|
69
77
|
export const DEFAULT_POLL_INTERVAL_MS = 3_000;
|
|
70
|
-
/**
|
|
71
|
-
export const DEFAULT_AWIKI_SERVICE_URL =
|
|
72
|
-
/**
|
|
73
|
-
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}`;
|
|
74
82
|
/** Host-owned model input cap after message minimization. */
|
|
75
83
|
export const DEFAULT_SUMMARY_MAX_INPUT_BYTES = 32 * 1024;
|
|
76
84
|
/** Hard limit for one user-triggered conversation summary. */
|
|
77
85
|
export const MAX_SUMMARY_MESSAGES = 50;
|
|
78
86
|
/** Loader schema for the Host deployment configuration. */
|
|
79
87
|
export const Config = z.object({
|
|
80
|
-
userServiceUrl: z.string(),
|
|
88
|
+
userServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
81
89
|
userServiceDomain: z.string().default(DEFAULT_AWIKI_DOMAIN),
|
|
82
|
-
|
|
90
|
+
legacyTenantSlot: z.string(),
|
|
91
|
+
messageServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
83
92
|
mailServiceUrl: z.string(),
|
|
84
|
-
messageServicePublicUrl: z.string(),
|
|
85
|
-
messageServiceDid: z.string(),
|
|
93
|
+
messageServicePublicUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
94
|
+
messageServiceDid: z.string().default(DEFAULT_AWIKI_MESSAGE_SERVICE_DID),
|
|
86
95
|
allowedAttachmentOrigins: z.array(z.string()).default([]),
|
|
87
96
|
allowInsecureLoopbackForTesting: z.boolean().default(false),
|
|
88
97
|
stateRoot: z.string(),
|
|
89
98
|
attachmentMaxBytes: z.number().default(DEFAULT_ATTACHMENT_MAX_BYTES),
|
|
90
|
-
mailAttachmentMaxCount: z.number().default(MAIL_ATTACHMENT_SERVICE_MAX_COUNT),
|
|
91
|
-
mailAttachmentMaxBytes: z.number().default(MAIL_ATTACHMENT_SERVICE_MAX_BYTES),
|
|
92
|
-
mailAttachmentTotalMaxBytes: z.number().default(MAIL_ATTACHMENT_SERVICE_TOTAL_MAX_BYTES),
|
|
93
99
|
imageAttachmentCacheMaxBytes: z.number().default(DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES),
|
|
94
100
|
pollIntervalMs: z.number().default(DEFAULT_POLL_INTERVAL_MS),
|
|
101
|
+
realtimeEnabled: z.boolean().default(true),
|
|
95
102
|
listenerEnabled: z.boolean().default(false),
|
|
96
103
|
listenerAllowedPeers: z.array(z.string()).default([]),
|
|
97
104
|
listenerWorkspacePath: z.string(),
|
|
98
105
|
summaryMaxInputBytes: z.number().default(DEFAULT_SUMMARY_MAX_INPUT_BYTES),
|
|
99
106
|
});
|
|
100
107
|
const FAILURE_CODES = new Set([
|
|
108
|
+
'client-version-unsupported',
|
|
101
109
|
'not-registered',
|
|
102
110
|
'signed-out',
|
|
103
111
|
'already-registered',
|
|
@@ -107,9 +115,9 @@ const FAILURE_CODES = new Set([
|
|
|
107
115
|
'handle-unavailable',
|
|
108
116
|
'not-found',
|
|
109
117
|
'forbidden',
|
|
118
|
+
'device-rejoin-required',
|
|
110
119
|
'identity-recovery-required',
|
|
111
120
|
'conflict',
|
|
112
|
-
'state-in-use',
|
|
113
121
|
'rate-limited',
|
|
114
122
|
'group-membership-required',
|
|
115
123
|
'group-identity-stale',
|
|
@@ -123,11 +131,18 @@ const FAILURE_CODES = new Set([
|
|
|
123
131
|
'network',
|
|
124
132
|
'remote',
|
|
125
133
|
]);
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const
|
|
134
|
+
const DEVICE_JOIN_APPROVAL_CONFIRMATION = 'APPROVE';
|
|
135
|
+
const DEVICE_REVOKE_CONFIRMATION = 'REVOKE';
|
|
136
|
+
const DEVICE_JOIN_CHALLENGE_TTL_SECONDS = 240;
|
|
137
|
+
const RESUMABLE_JOIN_PHASES = new Set([
|
|
138
|
+
'pending',
|
|
139
|
+
'challenge_prepared',
|
|
140
|
+
'response_prepared',
|
|
141
|
+
'response_verified',
|
|
142
|
+
'approval_prepared',
|
|
143
|
+
]);
|
|
130
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.',
|
|
131
146
|
'not-registered': 'No AWiki identity is registered for this deployment.',
|
|
132
147
|
'signed-out': 'This installation is signed out of AWiki.',
|
|
133
148
|
'already-registered': 'This deployment already has an AWiki identity.',
|
|
@@ -137,9 +152,9 @@ const FAILURE_MESSAGES = {
|
|
|
137
152
|
'handle-unavailable': 'The requested AWiki handle is unavailable.',
|
|
138
153
|
'not-found': 'The requested AWiki resource was not found.',
|
|
139
154
|
'forbidden': 'The AWiki operation is not permitted.',
|
|
155
|
+
'device-rejoin-required': 'This device credential was revoked and must join the Handle again.',
|
|
140
156
|
'identity-recovery-required': 'The local AWiki identity must be recovered before it can be used again.',
|
|
141
157
|
'conflict': 'The AWiki operation conflicts with current state.',
|
|
142
|
-
'state-in-use': 'AWiki data is already open in another client.',
|
|
143
158
|
'rate-limited': 'The AWiki service rate-limited the request.',
|
|
144
159
|
'group-membership-required': 'The active AWiki identity is not a member of this group.',
|
|
145
160
|
'group-identity-stale': 'The AWiki group identity binding is still recovering.',
|
|
@@ -157,6 +172,67 @@ class ProviderUnavailableError extends Error {
|
|
|
157
172
|
}
|
|
158
173
|
class SummaryProviderUnavailableError extends Error {
|
|
159
174
|
}
|
|
175
|
+
function candidateJoinPhase(value) {
|
|
176
|
+
if (value.remoteState === 'rejected')
|
|
177
|
+
return 'rejected';
|
|
178
|
+
if (value.remoteState === 'cancelled' || value.localPhase === 'cancelled')
|
|
179
|
+
return 'cancelled';
|
|
180
|
+
if (value.remoteState === 'expired' || value.localPhase === 'expired')
|
|
181
|
+
return 'expired';
|
|
182
|
+
if (value.localPhase === 'authorized'
|
|
183
|
+
&& value.remoteState === 'consumed'
|
|
184
|
+
&& value.completed
|
|
185
|
+
&& value.identity !== undefined)
|
|
186
|
+
return 'authorized';
|
|
187
|
+
if (value.sas !== undefined) {
|
|
188
|
+
return value.remoteState === 'response_verified' && /^\d{6}$/u.test(value.sas)
|
|
189
|
+
? 'sas-ready'
|
|
190
|
+
: undefined;
|
|
191
|
+
}
|
|
192
|
+
if (value.remoteState === 'challenge_sent'
|
|
193
|
+
|| value.remoteState === 'response_verified'
|
|
194
|
+
|| value.remoteState === 'consumed'
|
|
195
|
+
|| value.localPhase !== 'pending')
|
|
196
|
+
return 'verifying';
|
|
197
|
+
return value.remoteState === 'pending' ? 'pending' : undefined;
|
|
198
|
+
}
|
|
199
|
+
function adminJoinPhase(value) {
|
|
200
|
+
if (value.remoteState === 'rejected')
|
|
201
|
+
return 'rejected';
|
|
202
|
+
if (value.remoteState === 'cancelled' || value.localPhase === 'cancelled')
|
|
203
|
+
return 'cancelled';
|
|
204
|
+
if (value.remoteState === 'expired' || value.localPhase === 'expired')
|
|
205
|
+
return 'expired';
|
|
206
|
+
if (value.localPhase === 'authorized' && value.remoteState === 'consumed')
|
|
207
|
+
return 'authorized';
|
|
208
|
+
if (value.sas !== undefined) {
|
|
209
|
+
return value.remoteState === 'response_verified' && /^\d{6}$/u.test(value.sas)
|
|
210
|
+
? 'sas-ready'
|
|
211
|
+
: undefined;
|
|
212
|
+
}
|
|
213
|
+
if (value.remoteState === 'challenge_sent'
|
|
214
|
+
|| value.remoteState === 'response_verified'
|
|
215
|
+
|| value.remoteState === 'consumed'
|
|
216
|
+
|| value.localPhase !== 'pending')
|
|
217
|
+
return 'verifying';
|
|
218
|
+
return value.remoteState === 'pending' ? 'pending' : undefined;
|
|
219
|
+
}
|
|
220
|
+
function requestJoinPhase(value) {
|
|
221
|
+
switch (value.state) {
|
|
222
|
+
case 'pending': return 'pending';
|
|
223
|
+
case 'challenge_sent':
|
|
224
|
+
case 'response_verified': return 'verifying';
|
|
225
|
+
case 'consumed': return 'authorized';
|
|
226
|
+
case 'cancelled': return 'cancelled';
|
|
227
|
+
case 'rejected': return 'rejected';
|
|
228
|
+
case 'expired': return 'expired';
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function constantTimeSasMatches(expected, actual) {
|
|
232
|
+
if (!/^\d{6}$/u.test(expected) || !/^\d{6}$/u.test(actual))
|
|
233
|
+
return false;
|
|
234
|
+
return timingSafeEqual(Buffer.from(expected, 'ascii'), Buffer.from(actual, 'ascii'));
|
|
235
|
+
}
|
|
160
236
|
/** Validate and preserve one SDK service URL without accepting insecure remote HTTP. */
|
|
161
237
|
function serviceUrl(field, raw, allowInsecureLoopbackForTesting) {
|
|
162
238
|
let url;
|
|
@@ -175,17 +251,25 @@ function serviceUrl(field, raw, allowInsecureLoopbackForTesting) {
|
|
|
175
251
|
}
|
|
176
252
|
return raw;
|
|
177
253
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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;
|
|
263
|
+
try {
|
|
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;
|
|
182
269
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (parsed.search !== '') {
|
|
186
|
-
throw new TypeError('awiki: recovery reconciliation target must not contain a query');
|
|
270
|
+
catch {
|
|
271
|
+
return undefined;
|
|
187
272
|
}
|
|
188
|
-
return new URL('/api/identity-recovery', parsed).toString();
|
|
189
273
|
}
|
|
190
274
|
/** Validate a provider domain without inferring it from an API endpoint. */
|
|
191
275
|
function serviceDomain(raw, field = 'userServiceDomain') {
|
|
@@ -241,38 +325,15 @@ function resolveConfig(ctx, config) {
|
|
|
241
325
|
const dshHome = configuredDshHome === undefined || configuredDshHome.length === 0
|
|
242
326
|
? join(homedir(), '.dsh')
|
|
243
327
|
: configuredDshHome;
|
|
244
|
-
const profileName = configuredStateRoot === undefined || configuredStateRoot.length === 0
|
|
245
|
-
? resolveAwikiProfileName(ctx, dshHome)
|
|
246
|
-
: undefined;
|
|
247
328
|
const stateRoot = configuredStateRoot === undefined || configuredStateRoot.length === 0
|
|
248
329
|
? resolveAwikiStateRoot(ctx, dshHome)
|
|
249
330
|
: configuredStateRoot;
|
|
250
|
-
const legacySharedStateDetected = profileName !== undefined
|
|
251
|
-
&& existsSync(join(dshHome, 'awiki', 'im-core'));
|
|
252
331
|
if (!isAbsolute(stateRoot))
|
|
253
332
|
throw new TypeError('awiki: stateRoot must be an absolute path');
|
|
254
333
|
const attachmentMaxBytes = config.attachmentMaxBytes ?? DEFAULT_ATTACHMENT_MAX_BYTES;
|
|
255
334
|
if (!Number.isSafeInteger(attachmentMaxBytes) || attachmentMaxBytes < 1) {
|
|
256
335
|
throw new TypeError('awiki: attachmentMaxBytes must be a positive safe integer');
|
|
257
336
|
}
|
|
258
|
-
const mailAttachmentMaxCount = config.mailAttachmentMaxCount ?? MAIL_ATTACHMENT_SERVICE_MAX_COUNT;
|
|
259
|
-
if (!Number.isSafeInteger(mailAttachmentMaxCount)
|
|
260
|
-
|| mailAttachmentMaxCount < 0
|
|
261
|
-
|| mailAttachmentMaxCount > MAIL_ATTACHMENT_SERVICE_MAX_COUNT) {
|
|
262
|
-
throw new TypeError('awiki: mailAttachmentMaxCount must be an integer from 0 through 10');
|
|
263
|
-
}
|
|
264
|
-
const mailAttachmentMaxBytes = config.mailAttachmentMaxBytes ?? MAIL_ATTACHMENT_SERVICE_MAX_BYTES;
|
|
265
|
-
if (!Number.isSafeInteger(mailAttachmentMaxBytes)
|
|
266
|
-
|| mailAttachmentMaxBytes < 1
|
|
267
|
-
|| mailAttachmentMaxBytes > MAIL_ATTACHMENT_SERVICE_MAX_BYTES) {
|
|
268
|
-
throw new TypeError('awiki: mailAttachmentMaxBytes must be a positive safe integer no greater than 10 MiB');
|
|
269
|
-
}
|
|
270
|
-
const mailAttachmentTotalMaxBytes = config.mailAttachmentTotalMaxBytes ?? MAIL_ATTACHMENT_SERVICE_TOTAL_MAX_BYTES;
|
|
271
|
-
if (!Number.isSafeInteger(mailAttachmentTotalMaxBytes)
|
|
272
|
-
|| mailAttachmentTotalMaxBytes < mailAttachmentMaxBytes
|
|
273
|
-
|| mailAttachmentTotalMaxBytes > MAIL_ATTACHMENT_SERVICE_TOTAL_MAX_BYTES) {
|
|
274
|
-
throw new TypeError('awiki: mailAttachmentTotalMaxBytes must cover one attachment and not exceed 18 MiB');
|
|
275
|
-
}
|
|
276
337
|
const imageAttachmentCacheMaxBytes = config.imageAttachmentCacheMaxBytes ?? DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES;
|
|
277
338
|
if (!Number.isSafeInteger(imageAttachmentCacheMaxBytes)
|
|
278
339
|
|| imageAttachmentCacheMaxBytes < minimumImageAttachmentCacheMaxBytes(attachmentMaxBytes)) {
|
|
@@ -283,6 +344,10 @@ function resolveConfig(ctx, config) {
|
|
|
283
344
|
throw new TypeError('awiki: pollIntervalMs must be a safe integer from 1000 through 60000');
|
|
284
345
|
}
|
|
285
346
|
const listenerEnabled = config.listenerEnabled ?? false;
|
|
347
|
+
const realtimeEnabled = config.realtimeEnabled ?? true;
|
|
348
|
+
if (listenerEnabled && !realtimeEnabled) {
|
|
349
|
+
throw new TypeError('awiki: listenerEnabled requires realtimeEnabled');
|
|
350
|
+
}
|
|
286
351
|
const listenerWorkspacePath = config.listenerWorkspacePath?.trim() || join(dshHome, 'workspaces', 'awiki');
|
|
287
352
|
if (!isAbsolute(listenerWorkspacePath)) {
|
|
288
353
|
throw new TypeError('awiki: listenerWorkspacePath must be an absolute path');
|
|
@@ -296,10 +361,16 @@ function resolveConfig(ctx, config) {
|
|
|
296
361
|
const messageServiceUrl = serviceUrl('messageServiceUrl', config.messageServiceUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
297
362
|
const mailServiceUrl = serviceUrl('mailServiceUrl', config.mailServiceUrl ?? userServiceUrl, allowInsecureLoopbackForTesting);
|
|
298
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
|
+
}
|
|
299
370
|
return {
|
|
300
|
-
tenantDomain: serviceDomain(config.userServiceDomain ?? DEFAULT_AWIKI_DOMAIN),
|
|
301
371
|
userServiceUrl,
|
|
302
372
|
userServiceDomain: serviceDomain(config.userServiceDomain ?? DEFAULT_AWIKI_DOMAIN),
|
|
373
|
+
...legacyTenantSlot === undefined ? {} : { legacyTenantSlot },
|
|
303
374
|
messageServiceUrl,
|
|
304
375
|
mailServiceUrl,
|
|
305
376
|
messageServicePublicUrl,
|
|
@@ -308,12 +379,9 @@ function resolveConfig(ctx, config) {
|
|
|
308
379
|
allowInsecureLoopbackForTesting,
|
|
309
380
|
stateRoot,
|
|
310
381
|
attachmentMaxBytes,
|
|
311
|
-
mailAttachmentMaxCount,
|
|
312
|
-
mailAttachmentMaxBytes,
|
|
313
|
-
mailAttachmentTotalMaxBytes,
|
|
314
382
|
imageAttachmentCacheMaxBytes,
|
|
315
383
|
pollIntervalMs,
|
|
316
|
-
|
|
384
|
+
realtimeEnabled,
|
|
317
385
|
listenerEnabled,
|
|
318
386
|
listener: {
|
|
319
387
|
allowedPeers,
|
|
@@ -321,48 +389,21 @@ function resolveConfig(ctx, config) {
|
|
|
321
389
|
stateRoot,
|
|
322
390
|
},
|
|
323
391
|
summaryMaxInputBytes,
|
|
324
|
-
deriveUserServiceUrl: config.userServiceUrl === undefined,
|
|
325
|
-
deriveMessageServiceUrl: config.messageServiceUrl === undefined,
|
|
326
|
-
deriveMailServiceUrl: config.mailServiceUrl === undefined,
|
|
327
|
-
deriveMessageServicePublicUrl: config.messageServicePublicUrl === undefined,
|
|
328
|
-
deriveMessageServiceDid: config.messageServiceDid === undefined,
|
|
329
|
-
deriveAllowedAttachmentOrigins: config.allowedAttachmentOrigins === undefined
|
|
330
|
-
|| config.allowedAttachmentOrigins.length === 0,
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
/** Resolve every tenant-sensitive endpoint and state path from one selected domain. */
|
|
334
|
-
function activeTenantConfig(config, domain) {
|
|
335
|
-
const tenantOrigin = `https://${domain}`;
|
|
336
|
-
const userServiceUrl = config.deriveUserServiceUrl ? tenantOrigin : config.userServiceUrl;
|
|
337
|
-
const messageServiceUrl = config.deriveMessageServiceUrl ? tenantOrigin : config.messageServiceUrl;
|
|
338
|
-
const mailServiceUrl = config.deriveMailServiceUrl ? userServiceUrl : config.mailServiceUrl;
|
|
339
|
-
const messageServicePublicUrl = config.deriveMessageServicePublicUrl
|
|
340
|
-
? tenantOrigin
|
|
341
|
-
: config.messageServicePublicUrl;
|
|
342
|
-
const messageServiceDid = config.deriveMessageServiceDid
|
|
343
|
-
? `did:wba:${domain}`
|
|
344
|
-
: config.messageServiceDid;
|
|
345
|
-
const allowedAttachmentOrigins = config.deriveAllowedAttachmentOrigins
|
|
346
|
-
? attachmentOrigins(undefined, messageServicePublicUrl, config.allowInsecureLoopbackForTesting)
|
|
347
|
-
: config.allowedAttachmentOrigins;
|
|
348
|
-
return {
|
|
349
|
-
domain,
|
|
350
|
-
userServiceUrl,
|
|
351
|
-
userServiceDomain: domain,
|
|
352
|
-
messageServiceUrl,
|
|
353
|
-
mailServiceUrl,
|
|
354
|
-
messageServicePublicUrl,
|
|
355
|
-
messageServiceDid,
|
|
356
|
-
allowedAttachmentOrigins,
|
|
357
|
-
attachmentMaxBytes: config.attachmentMaxBytes,
|
|
358
|
-
allowInsecureLoopbackForTesting: config.allowInsecureLoopbackForTesting,
|
|
359
|
-
stateRoot: resolveAwikiTenantStateRoot(config.stateRoot, domain, config.userServiceDomain),
|
|
360
392
|
};
|
|
361
393
|
}
|
|
362
394
|
/** Return a public, fixed-message failure without retaining a thrown value. */
|
|
363
395
|
function failure(code) {
|
|
364
396
|
return { code, message: FAILURE_MESSAGES[code] };
|
|
365
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
|
+
}
|
|
366
407
|
/** Normalize SDK and provider failures without returning remote bodies, credentials, or causes. */
|
|
367
408
|
function normalizeFailure(error) {
|
|
368
409
|
if (error instanceof ProviderUnavailableError) {
|
|
@@ -428,38 +469,6 @@ function normalizeGroupDid(value) {
|
|
|
428
469
|
return undefined;
|
|
429
470
|
return value;
|
|
430
471
|
}
|
|
431
|
-
/** Rebuild the browser-safe recovery DTO instead of trusting provider object shape. */
|
|
432
|
-
function publicGroupRebindRecoverySummary(value) {
|
|
433
|
-
if (typeof value !== 'object' || value === null)
|
|
434
|
-
return undefined;
|
|
435
|
-
const candidate = value;
|
|
436
|
-
const counts = [candidate.processed, candidate.completed, candidate.pending, candidate.blocked];
|
|
437
|
-
if (counts.some(count => !Number.isSafeInteger(count) || count < 0 || count > 0xffff_ffff)) {
|
|
438
|
-
return undefined;
|
|
439
|
-
}
|
|
440
|
-
if (!Array.isArray(candidate.items) || candidate.items.length > 500)
|
|
441
|
-
return undefined;
|
|
442
|
-
const seen = new Set();
|
|
443
|
-
const items = [];
|
|
444
|
-
for (const value of candidate.items) {
|
|
445
|
-
if (typeof value !== 'object' || value === null)
|
|
446
|
-
return undefined;
|
|
447
|
-
const item = value;
|
|
448
|
-
const groupDid = normalizeGroupDid(item.groupDid);
|
|
449
|
-
if (groupDid === undefined || seen.has(groupDid) || (item.status !== 'pending' && item.status !== 'blocked')) {
|
|
450
|
-
return undefined;
|
|
451
|
-
}
|
|
452
|
-
seen.add(groupDid);
|
|
453
|
-
items.push({ groupDid, status: item.status });
|
|
454
|
-
}
|
|
455
|
-
return {
|
|
456
|
-
processed: candidate.processed,
|
|
457
|
-
completed: candidate.completed,
|
|
458
|
-
pending: candidate.pending,
|
|
459
|
-
blocked: candidate.blocked,
|
|
460
|
-
items,
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
472
|
function normalizeProfileRequest(request) {
|
|
464
473
|
if (typeof request?.displayName !== 'string' || typeof request.bio !== 'string' || !Array.isArray(request.tags))
|
|
465
474
|
return undefined;
|
|
@@ -510,6 +519,7 @@ function normalizeRecoveryPrepareRequest(request) {
|
|
|
510
519
|
return { ...operation, phone, otp };
|
|
511
520
|
}
|
|
512
521
|
const IDENTITY_ACCESS_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
522
|
+
const SERVER_INFO_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
513
523
|
/** Read one untrusted discovery response without buffering beyond the fixed Host limit. */
|
|
514
524
|
async function readBoundedResponseText(response, maxBytes) {
|
|
515
525
|
const declaredLength = response.headers.get('content-length');
|
|
@@ -716,7 +726,7 @@ function decodeAttachment(bytesBase64, maxBytes) {
|
|
|
716
726
|
const maxEncoded = Math.ceil(maxBytes / 3) * 4;
|
|
717
727
|
if (bytesBase64.length > maxEncoded)
|
|
718
728
|
return { ok: false, error: failure('attachment-too-large') };
|
|
719
|
-
if (bytesBase64.length % 4 !== 0 ||
|
|
729
|
+
if (bytesBase64.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(bytesBase64)) {
|
|
720
730
|
return { ok: false, error: failure('invalid-request') };
|
|
721
731
|
}
|
|
722
732
|
const bytes = Uint8Array.from(Buffer.from(bytesBase64, 'base64'));
|
|
@@ -731,6 +741,12 @@ let AwikiService = (() => {
|
|
|
731
741
|
let _classSuper = TypertRemoteService;
|
|
732
742
|
let _instanceExtraInitializers = [];
|
|
733
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;
|
|
734
750
|
let _getIdentity_decorators;
|
|
735
751
|
let _getSession_decorators;
|
|
736
752
|
let _logout_decorators;
|
|
@@ -738,6 +754,18 @@ let AwikiService = (() => {
|
|
|
738
754
|
let _inspectIdentityAccess_decorators;
|
|
739
755
|
let _sendRegistrationOtp_decorators;
|
|
740
756
|
let _registerIdentity_decorators;
|
|
757
|
+
let _retireDeviceIdentityForRejoin_decorators;
|
|
758
|
+
let _beginDeviceJoin_decorators;
|
|
759
|
+
let _getIdentityAccessState_decorators;
|
|
760
|
+
let _getDeviceJoinStatus_decorators;
|
|
761
|
+
let _cancelDeviceJoin_decorators;
|
|
762
|
+
let _refreshDeviceManagement_decorators;
|
|
763
|
+
let _startDeviceJoinVerification_decorators;
|
|
764
|
+
let _approveDeviceJoin_decorators;
|
|
765
|
+
let _rejectDeviceJoin_decorators;
|
|
766
|
+
let _revokeDevice_decorators;
|
|
767
|
+
let _prepareRootTransfer_decorators;
|
|
768
|
+
let _confirmRootTransfer_decorators;
|
|
741
769
|
let _updateDisplayName_decorators;
|
|
742
770
|
let _getProfile_decorators;
|
|
743
771
|
let _updateProfile_decorators;
|
|
@@ -746,16 +774,17 @@ let AwikiService = (() => {
|
|
|
746
774
|
let _activateRecovery_decorators;
|
|
747
775
|
let _getRecoveryStatus_decorators;
|
|
748
776
|
let _resumeRecovery_decorators;
|
|
777
|
+
let _enterRecoveredSession_decorators;
|
|
749
778
|
let _discardRecovery_decorators;
|
|
750
779
|
let _resolvePeer_decorators;
|
|
751
780
|
let _createGroup_decorators;
|
|
752
781
|
let _getGroup_decorators;
|
|
753
782
|
let _joinGroup_decorators;
|
|
754
783
|
let _leaveGroup_decorators;
|
|
784
|
+
let _getDisplayProfiles_decorators;
|
|
755
785
|
let _listGroupMembers_decorators;
|
|
756
786
|
let _addGroupMember_decorators;
|
|
757
787
|
let _removeGroupMember_decorators;
|
|
758
|
-
let _resumeGroupRebindRecovery_decorators;
|
|
759
788
|
let _getConversationPreferences_decorators;
|
|
760
789
|
let _updateConversationPreference_decorators;
|
|
761
790
|
let _listConversations_decorators;
|
|
@@ -771,12 +800,17 @@ let AwikiService = (() => {
|
|
|
771
800
|
let _readMail_decorators;
|
|
772
801
|
let _markMailRead_decorators;
|
|
773
802
|
let _sendMail_decorators;
|
|
774
|
-
let _downloadMailAttachment_decorators;
|
|
775
803
|
let _clearLocalData_decorators;
|
|
776
804
|
return class AwikiService extends _classSuper {
|
|
777
805
|
static {
|
|
778
806
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
779
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];
|
|
780
814
|
_getIdentity_decorators = [Remote];
|
|
781
815
|
_getSession_decorators = [Remote];
|
|
782
816
|
_logout_decorators = [Remote];
|
|
@@ -784,6 +818,18 @@ let AwikiService = (() => {
|
|
|
784
818
|
_inspectIdentityAccess_decorators = [Remote];
|
|
785
819
|
_sendRegistrationOtp_decorators = [Remote];
|
|
786
820
|
_registerIdentity_decorators = [Remote];
|
|
821
|
+
_retireDeviceIdentityForRejoin_decorators = [Remote];
|
|
822
|
+
_beginDeviceJoin_decorators = [Remote];
|
|
823
|
+
_getIdentityAccessState_decorators = [Remote];
|
|
824
|
+
_getDeviceJoinStatus_decorators = [Remote];
|
|
825
|
+
_cancelDeviceJoin_decorators = [Remote];
|
|
826
|
+
_refreshDeviceManagement_decorators = [Remote];
|
|
827
|
+
_startDeviceJoinVerification_decorators = [Remote];
|
|
828
|
+
_approveDeviceJoin_decorators = [Remote];
|
|
829
|
+
_rejectDeviceJoin_decorators = [Remote];
|
|
830
|
+
_revokeDevice_decorators = [Remote];
|
|
831
|
+
_prepareRootTransfer_decorators = [Remote];
|
|
832
|
+
_confirmRootTransfer_decorators = [Remote];
|
|
787
833
|
_updateDisplayName_decorators = [Remote];
|
|
788
834
|
_getProfile_decorators = [Remote];
|
|
789
835
|
_updateProfile_decorators = [Remote];
|
|
@@ -792,16 +838,17 @@ let AwikiService = (() => {
|
|
|
792
838
|
_activateRecovery_decorators = [Remote];
|
|
793
839
|
_getRecoveryStatus_decorators = [Remote];
|
|
794
840
|
_resumeRecovery_decorators = [Remote];
|
|
841
|
+
_enterRecoveredSession_decorators = [Remote];
|
|
795
842
|
_discardRecovery_decorators = [Remote];
|
|
796
843
|
_resolvePeer_decorators = [Remote];
|
|
797
844
|
_createGroup_decorators = [Remote];
|
|
798
845
|
_getGroup_decorators = [Remote];
|
|
799
846
|
_joinGroup_decorators = [Remote];
|
|
800
847
|
_leaveGroup_decorators = [Remote];
|
|
848
|
+
_getDisplayProfiles_decorators = [Remote];
|
|
801
849
|
_listGroupMembers_decorators = [Remote];
|
|
802
850
|
_addGroupMember_decorators = [Remote];
|
|
803
851
|
_removeGroupMember_decorators = [Remote];
|
|
804
|
-
_resumeGroupRebindRecovery_decorators = [Remote];
|
|
805
852
|
_getConversationPreferences_decorators = [Remote];
|
|
806
853
|
_updateConversationPreference_decorators = [Remote];
|
|
807
854
|
_listConversations_decorators = [Remote];
|
|
@@ -817,9 +864,14 @@ let AwikiService = (() => {
|
|
|
817
864
|
_readMail_decorators = [Remote];
|
|
818
865
|
_markMailRead_decorators = [Remote];
|
|
819
866
|
_sendMail_decorators = [Remote];
|
|
820
|
-
_downloadMailAttachment_decorators = [Remote];
|
|
821
867
|
_clearLocalData_decorators = [Remote];
|
|
822
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);
|
|
823
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);
|
|
824
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);
|
|
825
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);
|
|
@@ -827,6 +879,18 @@ let AwikiService = (() => {
|
|
|
827
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);
|
|
828
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);
|
|
829
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);
|
|
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);
|
|
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);
|
|
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);
|
|
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);
|
|
888
|
+
__esDecorate(this, null, _startDeviceJoinVerification_decorators, { kind: "method", name: "startDeviceJoinVerification", static: false, private: false, access: { has: obj => "startDeviceJoinVerification" in obj, get: obj => obj.startDeviceJoinVerification }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
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);
|
|
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);
|
|
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);
|
|
830
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);
|
|
831
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);
|
|
832
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);
|
|
@@ -835,16 +899,17 @@ let AwikiService = (() => {
|
|
|
835
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);
|
|
836
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);
|
|
837
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);
|
|
838
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);
|
|
839
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);
|
|
840
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);
|
|
841
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);
|
|
842
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);
|
|
843
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);
|
|
844
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);
|
|
845
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);
|
|
846
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);
|
|
847
|
-
__esDecorate(this, null, _resumeGroupRebindRecovery_decorators, { kind: "method", name: "resumeGroupRebindRecovery", static: false, private: false, access: { has: obj => "resumeGroupRebindRecovery" in obj, get: obj => obj.resumeGroupRebindRecovery }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
848
913
|
__esDecorate(this, null, _getConversationPreferences_decorators, { kind: "method", name: "getConversationPreferences", static: false, private: false, access: { has: obj => "getConversationPreferences" in obj, get: obj => obj.getConversationPreferences }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
849
914
|
__esDecorate(this, null, _updateConversationPreference_decorators, { kind: "method", name: "updateConversationPreference", static: false, private: false, access: { has: obj => "updateConversationPreference" in obj, get: obj => obj.updateConversationPreference }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
850
915
|
__esDecorate(this, null, _listConversations_decorators, { kind: "method", name: "listConversations", static: false, private: false, access: { has: obj => "listConversations" in obj, get: obj => obj.listConversations }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -860,28 +925,49 @@ let AwikiService = (() => {
|
|
|
860
925
|
__esDecorate(this, null, _readMail_decorators, { kind: "method", name: "readMail", static: false, private: false, access: { has: obj => "readMail" in obj, get: obj => obj.readMail }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
861
926
|
__esDecorate(this, null, _markMailRead_decorators, { kind: "method", name: "markMailRead", static: false, private: false, access: { has: obj => "markMailRead" in obj, get: obj => obj.markMailRead }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
862
927
|
__esDecorate(this, null, _sendMail_decorators, { kind: "method", name: "sendMail", static: false, private: false, access: { has: obj => "sendMail" in obj, get: obj => obj.sendMail }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
863
|
-
__esDecorate(this, null, _downloadMailAttachment_decorators, { kind: "method", name: "downloadMailAttachment", static: false, private: false, access: { has: obj => "downloadMailAttachment" in obj, get: obj => obj.downloadMailAttachment }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
864
928
|
__esDecorate(this, null, _clearLocalData_decorators, { kind: "method", name: "clearLocalData", static: false, private: false, access: { has: obj => "clearLocalData" in obj, get: obj => obj.clearLocalData }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
865
929
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
866
930
|
}
|
|
867
|
-
static inject = ['tools'
|
|
931
|
+
static inject = ['tools'];
|
|
868
932
|
static Config = Config;
|
|
869
933
|
resolved = __runInitializers(this, _instanceExtraInitializers);
|
|
870
|
-
|
|
871
|
-
|
|
934
|
+
sessionStore;
|
|
935
|
+
imageAttachmentCache;
|
|
936
|
+
conversationPreferenceStore;
|
|
872
937
|
startupUserServiceDomain;
|
|
873
938
|
settingsProvider;
|
|
939
|
+
tenantRegistry;
|
|
940
|
+
activeTenant;
|
|
941
|
+
activeClientOptions;
|
|
942
|
+
clientFactory;
|
|
874
943
|
provider;
|
|
944
|
+
runtimeGeneration = 0;
|
|
945
|
+
tenantSwitching = false;
|
|
946
|
+
tenantParticipants = new Set();
|
|
947
|
+
activeCapabilities;
|
|
948
|
+
updatePolicyStatus;
|
|
949
|
+
updatePolicyRequest;
|
|
950
|
+
updatePolicyTask;
|
|
951
|
+
updatePolicyTaskKey;
|
|
952
|
+
desktopDistribution;
|
|
875
953
|
signedOut;
|
|
876
954
|
sessionMutation = Promise.resolve();
|
|
877
955
|
sessionRevision = 0;
|
|
878
956
|
activeIdentityDid;
|
|
957
|
+
pendingDeviceJoin;
|
|
958
|
+
activeDeviceJoinSessionId;
|
|
959
|
+
requestRefs = new Map();
|
|
960
|
+
requestSessions = new Map();
|
|
961
|
+
deviceRefs = new Map();
|
|
962
|
+
deviceIds = new Map();
|
|
963
|
+
rootTransfers = new Map();
|
|
879
964
|
activeSummaryRequests = new Set();
|
|
880
965
|
summaryProvider;
|
|
881
|
-
recoveryReconciliationTarget;
|
|
882
966
|
hostContext;
|
|
883
967
|
/** Trusted same-process external HTTP authentication dispatcher. Never Remote. */
|
|
884
968
|
externalHttpAuth;
|
|
969
|
+
integrationClient;
|
|
970
|
+
mailListClient;
|
|
885
971
|
workspaceContext;
|
|
886
972
|
/**
|
|
887
973
|
* @param ctx - owning Host context.
|
|
@@ -893,39 +979,72 @@ let AwikiService = (() => {
|
|
|
893
979
|
this.resolved = resolveConfig(ctx, config);
|
|
894
980
|
this.externalHttpAuth = createAwikiExternalHttpAuth(() => this.acquireExternalHttpAuthSession());
|
|
895
981
|
this.startupUserServiceDomain = this.resolved.userServiceDomain;
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
982
|
+
ctx.inject(['settings'], (settingsCtx) => {
|
|
983
|
+
const provider = settingsCtx.settings;
|
|
984
|
+
const settingsScope = settingsCtx.settings.register(settingsNamespace(AWIKI_SETTINGS_NAMESPACE), AwikiSettingsSchema, {
|
|
985
|
+
base: { domain: this.resolved.userServiceDomain },
|
|
986
|
+
applies: 'restart',
|
|
987
|
+
validate: validateAwikiSettings,
|
|
988
|
+
});
|
|
989
|
+
this.settingsProvider = provider;
|
|
990
|
+
this.startupUserServiceDomain = settingsScope.get().domain;
|
|
991
|
+
settingsCtx.effect(() => () => {
|
|
992
|
+
if (this.settingsProvider === provider) {
|
|
993
|
+
this.settingsProvider = undefined;
|
|
994
|
+
this.startupUserServiceDomain = this.resolved.userServiceDomain;
|
|
995
|
+
}
|
|
996
|
+
}, 'awiki: release settings namespace');
|
|
901
997
|
});
|
|
902
|
-
this.settingsProvider = provider;
|
|
903
|
-
this.startupUserServiceDomain = settingsScope.get().domain;
|
|
904
|
-
ctx.effect(() => () => {
|
|
905
|
-
if (this.settingsProvider === provider) {
|
|
906
|
-
this.settingsProvider = undefined;
|
|
907
|
-
this.startupUserServiceDomain = this.resolved.userServiceDomain;
|
|
908
|
-
}
|
|
909
|
-
}, 'awiki: release settings namespace');
|
|
910
998
|
ctx.inject(['connection'], (connectionCtx) => {
|
|
911
|
-
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' });
|
|
912
1010
|
});
|
|
913
1011
|
ctx.inject(['workspaceRegistry'], (workspaceCtx) => {
|
|
914
1012
|
this.workspaceContext = workspaceCtx;
|
|
915
1013
|
const provider = this.provider;
|
|
916
1014
|
if (provider !== undefined)
|
|
917
|
-
|
|
1015
|
+
this.ensureAgentConsumer(provider);
|
|
918
1016
|
workspaceCtx.effect(() => async () => {
|
|
919
1017
|
if (this.workspaceContext !== workspaceCtx)
|
|
920
1018
|
return;
|
|
921
1019
|
this.workspaceContext = undefined;
|
|
922
1020
|
const current = this.provider;
|
|
923
1021
|
if (current !== undefined)
|
|
924
|
-
await this.
|
|
1022
|
+
await this.stopAgentConsumer(current);
|
|
925
1023
|
}, 'awiki: release Workspace listener composition');
|
|
926
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
|
+
});
|
|
927
1045
|
registerAwikiTools(ctx, this);
|
|
928
1046
|
ctx.effect(() => async () => {
|
|
1047
|
+
this.updatePolicyRequest?.abort();
|
|
929
1048
|
const provider = this.provider;
|
|
930
1049
|
if (provider !== undefined)
|
|
931
1050
|
await this.disposeProvider(provider);
|
|
@@ -942,36 +1061,351 @@ let AwikiService = (() => {
|
|
|
942
1061
|
* @returns asynchronous disposer for the exact registered client.
|
|
943
1062
|
*/
|
|
944
1063
|
registerClientFactory(factory) {
|
|
945
|
-
if (this.provider !== undefined)
|
|
1064
|
+
if (this.clientFactory !== undefined || this.provider !== undefined) {
|
|
946
1065
|
throw new Error('awiki: a client provider is already registered');
|
|
947
|
-
|
|
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],
|
|
1121
|
+
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
1122
|
+
allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
|
|
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);
|
|
948
1160
|
const provider = {
|
|
949
1161
|
client,
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1162
|
+
realtimeGeneration: 0,
|
|
1163
|
+
agentConsumerGeneration: 0,
|
|
1164
|
+
localDeviceJoinRequestCountAfterSync: 0,
|
|
953
1165
|
};
|
|
1166
|
+
this.bindTenantState(tenant, options);
|
|
954
1167
|
this.provider = provider;
|
|
955
|
-
void this.
|
|
956
|
-
|
|
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
|
+
});
|
|
957
1186
|
}
|
|
958
|
-
/**
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
const
|
|
964
|
-
|
|
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;
|
|
965
1206
|
});
|
|
966
|
-
this.
|
|
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);
|
|
967
1281
|
let active = true;
|
|
968
1282
|
return () => {
|
|
969
1283
|
if (!active)
|
|
970
1284
|
return;
|
|
971
1285
|
active = false;
|
|
972
|
-
|
|
973
|
-
|
|
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
|
+
}
|
|
974
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
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
/** Safe same-process diagnostics for focused E2E. Never exposed through Typert Remote. */
|
|
1394
|
+
getRealtimeDiagnostics() {
|
|
1395
|
+
const provider = this.provider;
|
|
1396
|
+
const realtime = provider?.realtimeSupervisor?.diagnostics() ?? {
|
|
1397
|
+
connected: false,
|
|
1398
|
+
activeSessionCount: 0,
|
|
1399
|
+
startCount: 0,
|
|
1400
|
+
stopCount: 0,
|
|
1401
|
+
maxActiveSessionCount: 0,
|
|
1402
|
+
generation: 0,
|
|
1403
|
+
retryCount: 0,
|
|
1404
|
+
lifecyclePhase: 'idle',
|
|
1405
|
+
};
|
|
1406
|
+
return {
|
|
1407
|
+
...realtime,
|
|
1408
|
+
localDeviceJoinRequestCountAfterSync: provider?.localDeviceJoinRequestCountAfterSync ?? 0,
|
|
975
1409
|
};
|
|
976
1410
|
}
|
|
977
1411
|
/** Register one replaceable conversation-summary provider for this deployment. */
|
|
@@ -992,58 +1426,145 @@ let AwikiService = (() => {
|
|
|
992
1426
|
* Read settings needed by the browser presentation.
|
|
993
1427
|
* @returns Browser-safe polling configuration without SDK endpoints or state paths.
|
|
994
1428
|
*/
|
|
995
|
-
getConfig() {
|
|
996
|
-
|
|
1429
|
+
async getConfig() {
|
|
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 {
|
|
997
1438
|
ok: true,
|
|
998
1439
|
value: {
|
|
999
|
-
tenantDomain: this.activeTenant().domain,
|
|
1000
1440
|
pollIntervalMs: this.resolved.pollIntervalMs,
|
|
1001
1441
|
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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 },
|
|
1008
1452
|
},
|
|
1009
1453
|
},
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
activeTenant() {
|
|
1013
|
-
const cached = this.tenantConfigCache;
|
|
1014
|
-
if (cached !== undefined)
|
|
1015
|
-
return cached;
|
|
1016
|
-
const value = Object.freeze(activeTenantConfig(this.resolved, this.startupUserServiceDomain));
|
|
1017
|
-
this.tenantConfigCache = value;
|
|
1018
|
-
return value;
|
|
1019
|
-
}
|
|
1020
|
-
activeTenantState() {
|
|
1021
|
-
const cached = this.tenantStateCache;
|
|
1022
|
-
if (cached !== undefined)
|
|
1023
|
-
return cached;
|
|
1024
|
-
const tenant = this.activeTenant();
|
|
1025
|
-
const value = Object.freeze({
|
|
1026
|
-
domain: tenant.domain,
|
|
1027
|
-
stateRoot: tenant.stateRoot,
|
|
1028
|
-
sessionStore: new AwikiSessionStore(tenant.stateRoot),
|
|
1029
|
-
imageAttachmentCache: new AwikiImageAttachmentCache(tenant.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes),
|
|
1030
|
-
sentMailStore: new AwikiSentMailStore(tenant.stateRoot),
|
|
1031
|
-
conversationPreferenceStore: new AwikiConversationPreferenceStore(tenant.stateRoot),
|
|
1032
|
-
});
|
|
1033
|
-
this.tenantStateCache = value;
|
|
1034
|
-
return value;
|
|
1035
|
-
}
|
|
1036
|
-
get sessionStore() {
|
|
1037
|
-
return this.activeTenantState().sessionStore;
|
|
1038
|
-
}
|
|
1039
|
-
get imageAttachmentCache() {
|
|
1040
|
-
return this.activeTenantState().imageAttachmentCache;
|
|
1454
|
+
};
|
|
1041
1455
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
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
|
+
};
|
|
1489
|
+
const abort = new AbortController();
|
|
1490
|
+
const timeout = setTimeout(() => { abort.abort(); }, timeoutMs);
|
|
1491
|
+
try {
|
|
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, {
|
|
1495
|
+
method: 'GET',
|
|
1496
|
+
headers: { accept: 'application/json', 'cache-control': 'no-store' },
|
|
1497
|
+
cache: 'no-store',
|
|
1498
|
+
redirect: 'error',
|
|
1499
|
+
signal: abort.signal,
|
|
1500
|
+
});
|
|
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);
|
|
1528
|
+
}
|
|
1529
|
+
finally {
|
|
1530
|
+
clearTimeout(timeout);
|
|
1531
|
+
}
|
|
1044
1532
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
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());
|
|
1047
1568
|
}
|
|
1048
1569
|
/**
|
|
1049
1570
|
* Read the deployment's identity status.
|
|
@@ -1065,6 +1586,9 @@ let AwikiService = (() => {
|
|
|
1065
1586
|
if (!identity.ok)
|
|
1066
1587
|
return identity;
|
|
1067
1588
|
this.activeIdentityDid = identity.value?.did;
|
|
1589
|
+
const provider = this.provider;
|
|
1590
|
+
if (identity.value !== null && provider !== undefined)
|
|
1591
|
+
this.ensureProviderRuntime(provider);
|
|
1068
1592
|
return identity.value === null
|
|
1069
1593
|
? { ok: true, value: { status: 'unregistered' } }
|
|
1070
1594
|
: { ok: true, value: { status: 'active', identity: identity.value } };
|
|
@@ -1074,6 +1598,8 @@ let AwikiService = (() => {
|
|
|
1074
1598
|
if (request?.confirmation !== AWIKI_LOGOUT_CONFIRMATION) {
|
|
1075
1599
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1076
1600
|
}
|
|
1601
|
+
// Fence pending post-Recovery work before the serialized sign-out reaches the head of the queue.
|
|
1602
|
+
this.sessionRevision += 1;
|
|
1077
1603
|
return this.mutateSession(async () => {
|
|
1078
1604
|
if (await this.isSignedOut())
|
|
1079
1605
|
return { ok: true, value: { status: 'signed-out' } };
|
|
@@ -1091,7 +1617,7 @@ let AwikiService = (() => {
|
|
|
1091
1617
|
this.publishSession(session);
|
|
1092
1618
|
const provider = this.provider;
|
|
1093
1619
|
if (provider !== undefined)
|
|
1094
|
-
await this.
|
|
1620
|
+
await this.stopProviderRuntime(provider);
|
|
1095
1621
|
return { ok: true, value: session };
|
|
1096
1622
|
}
|
|
1097
1623
|
catch {
|
|
@@ -1116,7 +1642,7 @@ let AwikiService = (() => {
|
|
|
1116
1642
|
this.publishSession(session);
|
|
1117
1643
|
const provider = this.provider;
|
|
1118
1644
|
if (provider !== undefined)
|
|
1119
|
-
|
|
1645
|
+
this.ensureProviderRuntime(provider);
|
|
1120
1646
|
return { ok: true, value: session };
|
|
1121
1647
|
}
|
|
1122
1648
|
catch {
|
|
@@ -1126,10 +1652,13 @@ let AwikiService = (() => {
|
|
|
1126
1652
|
}
|
|
1127
1653
|
/** Classify one configured-domain Handle before selecting the registration or recovery OTP purpose. */
|
|
1128
1654
|
async inspectIdentityAccess(request) {
|
|
1129
|
-
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);
|
|
1130
1659
|
if (target === undefined)
|
|
1131
1660
|
return { ok: false, error: failure('invalid-request') };
|
|
1132
|
-
const endpoint = new URL(`/.well-known/handle/${encodeURIComponent(target.localPart)}`,
|
|
1661
|
+
const endpoint = new URL(`/.well-known/handle/${encodeURIComponent(target.localPart)}`, options.userServiceUrl);
|
|
1133
1662
|
const abort = new AbortController();
|
|
1134
1663
|
const timeout = setTimeout(() => { abort.abort(); }, 10_000);
|
|
1135
1664
|
let response;
|
|
@@ -1174,8 +1703,14 @@ let AwikiService = (() => {
|
|
|
1174
1703
|
return { ok: false, error: failure('remote') };
|
|
1175
1704
|
}
|
|
1176
1705
|
}
|
|
1177
|
-
sendRegistrationOtp(request) {
|
|
1178
|
-
|
|
1706
|
+
async sendRegistrationOtp(request) {
|
|
1707
|
+
this.pendingDeviceJoin = undefined;
|
|
1708
|
+
return this.run(async (client) => {
|
|
1709
|
+
if (await this.selectDeviceJoinSession(client) !== null) {
|
|
1710
|
+
throw Object.assign(new Error('join already exists'), { name: 'AwikiSdkError', code: 'conflict' });
|
|
1711
|
+
}
|
|
1712
|
+
return client.sendRegistrationOtp(request);
|
|
1713
|
+
});
|
|
1179
1714
|
}
|
|
1180
1715
|
/**
|
|
1181
1716
|
* Register and persist the deployment's only AWiki identity.
|
|
@@ -1183,10 +1718,306 @@ let AwikiService = (() => {
|
|
|
1183
1718
|
* @returns The new public identity or a closed failure.
|
|
1184
1719
|
*/
|
|
1185
1720
|
async registerIdentity(request) {
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
await this.
|
|
1189
|
-
|
|
1721
|
+
this.pendingDeviceJoin = undefined;
|
|
1722
|
+
const result = await this.run(async (client) => {
|
|
1723
|
+
if (await this.selectDeviceJoinSession(client) !== null) {
|
|
1724
|
+
throw Object.assign(new Error('join already exists'), { name: 'AwikiSdkError', code: 'conflict' });
|
|
1725
|
+
}
|
|
1726
|
+
return client.registerIdentity(request);
|
|
1727
|
+
});
|
|
1728
|
+
if (!result.ok)
|
|
1729
|
+
return result;
|
|
1730
|
+
if (result.value.status === 'registered') {
|
|
1731
|
+
await this.activateRegisteredIdentity(result.value.identity);
|
|
1732
|
+
return { ok: true, value: { status: 'registered', identity: result.value.identity } };
|
|
1733
|
+
}
|
|
1734
|
+
this.pendingDeviceJoin = {
|
|
1735
|
+
continuationId: result.value.continuationId,
|
|
1736
|
+
fullHandle: result.value.fullHandle,
|
|
1737
|
+
mode: result.value.mode,
|
|
1738
|
+
requiresUserPresence: result.value.requiresUserPresence,
|
|
1739
|
+
};
|
|
1740
|
+
return {
|
|
1741
|
+
ok: true,
|
|
1742
|
+
value: {
|
|
1743
|
+
status: 'join-required',
|
|
1744
|
+
fullHandle: result.value.fullHandle,
|
|
1745
|
+
mode: result.value.mode,
|
|
1746
|
+
requiresUserPresence: result.value.requiresUserPresence,
|
|
1747
|
+
},
|
|
1748
|
+
};
|
|
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
|
+
}
|
|
1782
|
+
/** Consume the exact in-memory continuation; ordinary Join never claims rebind user presence. */
|
|
1783
|
+
async beginDeviceJoin() {
|
|
1784
|
+
const continuation = this.pendingDeviceJoin;
|
|
1785
|
+
if (continuation === undefined)
|
|
1786
|
+
return { ok: false, error: failure('conflict') };
|
|
1787
|
+
if ((continuation.mode === 'ordinary' && continuation.requiresUserPresence)
|
|
1788
|
+
|| (continuation.mode === 'handle-recovery-rebind' && !continuation.requiresUserPresence)) {
|
|
1789
|
+
return { ok: false, error: failure('forbidden') };
|
|
1790
|
+
}
|
|
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
|
+
});
|
|
1809
|
+
if (!result.ok)
|
|
1810
|
+
return result;
|
|
1811
|
+
this.activeDeviceJoinSessionId = result.value.joinSessionId;
|
|
1812
|
+
return this.applyCandidateJoinProgress(result.value);
|
|
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
|
+
}
|
|
1832
|
+
/** Restore from Core local_sessions and advance only the exact resumable Join. */
|
|
1833
|
+
async getDeviceJoinStatus() {
|
|
1834
|
+
const result = await this.run(async (client) => {
|
|
1835
|
+
const joinSessionId = await this.selectDeviceJoinSession(client);
|
|
1836
|
+
return joinSessionId === null ? null : client.getDeviceJoinStatus(joinSessionId);
|
|
1837
|
+
});
|
|
1838
|
+
if (!result.ok)
|
|
1839
|
+
return result;
|
|
1840
|
+
if (result.value === null)
|
|
1841
|
+
return { ok: true, value: null };
|
|
1842
|
+
this.activeDeviceJoinSessionId = result.value.joinSessionId;
|
|
1843
|
+
return this.applyCandidateJoinProgress(result.value);
|
|
1844
|
+
}
|
|
1845
|
+
async cancelDeviceJoin() {
|
|
1846
|
+
const result = await this.run(async (client) => {
|
|
1847
|
+
const joinSessionId = await this.selectDeviceJoinSession(client);
|
|
1848
|
+
if (joinSessionId === null)
|
|
1849
|
+
return null;
|
|
1850
|
+
return client.cancelDeviceJoin(joinSessionId);
|
|
1851
|
+
});
|
|
1852
|
+
if (!result.ok)
|
|
1853
|
+
return result;
|
|
1854
|
+
this.pendingDeviceJoin = undefined;
|
|
1855
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
1856
|
+
return { ok: true, value: { completed: true } };
|
|
1857
|
+
}
|
|
1858
|
+
/** Reliable-sync and project only Host-opaque device/request references. */
|
|
1859
|
+
refreshDeviceManagement() {
|
|
1860
|
+
return this.run(client => this.deviceManagementSnapshot(client));
|
|
1861
|
+
}
|
|
1862
|
+
async startDeviceJoinVerification(request) {
|
|
1863
|
+
const joinSessionId = this.requestSessions.get(request?.requestRef);
|
|
1864
|
+
if (joinSessionId === undefined)
|
|
1865
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1866
|
+
const result = await this.run(async (client) => {
|
|
1867
|
+
await this.requireDeviceManager(client);
|
|
1868
|
+
await client.syncDeviceManagement();
|
|
1869
|
+
let notice = (await client.listLocalDeviceJoinRequests())
|
|
1870
|
+
.find(value => value.joinSessionId === joinSessionId);
|
|
1871
|
+
if (notice === undefined) {
|
|
1872
|
+
throw Object.assign(new Error('request not found'), { name: 'AwikiSdkError', code: 'not-found' });
|
|
1873
|
+
}
|
|
1874
|
+
const localProgress = await this.localAdminJoinProgress(client, notice);
|
|
1875
|
+
if (localProgress !== undefined)
|
|
1876
|
+
return localProgress;
|
|
1877
|
+
if (!notice.canStartVerification) {
|
|
1878
|
+
throw Object.assign(new Error('request unavailable'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1879
|
+
}
|
|
1880
|
+
const operationId = `dsh-device-verify-${createHash('sha256').update(joinSessionId).digest('hex').slice(0, 32)}`;
|
|
1881
|
+
let started;
|
|
1882
|
+
try {
|
|
1883
|
+
started = await client.startDeviceJoinVerification({
|
|
1884
|
+
joinSessionId,
|
|
1885
|
+
operationId,
|
|
1886
|
+
challengeTtlSeconds: DEVICE_JOIN_CHALLENGE_TTL_SECONDS,
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
catch (error) {
|
|
1890
|
+
await client.syncDeviceManagement();
|
|
1891
|
+
notice = (await client.listLocalDeviceJoinRequests())
|
|
1892
|
+
.find(value => value.joinSessionId === joinSessionId);
|
|
1893
|
+
const recovered = notice === undefined ? undefined : await this.localAdminJoinProgress(client, notice);
|
|
1894
|
+
if (recovered !== undefined)
|
|
1895
|
+
return recovered;
|
|
1896
|
+
throw error;
|
|
1897
|
+
}
|
|
1898
|
+
await client.syncDeviceManagement();
|
|
1899
|
+
notice = (await client.listLocalDeviceJoinRequests())
|
|
1900
|
+
.find(value => value.joinSessionId === joinSessionId);
|
|
1901
|
+
if (notice === undefined)
|
|
1902
|
+
return started;
|
|
1903
|
+
return (await this.localAdminJoinProgress(client, notice)) ?? started;
|
|
1904
|
+
});
|
|
1905
|
+
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
1906
|
+
}
|
|
1907
|
+
async approveDeviceJoin(request) {
|
|
1908
|
+
if (request?.confirmation !== DEVICE_JOIN_APPROVAL_CONFIRMATION) {
|
|
1909
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1910
|
+
}
|
|
1911
|
+
const joinSessionId = this.requestSessions.get(request.requestRef);
|
|
1912
|
+
if (joinSessionId === undefined || !/^\d{6}$/u.test(request.enteredSas)) {
|
|
1913
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1914
|
+
}
|
|
1915
|
+
const result = await this.run(async (client) => {
|
|
1916
|
+
await this.requireDeviceManager(client);
|
|
1917
|
+
const progress = await client.getLocalDeviceJoinVerificationProgress(joinSessionId);
|
|
1918
|
+
if (progress.sas === undefined || !constantTimeSasMatches(progress.sas, request.enteredSas)) {
|
|
1919
|
+
throw Object.assign(new Error('sas mismatch'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1920
|
+
}
|
|
1921
|
+
const prompt = await client.prepareDeviceJoinApproval(joinSessionId);
|
|
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
|
+
}
|
|
1942
|
+
});
|
|
1943
|
+
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
1944
|
+
}
|
|
1945
|
+
async rejectDeviceJoin(request) {
|
|
1946
|
+
const joinSessionId = this.requestSessions.get(request?.requestRef);
|
|
1947
|
+
if (joinSessionId === undefined
|
|
1948
|
+
|| (request.reason !== 'user_rejected' && request.reason !== 'sas_mismatch')) {
|
|
1949
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1950
|
+
}
|
|
1951
|
+
const result = await this.run(async (client) => {
|
|
1952
|
+
await this.requireDeviceManager(client);
|
|
1953
|
+
return client.rejectDeviceJoin(joinSessionId, request.reason);
|
|
1954
|
+
});
|
|
1955
|
+
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
1956
|
+
}
|
|
1957
|
+
async revokeDevice(request) {
|
|
1958
|
+
if (request?.confirmation !== DEVICE_REVOKE_CONFIRMATION) {
|
|
1959
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1960
|
+
}
|
|
1961
|
+
const deviceId = this.deviceIds.get(request.deviceRef);
|
|
1962
|
+
if (deviceId === undefined)
|
|
1963
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1964
|
+
return this.run(async (client) => {
|
|
1965
|
+
await this.requireDeviceManager(client);
|
|
1966
|
+
const devices = await client.getDeviceRegistry();
|
|
1967
|
+
const target = devices.find(device => device.deviceId === deviceId);
|
|
1968
|
+
if (target === undefined || target.isCurrent) {
|
|
1969
|
+
throw Object.assign(new Error('device unavailable'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1970
|
+
}
|
|
1971
|
+
await client.revokeDevice(deviceId);
|
|
1972
|
+
return this.deviceManagementSnapshot(client);
|
|
1973
|
+
});
|
|
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
|
+
});
|
|
1190
2021
|
}
|
|
1191
2022
|
/**
|
|
1192
2023
|
* Update the deployment identity's public WNS display name.
|
|
@@ -1212,10 +2043,16 @@ let AwikiService = (() => {
|
|
|
1212
2043
|
}
|
|
1213
2044
|
/** Start durable phone recovery for one existing full Handle. */
|
|
1214
2045
|
sendRecoveryOtp(request) {
|
|
2046
|
+
this.pendingDeviceJoin = undefined;
|
|
1215
2047
|
const normalized = normalizeRecoveryOtpRequest(request);
|
|
1216
2048
|
if (normalized === undefined)
|
|
1217
2049
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1218
|
-
return this.run(client =>
|
|
2050
|
+
return this.run(async (client) => {
|
|
2051
|
+
if (await this.selectDeviceJoinSession(client) !== null) {
|
|
2052
|
+
throw Object.assign(new Error('join already exists'), { name: 'AwikiSdkError', code: 'conflict' });
|
|
2053
|
+
}
|
|
2054
|
+
return client.sendRecoveryOtp(normalized);
|
|
2055
|
+
}, { allowSignedOut: true });
|
|
1219
2056
|
}
|
|
1220
2057
|
/** Verify a recovery OTP and freeze its exact intent before the remote commit. */
|
|
1221
2058
|
prepareRecovery(request) {
|
|
@@ -1229,33 +2066,35 @@ let AwikiService = (() => {
|
|
|
1229
2066
|
const normalized = normalizeRecoveryOperation(request);
|
|
1230
2067
|
if (normalized === undefined)
|
|
1231
2068
|
return { ok: false, error: failure('invalid-request') };
|
|
1232
|
-
|
|
1233
|
-
if (result.ok && !await this.applyRecoveredSession(result.value)) {
|
|
1234
|
-
return { ok: false, error: failure('remote') };
|
|
1235
|
-
}
|
|
1236
|
-
return result;
|
|
2069
|
+
return this.run(client => client.activateRecovery(normalized), { allowSignedOut: true });
|
|
1237
2070
|
}
|
|
1238
2071
|
/** Read durable recovery state before deciding whether a retry is valid. */
|
|
1239
2072
|
async getRecoveryStatus(request) {
|
|
1240
2073
|
const normalized = normalizeRecoveryOperation(request);
|
|
1241
2074
|
if (normalized === undefined)
|
|
1242
2075
|
return { ok: false, error: failure('invalid-request') };
|
|
1243
|
-
|
|
1244
|
-
if (result.ok && !await this.applyRecoveredSession(result.value)) {
|
|
1245
|
-
return { ok: false, error: failure('remote') };
|
|
1246
|
-
}
|
|
1247
|
-
return result;
|
|
2076
|
+
return this.run(client => client.getRecoveryStatus(normalized), { allowSignedOut: true });
|
|
1248
2077
|
}
|
|
1249
2078
|
/** Resume only the exact Core-owned operation selected by the browser. */
|
|
1250
2079
|
async resumeRecovery(request) {
|
|
1251
2080
|
const normalized = normalizeRecoveryOperation(request);
|
|
1252
2081
|
if (normalized === undefined)
|
|
1253
2082
|
return { ok: false, error: failure('invalid-request') };
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
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') };
|
|
1257
2096
|
}
|
|
1258
|
-
return result;
|
|
2097
|
+
return this.applyRecoveredSession(result.value, requestGeneration);
|
|
1259
2098
|
}
|
|
1260
2099
|
/** Discard only a pre-attempt operation; Core rejects post-attempt deletion. */
|
|
1261
2100
|
async discardRecovery(request) {
|
|
@@ -1322,6 +2161,12 @@ let AwikiService = (() => {
|
|
|
1322
2161
|
return result.ok ? { ok: true, value: { completed: true } } : result;
|
|
1323
2162
|
}
|
|
1324
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
|
+
}
|
|
1325
2170
|
listGroupMembers(request) {
|
|
1326
2171
|
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
1327
2172
|
if (groupDid === undefined
|
|
@@ -1353,16 +2198,6 @@ let AwikiService = (() => {
|
|
|
1353
2198
|
}
|
|
1354
2199
|
return this.run(client => client.removeGroupMember(groupDid, member));
|
|
1355
2200
|
}
|
|
1356
|
-
/** Resume durable group membership convergence after account sync restores old groups. */
|
|
1357
|
-
async resumeGroupRebindRecovery() {
|
|
1358
|
-
const result = await this.run(client => client.resumeGroupRebindRecovery());
|
|
1359
|
-
if (!result.ok)
|
|
1360
|
-
return result;
|
|
1361
|
-
const summary = publicGroupRebindRecoverySummary(result.value);
|
|
1362
|
-
return summary === undefined
|
|
1363
|
-
? { ok: false, error: failure('remote') }
|
|
1364
|
-
: { ok: true, value: summary };
|
|
1365
|
-
}
|
|
1366
2201
|
/** Read presentation-only roster preferences for the active identity. */
|
|
1367
2202
|
getConversationPreferences() {
|
|
1368
2203
|
return this.run(async (client) => this.conversationPreferenceStore.get(await this.ownerDid(client)));
|
|
@@ -1583,7 +2418,7 @@ let AwikiService = (() => {
|
|
|
1583
2418
|
}
|
|
1584
2419
|
/** Return the deployment identity's public mailbox state. */
|
|
1585
2420
|
getMailAccount() {
|
|
1586
|
-
return this.run(client => client.getMailAccount()
|
|
2421
|
+
return this.run(client => client.getMailAccount());
|
|
1587
2422
|
}
|
|
1588
2423
|
/** List one bounded mailbox page on explicit browser/tool demand. */
|
|
1589
2424
|
async listMailInbox(request) {
|
|
@@ -1594,11 +2429,15 @@ let AwikiService = (() => {
|
|
|
1594
2429
|
catch {
|
|
1595
2430
|
return { ok: false, error: failure('invalid-request') };
|
|
1596
2431
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
2432
|
+
const requestGeneration = this.sessionRevision;
|
|
2433
|
+
return this.run(async (client) => {
|
|
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;
|
|
2440
|
+
});
|
|
1602
2441
|
}
|
|
1603
2442
|
/** Read one bounded plain-text mail message. */
|
|
1604
2443
|
async readMail(request) {
|
|
@@ -1609,116 +2448,22 @@ let AwikiService = (() => {
|
|
|
1609
2448
|
catch {
|
|
1610
2449
|
return { ok: false, error: failure('invalid-request') };
|
|
1611
2450
|
}
|
|
1612
|
-
return this.run(
|
|
1613
|
-
if (!isLocalSentMailId(normalized.messageId))
|
|
1614
|
-
return client.readMail(normalized);
|
|
1615
|
-
const local = await this.sentMailStore.read(ownerDid, normalized.messageId);
|
|
1616
|
-
if (local === undefined) {
|
|
1617
|
-
throw Object.assign(new Error('sent mail not found'), { name: 'AwikiSdkError', code: 'not-found' });
|
|
1618
|
-
}
|
|
1619
|
-
return local;
|
|
1620
|
-
}, { bindMailOwner: true });
|
|
2451
|
+
return this.run(client => client.readMail(normalized));
|
|
1621
2452
|
}
|
|
1622
2453
|
/** Mark explicitly selected mail messages read. Browser callers require an explicit click. */
|
|
1623
2454
|
markMailRead(request) {
|
|
1624
|
-
return this.runValidatedMail(() => mailMarkReadRequest(request), (client, normalized) => client.markMailRead(normalized)
|
|
2455
|
+
return this.runValidatedMail(() => mailMarkReadRequest(request), (client, normalized) => client.markMailRead(normalized));
|
|
1625
2456
|
}
|
|
1626
|
-
/** Send one mail once. Browser callers require an explicit confirmation. */
|
|
2457
|
+
/** Send one plain-text mail once. Browser callers require an explicit confirmation. */
|
|
1627
2458
|
async sendMail(request) {
|
|
1628
2459
|
let normalized;
|
|
1629
2460
|
try {
|
|
1630
|
-
normalized = mailSendRequest(request
|
|
1631
|
-
maxCount: this.resolved.mailAttachmentMaxCount,
|
|
1632
|
-
maxBytes: this.resolved.mailAttachmentMaxBytes,
|
|
1633
|
-
totalMaxBytes: this.resolved.mailAttachmentTotalMaxBytes,
|
|
1634
|
-
});
|
|
2461
|
+
normalized = mailSendRequest(request);
|
|
1635
2462
|
}
|
|
1636
2463
|
catch {
|
|
1637
2464
|
return { ok: false, error: failure('invalid-request') };
|
|
1638
2465
|
}
|
|
1639
|
-
return this.run(
|
|
1640
|
-
const result = await client.sendMail({
|
|
1641
|
-
to: [...normalized.to],
|
|
1642
|
-
cc: [...normalized.cc],
|
|
1643
|
-
subject: normalized.subject,
|
|
1644
|
-
bodyText: normalized.bodyText,
|
|
1645
|
-
...normalized.attachments.length === 0 ? {} : {
|
|
1646
|
-
attachments: normalized.attachments.map(attachment => ({
|
|
1647
|
-
fileName: attachment.fileName,
|
|
1648
|
-
contentType: attachment.contentType,
|
|
1649
|
-
bytes: attachment.bytes,
|
|
1650
|
-
})),
|
|
1651
|
-
},
|
|
1652
|
-
});
|
|
1653
|
-
assertActive();
|
|
1654
|
-
if (!result.accepted)
|
|
1655
|
-
return result;
|
|
1656
|
-
const settled = normalized.attachments.length > 0
|
|
1657
|
-
&& result.messageId === undefined
|
|
1658
|
-
&& result.warnings.length < 100
|
|
1659
|
-
? { ...result, warnings: [...result.warnings, 'Sent attachment download is unavailable because the service returned no message id.'] }
|
|
1660
|
-
: result;
|
|
1661
|
-
try {
|
|
1662
|
-
const account = await client.getMailAccount().catch(() => undefined);
|
|
1663
|
-
assertActive();
|
|
1664
|
-
await this.sentMailStore.append(ownerDid, normalized, settled, account);
|
|
1665
|
-
assertActive();
|
|
1666
|
-
return settled;
|
|
1667
|
-
}
|
|
1668
|
-
catch {
|
|
1669
|
-
assertActive();
|
|
1670
|
-
return settled.warnings.length >= 100
|
|
1671
|
-
? settled
|
|
1672
|
-
: { ...settled, warnings: [...settled.warnings, 'Sent history could not be saved locally.'] };
|
|
1673
|
-
}
|
|
1674
|
-
}, { bindMailOwner: true });
|
|
1675
|
-
}
|
|
1676
|
-
/** Download one mail attachment only after an explicit browser action. */
|
|
1677
|
-
async downloadMailAttachment(request) {
|
|
1678
|
-
let normalized;
|
|
1679
|
-
try {
|
|
1680
|
-
normalized = mailAttachmentDownloadRequest(request);
|
|
1681
|
-
}
|
|
1682
|
-
catch {
|
|
1683
|
-
return { ok: false, error: failure('invalid-request') };
|
|
1684
|
-
}
|
|
1685
|
-
return this.run(async (client, _provider, ownerDid, assertActive) => {
|
|
1686
|
-
let serviceMessageId = normalized.localMessageId;
|
|
1687
|
-
if (isLocalSentMailId(serviceMessageId)) {
|
|
1688
|
-
const resolved = await this.sentMailStore.resolveAttachment(ownerDid, serviceMessageId, normalized.attachmentIndex);
|
|
1689
|
-
if (resolved === undefined) {
|
|
1690
|
-
throw Object.assign(new Error('sent mail attachment is unavailable'), {
|
|
1691
|
-
name: 'AwikiSdkError', code: 'not-found',
|
|
1692
|
-
});
|
|
1693
|
-
}
|
|
1694
|
-
serviceMessageId = resolved;
|
|
1695
|
-
}
|
|
1696
|
-
const value = await client.downloadMailAttachment({
|
|
1697
|
-
messageId: serviceMessageId,
|
|
1698
|
-
attachmentIndex: normalized.attachmentIndex,
|
|
1699
|
-
});
|
|
1700
|
-
assertActive();
|
|
1701
|
-
if (!Number.isSafeInteger(value.sizeBytes) || value.sizeBytes < 0)
|
|
1702
|
-
throw new TypeError('invalid mail attachment size');
|
|
1703
|
-
if (value.sizeBytes > this.resolved.mailAttachmentMaxBytes) {
|
|
1704
|
-
throw Object.assign(new Error('mail attachment exceeds Host limit'), {
|
|
1705
|
-
name: 'AwikiSdkError', code: 'attachment-too-large',
|
|
1706
|
-
});
|
|
1707
|
-
}
|
|
1708
|
-
if (!(value.bytes instanceof Uint8Array) || value.bytes.byteLength !== value.sizeBytes) {
|
|
1709
|
-
throw new TypeError('invalid mail attachment bytes');
|
|
1710
|
-
}
|
|
1711
|
-
const fileName = mailAttachmentFileName(value.fileName);
|
|
1712
|
-
const contentType = mailAttachmentContentType(value.contentType);
|
|
1713
|
-
const bytes = value.bytes;
|
|
1714
|
-
return {
|
|
1715
|
-
fileName,
|
|
1716
|
-
contentType,
|
|
1717
|
-
sizeBytes: bytes.byteLength,
|
|
1718
|
-
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
1719
|
-
bytesBase64: Buffer.from(bytes).toString('base64'),
|
|
1720
|
-
};
|
|
1721
|
-
}, { bindMailOwner: true });
|
|
2466
|
+
return this.run(client => client.sendMail(normalized));
|
|
1722
2467
|
}
|
|
1723
2468
|
/**
|
|
1724
2469
|
* Permanently remove the exact SDK-owned local state after an explicit browser acknowledgement.
|
|
@@ -1730,27 +2475,35 @@ let AwikiService = (() => {
|
|
|
1730
2475
|
if (request?.confirmation !== AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION) {
|
|
1731
2476
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1732
2477
|
}
|
|
2478
|
+
// Fence pending post-Recovery work before the serialized clear reaches the head of the queue.
|
|
2479
|
+
this.sessionRevision += 1;
|
|
1733
2480
|
return this.mutateSession(async () => {
|
|
1734
2481
|
const provider = this.provider;
|
|
1735
2482
|
if (provider !== undefined)
|
|
1736
|
-
await this.
|
|
1737
|
-
|
|
2483
|
+
await this.stopProviderRuntime(provider);
|
|
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
|
+
}
|
|
1738
2491
|
if (!result.ok) {
|
|
1739
2492
|
if (provider !== undefined && this.provider === provider)
|
|
1740
|
-
|
|
2493
|
+
this.ensureProviderRuntime(provider);
|
|
1741
2494
|
return result;
|
|
1742
2495
|
}
|
|
1743
2496
|
try {
|
|
1744
2497
|
await this.imageAttachmentCache.clear();
|
|
1745
|
-
await this.
|
|
2498
|
+
await clearLegacySentMailCache(this.activeClientOptions?.stateRoot ?? this.resolved.stateRoot);
|
|
1746
2499
|
await this.conversationPreferenceStore.clear();
|
|
1747
2500
|
await this.sessionStore.signIn();
|
|
1748
2501
|
this.signedOut = false;
|
|
1749
2502
|
this.activeIdentityDid = undefined;
|
|
2503
|
+
this.pendingDeviceJoin = undefined;
|
|
2504
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
1750
2505
|
this.invalidateSummaries();
|
|
1751
2506
|
this.publishSession({ status: 'unregistered' });
|
|
1752
|
-
if (provider !== undefined && this.provider === provider)
|
|
1753
|
-
void this.startListener(provider);
|
|
1754
2507
|
return result;
|
|
1755
2508
|
}
|
|
1756
2509
|
catch {
|
|
@@ -1758,53 +2511,291 @@ let AwikiService = (() => {
|
|
|
1758
2511
|
}
|
|
1759
2512
|
});
|
|
1760
2513
|
}
|
|
1761
|
-
/** Re-enter after Core
|
|
1762
|
-
async applyRecoveredSession(progress) {
|
|
2514
|
+
/** Re-enter only after Core confirms that the exact recovered identity is applied locally. */
|
|
2515
|
+
async applyRecoveredSession(progress, requestGeneration) {
|
|
1763
2516
|
if (progress.phase !== 'applied')
|
|
1764
|
-
return true;
|
|
2517
|
+
return { ok: true, value: progress };
|
|
1765
2518
|
return this.mutateSession(async () => {
|
|
1766
2519
|
const provider = this.provider;
|
|
1767
2520
|
if (provider === undefined)
|
|
1768
|
-
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
|
+
}
|
|
1769
2531
|
try {
|
|
1770
2532
|
const identity = await provider.client.getIdentity();
|
|
1771
|
-
|
|
1772
|
-
|
|
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
|
+
}
|
|
1773
2552
|
const alreadyActive = this.signedOut === false && this.activeIdentityDid === identity.did;
|
|
1774
2553
|
if (!alreadyActive) {
|
|
1775
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
|
+
}
|
|
1776
2564
|
this.signedOut = false;
|
|
1777
2565
|
this.activeIdentityDid = identity.did;
|
|
1778
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
|
+
};
|
|
1779
2578
|
}
|
|
1780
|
-
if (this.provider !== provider)
|
|
1781
|
-
return false;
|
|
1782
2579
|
if (!alreadyActive) {
|
|
1783
2580
|
const session = { status: 'active', identity };
|
|
1784
2581
|
this.publishSession(session);
|
|
1785
|
-
|
|
1786
|
-
await this.startListener(provider);
|
|
2582
|
+
this.ensureProviderRuntime(provider);
|
|
1787
2583
|
}
|
|
1788
|
-
return true;
|
|
2584
|
+
return { ok: true, value: { ...progress, mailRecoveryObservability } };
|
|
1789
2585
|
}
|
|
1790
2586
|
catch {
|
|
1791
|
-
return false;
|
|
2587
|
+
return { ok: false, error: failure('remote') };
|
|
1792
2588
|
}
|
|
1793
2589
|
});
|
|
1794
2590
|
}
|
|
1795
|
-
/**
|
|
2591
|
+
/** Rebind Mail first-use ownership and retain only its closed, secret-free classification. */
|
|
2592
|
+
async reconcileRecoveredMailbox(provider) {
|
|
2593
|
+
try {
|
|
2594
|
+
await provider.client.getMailAccount();
|
|
2595
|
+
return successfulMailRecoveryObservability();
|
|
2596
|
+
}
|
|
2597
|
+
catch (error) {
|
|
2598
|
+
return failedMailRecoveryObservability(error);
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
/** Select the only resumable new-device session; Core local_sessions is the sole restart SoT. */
|
|
2602
|
+
async selectDeviceJoinSession(client) {
|
|
2603
|
+
const sessions = await client.listLocalDeviceJoinSessions();
|
|
2604
|
+
const resumable = sessions.filter(value => (value.side === 'new_device' && RESUMABLE_JOIN_PHASES.has(value.localPhase)));
|
|
2605
|
+
if (this.activeDeviceJoinSessionId !== undefined) {
|
|
2606
|
+
const tracked = resumable.find(value => value.joinSessionId === this.activeDeviceJoinSessionId);
|
|
2607
|
+
if (tracked !== undefined)
|
|
2608
|
+
return tracked.joinSessionId;
|
|
2609
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
2610
|
+
}
|
|
2611
|
+
if (resumable.length > 1) {
|
|
2612
|
+
throw Object.assign(new Error('multiple local joins'), { name: 'AwikiSdkError', code: 'conflict' });
|
|
2613
|
+
}
|
|
2614
|
+
const selected = resumable[0];
|
|
2615
|
+
this.activeDeviceJoinSessionId = selected?.joinSessionId;
|
|
2616
|
+
return selected?.joinSessionId ?? null;
|
|
2617
|
+
}
|
|
2618
|
+
async applyCandidateJoinProgress(value) {
|
|
2619
|
+
const phase = candidateJoinPhase(value);
|
|
2620
|
+
if (phase === undefined)
|
|
2621
|
+
return { ok: false, error: failure('remote') };
|
|
2622
|
+
if (phase === 'cancelled' || phase === 'rejected' || phase === 'expired') {
|
|
2623
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
2624
|
+
}
|
|
2625
|
+
if (phase === 'authorized') {
|
|
2626
|
+
if (value.identity === undefined)
|
|
2627
|
+
return { ok: false, error: failure('remote') };
|
|
2628
|
+
await this.activateRegisteredIdentity(value.identity);
|
|
2629
|
+
}
|
|
2630
|
+
return {
|
|
2631
|
+
ok: true,
|
|
2632
|
+
value: {
|
|
2633
|
+
phase,
|
|
2634
|
+
expiresAt: value.expiresAt,
|
|
2635
|
+
...(phase === 'sas-ready' && value.sas !== undefined ? { sas: value.sas } : {}),
|
|
2636
|
+
completed: phase === 'authorized',
|
|
2637
|
+
},
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
publicAdminJoinProgress(requestRef, value) {
|
|
2641
|
+
const phase = adminJoinPhase(value);
|
|
2642
|
+
if (phase === undefined)
|
|
2643
|
+
return { ok: false, error: failure('remote') };
|
|
2644
|
+
return {
|
|
2645
|
+
ok: true,
|
|
2646
|
+
value: {
|
|
2647
|
+
requestRef,
|
|
2648
|
+
phase,
|
|
2649
|
+
expiresAt: value.expiresAt,
|
|
2650
|
+
...(phase === 'sas-ready' && value.sas !== undefined ? { sas: value.sas } : {}),
|
|
2651
|
+
},
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
async localAdminJoinProgress(client, notice) {
|
|
2655
|
+
const local = (await client.listLocalDeviceJoinSessions())
|
|
2656
|
+
.find(session => session.side === 'admin' && session.joinSessionId === notice.joinSessionId);
|
|
2657
|
+
if (local === undefined) {
|
|
2658
|
+
return notice.claimedByCurrentDevice
|
|
2659
|
+
? client.getLocalDeviceJoinVerificationProgress(notice.joinSessionId)
|
|
2660
|
+
: undefined;
|
|
2661
|
+
}
|
|
2662
|
+
if (local.localPhase === 'challenge_prepared') {
|
|
2663
|
+
return {
|
|
2664
|
+
joinSessionId: notice.joinSessionId,
|
|
2665
|
+
localPhase: local.localPhase,
|
|
2666
|
+
remoteState: notice.state,
|
|
2667
|
+
expiresAt: local.expiresAt,
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
return client.getLocalDeviceJoinVerificationProgress(notice.joinSessionId);
|
|
2671
|
+
}
|
|
2672
|
+
async requireDeviceManager(client) {
|
|
2673
|
+
const current = await client.getCurrentDeviceSummary();
|
|
2674
|
+
if (!current.canManage || current.role !== 'admin' || current.readiness !== 'admin_ready') {
|
|
2675
|
+
throw Object.assign(new Error('device manager required'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2676
|
+
}
|
|
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
|
+
}
|
|
2698
|
+
requestRef(joinSessionId) {
|
|
2699
|
+
const existing = this.requestRefs.get(joinSessionId);
|
|
2700
|
+
if (existing !== undefined)
|
|
2701
|
+
return existing;
|
|
2702
|
+
const reference = `join-${randomUUID()}`;
|
|
2703
|
+
this.requestRefs.set(joinSessionId, reference);
|
|
2704
|
+
this.requestSessions.set(reference, joinSessionId);
|
|
2705
|
+
return reference;
|
|
2706
|
+
}
|
|
2707
|
+
deviceRef(deviceId) {
|
|
2708
|
+
const existing = this.deviceRefs.get(deviceId);
|
|
2709
|
+
if (existing !== undefined)
|
|
2710
|
+
return existing;
|
|
2711
|
+
const reference = `device-${randomUUID()}`;
|
|
2712
|
+
this.deviceRefs.set(deviceId, reference);
|
|
2713
|
+
this.deviceIds.set(reference, deviceId);
|
|
2714
|
+
return reference;
|
|
2715
|
+
}
|
|
2716
|
+
async deviceManagementSnapshot(client) {
|
|
2717
|
+
await client.syncDeviceManagement();
|
|
2718
|
+
const current = await client.getCurrentDeviceSummary();
|
|
2719
|
+
if (!current.canManage || current.role !== 'admin' || current.readiness !== 'admin_ready') {
|
|
2720
|
+
this.requestRefs.clear();
|
|
2721
|
+
this.requestSessions.clear();
|
|
2722
|
+
this.deviceRefs.clear();
|
|
2723
|
+
this.deviceIds.clear();
|
|
2724
|
+
this.rootTransfers.clear();
|
|
2725
|
+
return {
|
|
2726
|
+
canManage: false,
|
|
2727
|
+
rootTransferSupported: client.trustedUserPresenceSupported,
|
|
2728
|
+
...current.role === undefined ? {} : { role: current.role },
|
|
2729
|
+
readiness: current.readiness,
|
|
2730
|
+
devices: [],
|
|
2731
|
+
requests: [],
|
|
2732
|
+
};
|
|
2733
|
+
}
|
|
2734
|
+
const requests = await client.listLocalDeviceJoinRequests();
|
|
2735
|
+
await Promise.all(requests
|
|
2736
|
+
.filter(request => request.claimedByCurrentDevice)
|
|
2737
|
+
.map(request => client.getLocalDeviceJoinVerificationProgress(request.joinSessionId).catch(() => undefined)));
|
|
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();
|
|
2750
|
+
return {
|
|
2751
|
+
canManage: true,
|
|
2752
|
+
rootTransferSupported: client.trustedUserPresenceSupported,
|
|
2753
|
+
role: 'admin',
|
|
2754
|
+
readiness: 'admin_ready',
|
|
2755
|
+
devices: devices
|
|
2756
|
+
.filter(device => device.status === 'active')
|
|
2757
|
+
.map(device => this.publicDevice(device, joinedAtByDeviceId.get(device.deviceId) ?? (device.isCurrent ? initialDeviceJoinedAt : undefined))),
|
|
2758
|
+
requests: requests.map(request => ({
|
|
2759
|
+
requestRef: this.requestRef(request.joinSessionId),
|
|
2760
|
+
candidateKeyFingerprint: request.candidateKeyFingerprint,
|
|
2761
|
+
issuedAt: request.issuedAt,
|
|
2762
|
+
expiresAt: request.expiresAt,
|
|
2763
|
+
state: requestJoinPhase(request),
|
|
2764
|
+
claimedByCurrentDevice: request.claimedByCurrentDevice,
|
|
2765
|
+
canStartVerification: request.canStartVerification,
|
|
2766
|
+
})),
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
publicDevice(device, joinedAt) {
|
|
2770
|
+
const fingerprint = createHash('sha256').update(device.deviceId).digest('hex').slice(0, 8).toUpperCase();
|
|
2771
|
+
return {
|
|
2772
|
+
deviceRef: this.deviceRef(device.deviceId),
|
|
2773
|
+
displayId: `${fingerprint.slice(0, 4)}-${fingerprint.slice(4)}`,
|
|
2774
|
+
...joinedAt === undefined ? {} : { joinedAt },
|
|
2775
|
+
status: device.status,
|
|
2776
|
+
role: device.role,
|
|
2777
|
+
managementReady: device.managementReady,
|
|
2778
|
+
isCurrent: device.isCurrent,
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
/** Publish one newly registered identity, then start realtime only in the background. */
|
|
1796
2782
|
async activateRegisteredIdentity(identity) {
|
|
2783
|
+
this.pendingDeviceJoin = undefined;
|
|
2784
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
1797
2785
|
this.activeIdentityDid = identity.did;
|
|
1798
2786
|
this.publishSession({ status: 'active', identity });
|
|
1799
2787
|
const provider = this.provider;
|
|
1800
|
-
if (provider !== undefined)
|
|
1801
|
-
|
|
1802
|
-
await this.startListener(provider);
|
|
1803
|
-
}
|
|
2788
|
+
if (provider !== undefined)
|
|
2789
|
+
this.ensureProviderRuntime(provider);
|
|
1804
2790
|
}
|
|
1805
2791
|
/** Invalidate cached session work and cancel every model request still owned by the old session. */
|
|
1806
2792
|
invalidateSummaries() {
|
|
1807
2793
|
this.sessionRevision += 1;
|
|
2794
|
+
this.requestRefs.clear();
|
|
2795
|
+
this.requestSessions.clear();
|
|
2796
|
+
this.deviceRefs.clear();
|
|
2797
|
+
this.deviceIds.clear();
|
|
2798
|
+
this.rootTransfers.clear();
|
|
1808
2799
|
for (const controller of this.activeSummaryRequests)
|
|
1809
2800
|
controller.abort();
|
|
1810
2801
|
this.activeSummaryRequests.clear();
|
|
@@ -1815,40 +2806,31 @@ let AwikiService = (() => {
|
|
|
1815
2806
|
}
|
|
1816
2807
|
/** Resolve and cache the owner binding required by private Host-side projections. */
|
|
1817
2808
|
async ownerDid(client) {
|
|
1818
|
-
const identity = await client.getIdentity();
|
|
1819
|
-
const ownerDid = identity?.did;
|
|
2809
|
+
const identity = this.activeIdentityDid === undefined ? await client.getIdentity() : undefined;
|
|
2810
|
+
const ownerDid = this.activeIdentityDid ?? identity?.did;
|
|
1820
2811
|
if (ownerDid === undefined) {
|
|
1821
2812
|
throw Object.assign(new Error('not registered'), { name: 'AwikiSdkError', code: 'not-registered' });
|
|
1822
2813
|
}
|
|
1823
|
-
if (this.activeIdentityDid !== undefined && this.activeIdentityDid !== ownerDid) {
|
|
1824
|
-
throw new ProviderUnavailableError();
|
|
1825
|
-
}
|
|
1826
2814
|
this.activeIdentityDid = ownerDid;
|
|
1827
2815
|
return ownerDid;
|
|
1828
2816
|
}
|
|
1829
2817
|
/** Invoke the current client and normalize every rejection to a public result. */
|
|
1830
2818
|
async run(operation, options = {}) {
|
|
1831
2819
|
try {
|
|
1832
|
-
const
|
|
2820
|
+
const generation = this.runtimeGeneration;
|
|
2821
|
+
if (this.getUpdatePolicyStatus().restricted) {
|
|
2822
|
+
return { ok: false, error: failure('client-version-unsupported') };
|
|
2823
|
+
}
|
|
1833
2824
|
if (options.allowSignedOut !== true && await this.isSignedOut()) {
|
|
1834
2825
|
return { ok: false, error: failure('signed-out') };
|
|
1835
2826
|
}
|
|
1836
2827
|
const provider = this.provider;
|
|
1837
2828
|
if (provider === undefined)
|
|
1838
2829
|
throw new ProviderUnavailableError();
|
|
1839
|
-
const
|
|
1840
|
-
|
|
1841
|
-
:
|
|
1842
|
-
|
|
1843
|
-
if (this.provider !== provider
|
|
1844
|
-
|| (options.bindMailOwner === true && (this.sessionRevision !== sessionRevision
|
|
1845
|
-
|| this.signedOut === true
|
|
1846
|
-
|| this.activeIdentityDid !== ownerDid)))
|
|
1847
|
-
throw new ProviderUnavailableError();
|
|
1848
|
-
};
|
|
1849
|
-
assertActive();
|
|
1850
|
-
const value = await operation(provider.client, provider, ownerDid, assertActive);
|
|
1851
|
-
assertActive();
|
|
2830
|
+
const value = await operation(provider.client);
|
|
2831
|
+
if (this.runtimeGeneration !== generation || this.provider !== provider) {
|
|
2832
|
+
return { ok: false, error: failure('conflict') };
|
|
2833
|
+
}
|
|
1852
2834
|
if (options.skipAttachmentByteValidation !== true && containsUnexpectedBinary(value, new Set())) {
|
|
1853
2835
|
return { ok: false, error: failure('remote') };
|
|
1854
2836
|
}
|
|
@@ -1858,8 +2840,13 @@ let AwikiService = (() => {
|
|
|
1858
2840
|
return { ok: false, error: normalizeFailure(error) };
|
|
1859
2841
|
}
|
|
1860
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
|
+
}
|
|
1861
2848
|
/** Validate mail input before entering the provider and preserve fixed public failures. */
|
|
1862
|
-
runValidatedMail(validate, operation
|
|
2849
|
+
runValidatedMail(validate, operation) {
|
|
1863
2850
|
let request;
|
|
1864
2851
|
try {
|
|
1865
2852
|
request = validate();
|
|
@@ -1867,7 +2854,7 @@ let AwikiService = (() => {
|
|
|
1867
2854
|
catch {
|
|
1868
2855
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1869
2856
|
}
|
|
1870
|
-
return this.run(client => operation(client, request)
|
|
2857
|
+
return this.run(client => operation(client, request));
|
|
1871
2858
|
}
|
|
1872
2859
|
/** Read and cache the private Host-owned session marker. */
|
|
1873
2860
|
async isSignedOut() {
|
|
@@ -1918,165 +2905,211 @@ let AwikiService = (() => {
|
|
|
1918
2905
|
}
|
|
1919
2906
|
/** Serialize sign-in, sign-out, and destructive clear transitions. */
|
|
1920
2907
|
mutateSession(operation) {
|
|
1921
|
-
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);
|
|
1922
2915
|
this.sessionMutation = pending.then(() => undefined, () => undefined);
|
|
1923
2916
|
return pending;
|
|
1924
2917
|
}
|
|
1925
|
-
/** Start
|
|
1926
|
-
|
|
1927
|
-
if (
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
return
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
return
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
2918
|
+
/** Start identity realtime and the optional Agent consumer without blocking identity success. */
|
|
2919
|
+
ensureProviderRuntime(provider) {
|
|
2920
|
+
if (this.tenantSwitching)
|
|
2921
|
+
return;
|
|
2922
|
+
const identityDid = this.activeIdentityDid;
|
|
2923
|
+
if (provider.runtimeReplacement !== undefined)
|
|
2924
|
+
return;
|
|
2925
|
+
if (identityDid !== undefined
|
|
2926
|
+
&& provider.realtimeIdentityDid !== undefined
|
|
2927
|
+
&& provider.realtimeIdentityDid !== identityDid) {
|
|
2928
|
+
this.replaceProviderRuntime(provider, identityDid);
|
|
2929
|
+
return;
|
|
2930
|
+
}
|
|
2931
|
+
this.ensureRealtimeSupervisor(provider);
|
|
2932
|
+
this.ensureAgentConsumer(provider);
|
|
2933
|
+
}
|
|
2934
|
+
replaceProviderRuntime(provider, identityDid) {
|
|
2935
|
+
if (provider.runtimeReplacement !== undefined || this.provider !== provider)
|
|
2936
|
+
return;
|
|
2937
|
+
const replacement = (async () => {
|
|
2938
|
+
await this.stopProviderRuntime(provider);
|
|
2939
|
+
if (this.provider !== provider || this.activeIdentityDid !== identityDid)
|
|
2940
|
+
return;
|
|
2941
|
+
this.ensureRealtimeSupervisor(provider);
|
|
2942
|
+
this.ensureAgentConsumer(provider);
|
|
2943
|
+
})();
|
|
2944
|
+
const observed = replacement
|
|
2945
|
+
.catch((error) => {
|
|
2946
|
+
this.ctx.logger('awiki-realtime').warn('AWiki identity realtime replacement failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
2947
|
+
})
|
|
2948
|
+
.finally(() => {
|
|
2949
|
+
if (provider.runtimeReplacement === observed)
|
|
2950
|
+
delete provider.runtimeReplacement;
|
|
2951
|
+
if (this.provider === provider)
|
|
2952
|
+
this.ensureProviderRuntime(provider);
|
|
2953
|
+
});
|
|
2954
|
+
provider.runtimeReplacement = observed;
|
|
2955
|
+
}
|
|
2956
|
+
ensureRealtimeSupervisor(provider) {
|
|
2957
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
2958
|
+
return;
|
|
2959
|
+
if (!this.resolved.realtimeEnabled
|
|
2960
|
+
|| provider.realtimeSupervisor !== undefined
|
|
2961
|
+
|| provider.realtimeStartup !== undefined
|
|
2962
|
+
|| this.provider !== provider
|
|
2963
|
+
|| (provider.client.realtime ?? provider.client.listener) === undefined)
|
|
2964
|
+
return;
|
|
2965
|
+
const generation = provider.realtimeGeneration;
|
|
2966
|
+
const source = provider.client.realtime ?? provider.client.listener;
|
|
2967
|
+
if (source === undefined)
|
|
2968
|
+
return;
|
|
2969
|
+
const logger = this.ctx.logger('awiki-realtime');
|
|
1943
2970
|
const startup = (async () => {
|
|
1944
2971
|
if (await this.isSignedOut())
|
|
1945
2972
|
return;
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
if (!this.listenerFenceMatches(provider, workspaceContext, generation))
|
|
2973
|
+
const identity = await provider.client.getIdentity();
|
|
2974
|
+
if (identity === null || !this.realtimeFenceMatches(provider, generation))
|
|
1949
2975
|
return;
|
|
1950
|
-
|
|
1951
|
-
const
|
|
1952
|
-
|
|
1953
|
-
stateRoot: this.activeTenant().stateRoot,
|
|
2976
|
+
this.activeIdentityDid = identity.did;
|
|
2977
|
+
const supervisor = new IdentityRealtimeSupervisor(source, {
|
|
2978
|
+
onSynchronized: cause => this.onRealtimeSynchronized(provider, generation, cause),
|
|
1954
2979
|
}, logger);
|
|
1955
|
-
provider.
|
|
2980
|
+
provider.realtimeSupervisor = supervisor;
|
|
2981
|
+
provider.realtimeIdentityDid = identity.did;
|
|
2982
|
+
supervisor.start();
|
|
2983
|
+
this.ensureAgentConsumer(provider);
|
|
2984
|
+
})();
|
|
2985
|
+
const observed = startup
|
|
2986
|
+
.catch((error) => {
|
|
2987
|
+
logger.warn('AWiki realtime startup failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
2988
|
+
})
|
|
2989
|
+
.finally(() => {
|
|
2990
|
+
if (provider.realtimeStartup === observed)
|
|
2991
|
+
delete provider.realtimeStartup;
|
|
2992
|
+
});
|
|
2993
|
+
provider.realtimeStartup = observed;
|
|
2994
|
+
}
|
|
2995
|
+
realtimeFenceMatches(provider, generation) {
|
|
2996
|
+
return this.provider === provider && provider.realtimeGeneration === generation;
|
|
2997
|
+
}
|
|
2998
|
+
async onRealtimeSynchronized(provider, generation, cause) {
|
|
2999
|
+
if (!this.realtimeFenceMatches(provider, generation))
|
|
3000
|
+
return;
|
|
3001
|
+
if (cause === 'system_notification' || cause === 'stream_recovery') {
|
|
1956
3002
|
try {
|
|
1957
|
-
await
|
|
3003
|
+
provider.localDeviceJoinRequestCountAfterSync = (await provider.client.listLocalDeviceJoinRequests()).length;
|
|
1958
3004
|
}
|
|
1959
|
-
catch
|
|
1960
|
-
|
|
1961
|
-
delete provider.listener;
|
|
1962
|
-
try {
|
|
1963
|
-
await listener.dispose();
|
|
1964
|
-
}
|
|
1965
|
-
catch {
|
|
1966
|
-
provider.listenerRecoveryBlocked = true;
|
|
1967
|
-
}
|
|
1968
|
-
if (!provider.listenerRecoveryBlocked && provider.listenerRestartAttempt > 0) {
|
|
1969
|
-
this.scheduleListenerRestart(provider, workspaceContext, generation);
|
|
1970
|
-
}
|
|
1971
|
-
throw error;
|
|
3005
|
+
catch {
|
|
3006
|
+
this.ctx.logger('awiki-realtime').debug('AWiki realtime could not observe local device Join requests');
|
|
1972
3007
|
}
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
3008
|
+
}
|
|
3009
|
+
if (!['session_start', 'connection_ready', 'reconnected', 'message', 'message_update'].includes(cause))
|
|
3010
|
+
return;
|
|
3011
|
+
this.ensureAgentConsumer(provider);
|
|
3012
|
+
await provider.agentConsumerStartup;
|
|
3013
|
+
if (!this.realtimeFenceMatches(provider, generation))
|
|
3014
|
+
return;
|
|
3015
|
+
await provider.agentConsumer?.reconcileOnce();
|
|
3016
|
+
}
|
|
3017
|
+
ensureAgentConsumer(provider) {
|
|
3018
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
3019
|
+
return;
|
|
3020
|
+
const workspaceContext = this.workspaceContext;
|
|
3021
|
+
const source = provider.client.agentInbox ?? provider.client.listener;
|
|
3022
|
+
const identityDid = this.activeIdentityDid;
|
|
3023
|
+
if (!this.resolved.listenerEnabled
|
|
3024
|
+
|| workspaceContext === undefined
|
|
3025
|
+
|| source === undefined
|
|
3026
|
+
|| identityDid === undefined
|
|
3027
|
+
|| this.provider !== provider)
|
|
3028
|
+
return;
|
|
3029
|
+
if (provider.agentConsumer !== undefined && provider.agentConsumerIdentityDid === identityDid)
|
|
3030
|
+
return;
|
|
3031
|
+
if (provider.agentConsumerStartup !== undefined)
|
|
3032
|
+
return;
|
|
3033
|
+
const generation = ++provider.agentConsumerGeneration;
|
|
3034
|
+
const logger = this.ctx.logger('awiki-listener');
|
|
3035
|
+
const startup = (async () => {
|
|
3036
|
+
await this.detachAgentConsumer(provider);
|
|
3037
|
+
if (!this.agentConsumerFenceMatches(provider, workspaceContext, identityDid, generation))
|
|
1983
3038
|
return;
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
3039
|
+
const agents = new DshAwikiListenerAgentRuntime(workspaceContext, this.resolved.listener.workspacePath);
|
|
3040
|
+
const consumer = new AwikiAgentListener(source, agents, {
|
|
3041
|
+
...this.resolved.listener,
|
|
3042
|
+
stateRoot: this.activeClientOptions?.stateRoot ?? this.resolved.listener.stateRoot,
|
|
3043
|
+
identityScope: identityDid,
|
|
3044
|
+
}, logger);
|
|
3045
|
+
provider.agentConsumer = consumer;
|
|
3046
|
+
provider.agentConsumerIdentityDid = identityDid;
|
|
3047
|
+
await consumer.reconcileOnce();
|
|
1991
3048
|
})();
|
|
1992
3049
|
const observed = startup
|
|
1993
3050
|
.catch((error) => {
|
|
1994
|
-
logger.warn('AWiki
|
|
3051
|
+
logger.warn('AWiki Agent consumer startup failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
1995
3052
|
})
|
|
1996
3053
|
.finally(() => {
|
|
1997
|
-
if (provider.
|
|
1998
|
-
delete provider.
|
|
3054
|
+
if (provider.agentConsumerStartup === observed)
|
|
3055
|
+
delete provider.agentConsumerStartup;
|
|
1999
3056
|
});
|
|
2000
|
-
provider.
|
|
2001
|
-
return observed;
|
|
2002
|
-
}
|
|
2003
|
-
async stopListener(provider) {
|
|
2004
|
-
provider.listenerGeneration += 1;
|
|
2005
|
-
provider.listenerRestartAttempt = 0;
|
|
2006
|
-
delete provider.listenerStartedAt;
|
|
2007
|
-
const timer = provider.listenerRestartTimer;
|
|
2008
|
-
if (timer !== undefined) {
|
|
2009
|
-
delete provider.listenerRestartTimer;
|
|
2010
|
-
clearTimeout(timer);
|
|
2011
|
-
}
|
|
2012
|
-
await provider.listenerStartup;
|
|
2013
|
-
await provider.listenerCleanup;
|
|
2014
|
-
const listener = provider.listener;
|
|
2015
|
-
if (listener === undefined)
|
|
2016
|
-
return;
|
|
2017
|
-
delete provider.listener;
|
|
2018
|
-
try {
|
|
2019
|
-
await listener.dispose();
|
|
2020
|
-
}
|
|
2021
|
-
catch (error) {
|
|
2022
|
-
provider.listenerRecoveryBlocked = true;
|
|
2023
|
-
throw error;
|
|
2024
|
-
}
|
|
3057
|
+
provider.agentConsumerStartup = observed;
|
|
2025
3058
|
}
|
|
2026
|
-
|
|
3059
|
+
agentConsumerFenceMatches(provider, workspaceContext, identityDid, generation) {
|
|
2027
3060
|
return this.provider === provider
|
|
2028
3061
|
&& this.workspaceContext === workspaceContext
|
|
2029
|
-
&&
|
|
3062
|
+
&& this.activeIdentityDid === identityDid
|
|
3063
|
+
&& provider.agentConsumerGeneration === generation;
|
|
2030
3064
|
}
|
|
2031
|
-
|
|
2032
|
-
|
|
3065
|
+
async detachAgentConsumer(provider) {
|
|
3066
|
+
const consumer = provider.agentConsumer;
|
|
3067
|
+
if (consumer === undefined)
|
|
2033
3068
|
return;
|
|
2034
|
-
delete provider.
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
if (startedAt !== undefined && Date.now() - startedAt >= LISTENER_STABLE_RESET_MS) {
|
|
2038
|
-
provider.listenerRestartAttempt = 0;
|
|
2039
|
-
}
|
|
2040
|
-
const logger = this.ctx.logger('awiki-listener');
|
|
2041
|
-
logger.warn('AWiki listener lifecycle failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
2042
|
-
let disposed = false;
|
|
2043
|
-
const cleanup = listener.dispose()
|
|
2044
|
-
.then(() => { disposed = true; }, (cleanupError) => {
|
|
2045
|
-
provider.listenerRecoveryBlocked = true;
|
|
2046
|
-
logger.warn('AWiki listener cleanup failed: %s', cleanupError instanceof Error ? cleanupError.message : 'unknown failure');
|
|
2047
|
-
});
|
|
3069
|
+
delete provider.agentConsumer;
|
|
3070
|
+
delete provider.agentConsumerIdentityDid;
|
|
3071
|
+
const cleanup = consumer.dispose();
|
|
2048
3072
|
const observed = cleanup.finally(() => {
|
|
2049
|
-
if (provider.
|
|
2050
|
-
delete provider.
|
|
2051
|
-
if (disposed)
|
|
2052
|
-
this.scheduleListenerRestart(provider, workspaceContext, generation);
|
|
3073
|
+
if (provider.agentConsumerCleanup === observed)
|
|
3074
|
+
delete provider.agentConsumerCleanup;
|
|
2053
3075
|
});
|
|
2054
|
-
provider.
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
3076
|
+
provider.agentConsumerCleanup = observed;
|
|
3077
|
+
await observed;
|
|
3078
|
+
}
|
|
3079
|
+
async stopAgentConsumer(provider) {
|
|
3080
|
+
provider.agentConsumerGeneration += 1;
|
|
3081
|
+
await provider.agentConsumerStartup;
|
|
3082
|
+
await provider.agentConsumerCleanup;
|
|
3083
|
+
await this.detachAgentConsumer(provider);
|
|
3084
|
+
}
|
|
3085
|
+
async stopRealtimeSupervisor(provider) {
|
|
3086
|
+
provider.realtimeGeneration += 1;
|
|
3087
|
+
await provider.realtimeStartup;
|
|
3088
|
+
const supervisor = provider.realtimeSupervisor;
|
|
3089
|
+
if (supervisor === undefined)
|
|
2059
3090
|
return;
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
void this.startListener(provider);
|
|
2070
|
-
}, delay);
|
|
2071
|
-
provider.listenerRestartTimer = timer;
|
|
3091
|
+
delete provider.realtimeSupervisor;
|
|
3092
|
+
delete provider.realtimeIdentityDid;
|
|
3093
|
+
await supervisor.dispose();
|
|
3094
|
+
}
|
|
3095
|
+
async stopProviderRuntime(provider) {
|
|
3096
|
+
await Promise.all([
|
|
3097
|
+
this.stopRealtimeSupervisor(provider),
|
|
3098
|
+
this.stopAgentConsumer(provider),
|
|
3099
|
+
]);
|
|
2072
3100
|
}
|
|
2073
3101
|
/** Clear one exact provider slot before joining its one shared disposal. */
|
|
2074
3102
|
disposeProvider(provider) {
|
|
2075
|
-
if (this.provider === provider)
|
|
3103
|
+
if (this.provider === provider) {
|
|
2076
3104
|
this.provider = undefined;
|
|
3105
|
+
this.pendingDeviceJoin = undefined;
|
|
3106
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
3107
|
+
this.invalidateSummaries();
|
|
3108
|
+
}
|
|
2077
3109
|
provider.disposal ??= (async () => {
|
|
2078
3110
|
try {
|
|
2079
|
-
await
|
|
3111
|
+
await provider.runtimeReplacement;
|
|
3112
|
+
await this.stopProviderRuntime(provider);
|
|
2080
3113
|
}
|
|
2081
3114
|
finally {
|
|
2082
3115
|
await provider.client.dispose();
|