@awiki/dsh-plugin 0.3.7 → 0.3.11-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -73
- package/README.zh.md +91 -62
- 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 +9281 -2379
- package/lib/client.js.map +1 -1
- package/lib/index.js +3553 -1138
- package/lib/model-proxy-contract.js +19 -11
- 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 +17 -8
- package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
- package/lib/types/client/AwikiIdentityAccess.js +269 -37
- 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 +6 -1
- package/lib/types/client/AwikiRecoveryForm.d.ts.map +1 -1
- package/lib/types/client/AwikiRecoveryForm.js +74 -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 +2 -2
- 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 +92 -44
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +761 -334
- 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 +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +64 -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 +7 -0
- package/lib/types/client/otp-cooldown.d.ts.map +1 -0
- package/lib/types/client/otp-cooldown.js +29 -0
- package/lib/types/client/otp-cooldown.js.map +1 -0
- package/lib/types/client/settings-controller.d.ts +38 -2
- package/lib/types/client/settings-controller.d.ts.map +1 -1
- package/lib/types/client/settings-controller.js +206 -10
- 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 +21 -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/client/store.d.ts +1 -1
- package/lib/types/client/store.d.ts.map +1 -1
- package/lib/types/client/store.js +1 -1
- package/lib/types/client/store.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 +165 -41
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1594 -535
- 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 +13 -128
- 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 +8 -8
- package/lib/types/model-proxy-contract.d.ts.map +1 -1
- package/lib/types/model-proxy-contract.js +15 -8
- 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/registration-policy.d.ts +7 -0
- package/lib/types/registration-policy.d.ts.map +1 -0
- package/lib/types/registration-policy.js +21 -0
- package/lib/types/registration-policy.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 +63 -5
- package/lib/types/settings-rpc-contract.d.ts.map +1 -1
- package/lib/types/settings-rpc-contract.js +108 -5
- 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 +132 -3
- package/lib/types/settings-rpc.js.map +1 -1
- package/lib/types/settings-transport.d.ts +7 -0
- package/lib/types/settings-transport.d.ts.map +1 -0
- package/lib/types/settings-transport.js +52 -0
- package/lib/types/settings-transport.js.map +1 -0
- 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 +88 -61
- 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,31 +32,43 @@ 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
|
+
export { registerAwikiLoopbackRpc } from "./settings-transport.js";
|
|
36
|
+
import { registerAwikiSettingsTransport } from "./settings-transport.js";
|
|
37
|
+
import { registrationHandleLocalPart, shortHandleInviteRequired } from "./registration-policy.js";
|
|
38
|
+
import { decodeDesktopDistribution } from "./desktop-distribution.js";
|
|
39
|
+
/** Unified AWiki identity, messaging, attachment, Remote, and model-tool service. */
|
|
36
40
|
import { Context } from '@deepseek-ai/cordis';
|
|
37
|
-
import { createHash } from 'node:crypto';
|
|
38
|
-
import { existsSync } from 'node:fs';
|
|
41
|
+
import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
39
42
|
import { homedir } from 'node:os';
|
|
40
43
|
import { isAbsolute, join } from 'node:path';
|
|
41
44
|
import z from '@deepseek-ai/schemastery';
|
|
42
45
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
43
|
-
import {
|
|
46
|
+
import {} from '@deepseek-ai/dsh-settings';
|
|
44
47
|
import { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "./types.js";
|
|
45
48
|
import { AwikiExternalHttpAuthError, createAwikiExternalHttpAuth, externalHttpAuthError, mapProviderError as mapExternalHttpProviderError, } from "./external-http-auth.js";
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
49
|
+
import { AwikiIntegrationClient } from "./integration-client.js";
|
|
50
|
+
import { clearLegacySentMailCache } from "./legacy-sent-mail-cache.js";
|
|
51
|
+
import { AwikiMailListClient } from "./mail-list-client.js";
|
|
52
|
+
import { AwikiSdkError, downloadedAttachment } from "./sdk-adapter.js";
|
|
53
|
+
import { failedMailRecoveryObservability, fencedMailRecoveryObservability, successfulMailRecoveryObservability, } from "./mail-recovery-observability.js";
|
|
48
54
|
import { registerAwikiTools } from "./tools.js";
|
|
49
|
-
import {
|
|
55
|
+
import { mailInboxRequest, mailMarkReadRequest, mailReadRequest, mailSendRequest, } from "./mail.js";
|
|
50
56
|
import { AwikiSettingsSchema, validateAwikiSettings, } from "./settings.js";
|
|
51
57
|
import { AWIKI_SETTINGS_NAMESPACE, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain } from "./domain.js";
|
|
58
|
+
import { AWIKI_DEFAULT_BUILTIN_TENANT, } from "./builtin-tenant-config.js";
|
|
52
59
|
import { AWIKI_SETTINGS_RPC_CHANNEL } from "./settings-rpc-contract.js";
|
|
53
60
|
import { createAwikiSettingsRpcHandler } from "./settings-rpc.js";
|
|
54
61
|
import { AwikiSessionStore } from "./session.js";
|
|
55
62
|
import { AwikiImageAttachmentCache, minimumImageAttachmentCacheMaxBytes } from "./attachment-cache.js";
|
|
56
|
-
import { AwikiSentMailStore, isLocalSentMailId } from "./sent-mail-store.js";
|
|
57
63
|
import { AwikiConversationPreferenceStore, normalizeConversationPreferenceMutation, } from "./conversation-preferences.js";
|
|
58
64
|
import { AwikiAgentListener, DshAwikiListenerAgentRuntime, } from "./listener.js";
|
|
59
|
-
import {
|
|
65
|
+
import { IdentityRealtimeSupervisor, } from "./realtime-supervisor.js";
|
|
66
|
+
import { resolveAwikiStateRoot } from "./profile-state.js";
|
|
67
|
+
import { AwikiTenantRegistry, } from "./tenant-registry.js";
|
|
68
|
+
import { checkAwikiUpdatePolicy, readAwikiUpdatePolicyStatus, DSH_AWIKI_VERSION, } from "./update-policy.js";
|
|
60
69
|
export { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "./types.js";
|
|
70
|
+
export { AWIKI_CHINA_TENANT_ID, AWIKI_GLOBAL_TENANT_ID, AWIKI_OFFICIAL_CATALOG_VERSION, AWIKI_TENANT_REGISTRY_SCHEMA_VERSION, } from "./tenant-registry.js";
|
|
71
|
+
export { DSH_AWIKI_MODEL_PROXY_VERSION, DSH_AWIKI_VERSION, compareVersions as compareAwikiPluginVersions, } from "./update-policy.js";
|
|
61
72
|
export { AWIKI_EXTERNAL_HTTP_MAX_BODY_BYTES, AwikiExternalHttpAuthError, } from "./external-http-auth.js";
|
|
62
73
|
export { AWIKI_DOMAIN_FIELD, AWIKI_SETTINGS_NAMESPACE, AwikiSettingsSchema, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain, validateAwikiSettings, } from "./settings.js";
|
|
63
74
|
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 +78,37 @@ export const DEFAULT_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
|
67
78
|
export const DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES = 64 * 1024 * 1024;
|
|
68
79
|
/** Default browser polling interval while the AWiki drawer is open. */
|
|
69
80
|
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 =
|
|
81
|
+
/** Package-configured primary AWiki service origin. */
|
|
82
|
+
export const DEFAULT_AWIKI_SERVICE_URL = AWIKI_DEFAULT_BUILTIN_TENANT.backendOrigin;
|
|
83
|
+
/** Package-configured primary authoritative AWiki message-service DID. */
|
|
84
|
+
export const DEFAULT_AWIKI_MESSAGE_SERVICE_DID = `did:wba:${AWIKI_DEFAULT_BUILTIN_TENANT.didHost}`;
|
|
74
85
|
/** Host-owned model input cap after message minimization. */
|
|
75
86
|
export const DEFAULT_SUMMARY_MAX_INPUT_BYTES = 32 * 1024;
|
|
76
87
|
/** Hard limit for one user-triggered conversation summary. */
|
|
77
88
|
export const MAX_SUMMARY_MESSAGES = 50;
|
|
78
89
|
/** Loader schema for the Host deployment configuration. */
|
|
79
90
|
export const Config = z.object({
|
|
80
|
-
userServiceUrl: z.string(),
|
|
91
|
+
userServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
81
92
|
userServiceDomain: z.string().default(DEFAULT_AWIKI_DOMAIN),
|
|
82
|
-
|
|
93
|
+
legacyTenantSlot: z.string(),
|
|
94
|
+
messageServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
83
95
|
mailServiceUrl: z.string(),
|
|
84
|
-
messageServicePublicUrl: z.string(),
|
|
85
|
-
messageServiceDid: z.string(),
|
|
96
|
+
messageServicePublicUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
97
|
+
messageServiceDid: z.string().default(DEFAULT_AWIKI_MESSAGE_SERVICE_DID),
|
|
86
98
|
allowedAttachmentOrigins: z.array(z.string()).default([]),
|
|
87
99
|
allowInsecureLoopbackForTesting: z.boolean().default(false),
|
|
88
100
|
stateRoot: z.string(),
|
|
89
101
|
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
102
|
imageAttachmentCacheMaxBytes: z.number().default(DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES),
|
|
94
103
|
pollIntervalMs: z.number().default(DEFAULT_POLL_INTERVAL_MS),
|
|
104
|
+
realtimeEnabled: z.boolean().default(true),
|
|
95
105
|
listenerEnabled: z.boolean().default(false),
|
|
96
106
|
listenerAllowedPeers: z.array(z.string()).default([]),
|
|
97
107
|
listenerWorkspacePath: z.string(),
|
|
98
108
|
summaryMaxInputBytes: z.number().default(DEFAULT_SUMMARY_MAX_INPUT_BYTES),
|
|
99
109
|
});
|
|
100
110
|
const FAILURE_CODES = new Set([
|
|
111
|
+
'client-version-unsupported',
|
|
101
112
|
'not-registered',
|
|
102
113
|
'signed-out',
|
|
103
114
|
'already-registered',
|
|
@@ -105,11 +116,12 @@ const FAILURE_CODES = new Set([
|
|
|
105
116
|
'invalid-otp',
|
|
106
117
|
'challenge-expired',
|
|
107
118
|
'handle-unavailable',
|
|
119
|
+
'short-handle-invite-required',
|
|
108
120
|
'not-found',
|
|
109
121
|
'forbidden',
|
|
122
|
+
'device-rejoin-required',
|
|
110
123
|
'identity-recovery-required',
|
|
111
124
|
'conflict',
|
|
112
|
-
'state-in-use',
|
|
113
125
|
'rate-limited',
|
|
114
126
|
'group-membership-required',
|
|
115
127
|
'group-identity-stale',
|
|
@@ -123,11 +135,18 @@ const FAILURE_CODES = new Set([
|
|
|
123
135
|
'network',
|
|
124
136
|
'remote',
|
|
125
137
|
]);
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const
|
|
138
|
+
const DEVICE_JOIN_APPROVAL_CONFIRMATION = 'APPROVE';
|
|
139
|
+
const DEVICE_REVOKE_CONFIRMATION = 'REVOKE';
|
|
140
|
+
const DEVICE_JOIN_CHALLENGE_TTL_SECONDS = 240;
|
|
141
|
+
const RESUMABLE_JOIN_PHASES = new Set([
|
|
142
|
+
'pending',
|
|
143
|
+
'challenge_prepared',
|
|
144
|
+
'response_prepared',
|
|
145
|
+
'response_verified',
|
|
146
|
+
'approval_prepared',
|
|
147
|
+
]);
|
|
130
148
|
const FAILURE_MESSAGES = {
|
|
149
|
+
'client-version-unsupported': 'This AWiki plugin version is not supported by the active tenant. Update the plugin or switch tenants.',
|
|
131
150
|
'not-registered': 'No AWiki identity is registered for this deployment.',
|
|
132
151
|
'signed-out': 'This installation is signed out of AWiki.',
|
|
133
152
|
'already-registered': 'This deployment already has an AWiki identity.',
|
|
@@ -135,11 +154,12 @@ const FAILURE_MESSAGES = {
|
|
|
135
154
|
'invalid-otp': 'The AWiki verification code is invalid.',
|
|
136
155
|
'challenge-expired': 'The AWiki verification challenge expired.',
|
|
137
156
|
'handle-unavailable': 'The requested AWiki handle is unavailable.',
|
|
157
|
+
'short-handle-invite-required': 'A new one- through four-character AWiki handle requires an invitation.',
|
|
138
158
|
'not-found': 'The requested AWiki resource was not found.',
|
|
139
159
|
'forbidden': 'The AWiki operation is not permitted.',
|
|
160
|
+
'device-rejoin-required': 'This device credential was revoked and must join the Handle again.',
|
|
140
161
|
'identity-recovery-required': 'The local AWiki identity must be recovered before it can be used again.',
|
|
141
162
|
'conflict': 'The AWiki operation conflicts with current state.',
|
|
142
|
-
'state-in-use': 'AWiki data is already open in another client.',
|
|
143
163
|
'rate-limited': 'The AWiki service rate-limited the request.',
|
|
144
164
|
'group-membership-required': 'The active AWiki identity is not a member of this group.',
|
|
145
165
|
'group-identity-stale': 'The AWiki group identity binding is still recovering.',
|
|
@@ -157,6 +177,67 @@ class ProviderUnavailableError extends Error {
|
|
|
157
177
|
}
|
|
158
178
|
class SummaryProviderUnavailableError extends Error {
|
|
159
179
|
}
|
|
180
|
+
function candidateJoinPhase(value) {
|
|
181
|
+
if (value.remoteState === 'rejected')
|
|
182
|
+
return 'rejected';
|
|
183
|
+
if (value.remoteState === 'cancelled' || value.localPhase === 'cancelled')
|
|
184
|
+
return 'cancelled';
|
|
185
|
+
if (value.remoteState === 'expired' || value.localPhase === 'expired')
|
|
186
|
+
return 'expired';
|
|
187
|
+
if (value.localPhase === 'authorized'
|
|
188
|
+
&& value.remoteState === 'consumed'
|
|
189
|
+
&& value.completed
|
|
190
|
+
&& value.identity !== undefined)
|
|
191
|
+
return 'authorized';
|
|
192
|
+
if (value.sas !== undefined) {
|
|
193
|
+
return value.remoteState === 'response_verified' && /^\d{6}$/u.test(value.sas)
|
|
194
|
+
? 'sas-ready'
|
|
195
|
+
: undefined;
|
|
196
|
+
}
|
|
197
|
+
if (value.remoteState === 'challenge_sent'
|
|
198
|
+
|| value.remoteState === 'response_verified'
|
|
199
|
+
|| value.remoteState === 'consumed'
|
|
200
|
+
|| value.localPhase !== 'pending')
|
|
201
|
+
return 'verifying';
|
|
202
|
+
return value.remoteState === 'pending' ? 'pending' : undefined;
|
|
203
|
+
}
|
|
204
|
+
function adminJoinPhase(value) {
|
|
205
|
+
if (value.remoteState === 'rejected')
|
|
206
|
+
return 'rejected';
|
|
207
|
+
if (value.remoteState === 'cancelled' || value.localPhase === 'cancelled')
|
|
208
|
+
return 'cancelled';
|
|
209
|
+
if (value.remoteState === 'expired' || value.localPhase === 'expired')
|
|
210
|
+
return 'expired';
|
|
211
|
+
if (value.localPhase === 'authorized' && value.remoteState === 'consumed')
|
|
212
|
+
return 'authorized';
|
|
213
|
+
if (value.sas !== undefined) {
|
|
214
|
+
return value.remoteState === 'response_verified' && /^\d{6}$/u.test(value.sas)
|
|
215
|
+
? 'sas-ready'
|
|
216
|
+
: undefined;
|
|
217
|
+
}
|
|
218
|
+
if (value.remoteState === 'challenge_sent'
|
|
219
|
+
|| value.remoteState === 'response_verified'
|
|
220
|
+
|| value.remoteState === 'consumed'
|
|
221
|
+
|| value.localPhase !== 'pending')
|
|
222
|
+
return 'verifying';
|
|
223
|
+
return value.remoteState === 'pending' ? 'pending' : undefined;
|
|
224
|
+
}
|
|
225
|
+
function requestJoinPhase(value) {
|
|
226
|
+
switch (value.state) {
|
|
227
|
+
case 'pending': return 'pending';
|
|
228
|
+
case 'challenge_sent':
|
|
229
|
+
case 'response_verified': return 'verifying';
|
|
230
|
+
case 'consumed': return 'authorized';
|
|
231
|
+
case 'cancelled': return 'cancelled';
|
|
232
|
+
case 'rejected': return 'rejected';
|
|
233
|
+
case 'expired': return 'expired';
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function constantTimeSasMatches(expected, actual) {
|
|
237
|
+
if (!/^\d{6}$/u.test(expected) || !/^\d{6}$/u.test(actual))
|
|
238
|
+
return false;
|
|
239
|
+
return timingSafeEqual(Buffer.from(expected, 'ascii'), Buffer.from(actual, 'ascii'));
|
|
240
|
+
}
|
|
160
241
|
/** Validate and preserve one SDK service URL without accepting insecure remote HTTP. */
|
|
161
242
|
function serviceUrl(field, raw, allowInsecureLoopbackForTesting) {
|
|
162
243
|
let url;
|
|
@@ -175,17 +256,25 @@ function serviceUrl(field, raw, allowInsecureLoopbackForTesting) {
|
|
|
175
256
|
}
|
|
176
257
|
return raw;
|
|
177
258
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
259
|
+
function publishedServiceBaseUrl(services, name, allowInsecureLoopbackForTesting) {
|
|
260
|
+
if (typeof services !== 'object' || services === null || Array.isArray(services))
|
|
261
|
+
return undefined;
|
|
262
|
+
const candidate = services[name];
|
|
263
|
+
if (typeof candidate !== 'object' || candidate === null || Array.isArray(candidate))
|
|
264
|
+
return undefined;
|
|
265
|
+
const value = candidate;
|
|
266
|
+
if (value.enabled !== true || typeof value.base_url !== 'string')
|
|
267
|
+
return undefined;
|
|
268
|
+
try {
|
|
269
|
+
const baseUrl = serviceUrl(`server-info.services.${name}.base_url`, value.base_url, allowInsecureLoopbackForTesting);
|
|
270
|
+
const parsed = new URL(baseUrl);
|
|
271
|
+
if (parsed.search !== '')
|
|
272
|
+
return undefined;
|
|
273
|
+
return baseUrl;
|
|
182
274
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (parsed.search !== '') {
|
|
186
|
-
throw new TypeError('awiki: recovery reconciliation target must not contain a query');
|
|
275
|
+
catch {
|
|
276
|
+
return undefined;
|
|
187
277
|
}
|
|
188
|
-
return new URL('/api/identity-recovery', parsed).toString();
|
|
189
278
|
}
|
|
190
279
|
/** Validate a provider domain without inferring it from an API endpoint. */
|
|
191
280
|
function serviceDomain(raw, field = 'userServiceDomain') {
|
|
@@ -241,38 +330,15 @@ function resolveConfig(ctx, config) {
|
|
|
241
330
|
const dshHome = configuredDshHome === undefined || configuredDshHome.length === 0
|
|
242
331
|
? join(homedir(), '.dsh')
|
|
243
332
|
: configuredDshHome;
|
|
244
|
-
const profileName = configuredStateRoot === undefined || configuredStateRoot.length === 0
|
|
245
|
-
? resolveAwikiProfileName(ctx, dshHome)
|
|
246
|
-
: undefined;
|
|
247
333
|
const stateRoot = configuredStateRoot === undefined || configuredStateRoot.length === 0
|
|
248
334
|
? resolveAwikiStateRoot(ctx, dshHome)
|
|
249
335
|
: configuredStateRoot;
|
|
250
|
-
const legacySharedStateDetected = profileName !== undefined
|
|
251
|
-
&& existsSync(join(dshHome, 'awiki', 'im-core'));
|
|
252
336
|
if (!isAbsolute(stateRoot))
|
|
253
337
|
throw new TypeError('awiki: stateRoot must be an absolute path');
|
|
254
338
|
const attachmentMaxBytes = config.attachmentMaxBytes ?? DEFAULT_ATTACHMENT_MAX_BYTES;
|
|
255
339
|
if (!Number.isSafeInteger(attachmentMaxBytes) || attachmentMaxBytes < 1) {
|
|
256
340
|
throw new TypeError('awiki: attachmentMaxBytes must be a positive safe integer');
|
|
257
341
|
}
|
|
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
342
|
const imageAttachmentCacheMaxBytes = config.imageAttachmentCacheMaxBytes ?? DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES;
|
|
277
343
|
if (!Number.isSafeInteger(imageAttachmentCacheMaxBytes)
|
|
278
344
|
|| imageAttachmentCacheMaxBytes < minimumImageAttachmentCacheMaxBytes(attachmentMaxBytes)) {
|
|
@@ -283,6 +349,10 @@ function resolveConfig(ctx, config) {
|
|
|
283
349
|
throw new TypeError('awiki: pollIntervalMs must be a safe integer from 1000 through 60000');
|
|
284
350
|
}
|
|
285
351
|
const listenerEnabled = config.listenerEnabled ?? false;
|
|
352
|
+
const realtimeEnabled = config.realtimeEnabled ?? true;
|
|
353
|
+
if (listenerEnabled && !realtimeEnabled) {
|
|
354
|
+
throw new TypeError('awiki: listenerEnabled requires realtimeEnabled');
|
|
355
|
+
}
|
|
286
356
|
const listenerWorkspacePath = config.listenerWorkspacePath?.trim() || join(dshHome, 'workspaces', 'awiki');
|
|
287
357
|
if (!isAbsolute(listenerWorkspacePath)) {
|
|
288
358
|
throw new TypeError('awiki: listenerWorkspacePath must be an absolute path');
|
|
@@ -296,10 +366,16 @@ function resolveConfig(ctx, config) {
|
|
|
296
366
|
const messageServiceUrl = serviceUrl('messageServiceUrl', config.messageServiceUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
297
367
|
const mailServiceUrl = serviceUrl('mailServiceUrl', config.mailServiceUrl ?? userServiceUrl, allowInsecureLoopbackForTesting);
|
|
298
368
|
const messageServicePublicUrl = serviceUrl('messageServicePublicUrl', config.messageServicePublicUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
369
|
+
const legacyTenantSlot = config.legacyTenantSlot?.trim();
|
|
370
|
+
if (legacyTenantSlot !== undefined
|
|
371
|
+
&& legacyTenantSlot !== 'primary'
|
|
372
|
+
&& legacyTenantSlot !== 'secondary') {
|
|
373
|
+
throw new TypeError('awiki: legacyTenantSlot must be primary or secondary');
|
|
374
|
+
}
|
|
299
375
|
return {
|
|
300
|
-
tenantDomain: serviceDomain(config.userServiceDomain ?? DEFAULT_AWIKI_DOMAIN),
|
|
301
376
|
userServiceUrl,
|
|
302
377
|
userServiceDomain: serviceDomain(config.userServiceDomain ?? DEFAULT_AWIKI_DOMAIN),
|
|
378
|
+
...legacyTenantSlot === undefined ? {} : { legacyTenantSlot },
|
|
303
379
|
messageServiceUrl,
|
|
304
380
|
mailServiceUrl,
|
|
305
381
|
messageServicePublicUrl,
|
|
@@ -308,12 +384,9 @@ function resolveConfig(ctx, config) {
|
|
|
308
384
|
allowInsecureLoopbackForTesting,
|
|
309
385
|
stateRoot,
|
|
310
386
|
attachmentMaxBytes,
|
|
311
|
-
mailAttachmentMaxCount,
|
|
312
|
-
mailAttachmentMaxBytes,
|
|
313
|
-
mailAttachmentTotalMaxBytes,
|
|
314
387
|
imageAttachmentCacheMaxBytes,
|
|
315
388
|
pollIntervalMs,
|
|
316
|
-
|
|
389
|
+
realtimeEnabled,
|
|
317
390
|
listenerEnabled,
|
|
318
391
|
listener: {
|
|
319
392
|
allowedPeers,
|
|
@@ -321,48 +394,21 @@ function resolveConfig(ctx, config) {
|
|
|
321
394
|
stateRoot,
|
|
322
395
|
},
|
|
323
396
|
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
397
|
};
|
|
361
398
|
}
|
|
362
399
|
/** Return a public, fixed-message failure without retaining a thrown value. */
|
|
363
400
|
function failure(code) {
|
|
364
401
|
return { code, message: FAILURE_MESSAGES[code] };
|
|
365
402
|
}
|
|
403
|
+
function integrationUnavailable() {
|
|
404
|
+
return {
|
|
405
|
+
ok: false,
|
|
406
|
+
error: {
|
|
407
|
+
code: 'unavailable',
|
|
408
|
+
message: 'Guest messaging is not available for the active AWiki tenant.',
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
}
|
|
366
412
|
/** Normalize SDK and provider failures without returning remote bodies, credentials, or causes. */
|
|
367
413
|
function normalizeFailure(error) {
|
|
368
414
|
if (error instanceof ProviderUnavailableError) {
|
|
@@ -428,38 +474,6 @@ function normalizeGroupDid(value) {
|
|
|
428
474
|
return undefined;
|
|
429
475
|
return value;
|
|
430
476
|
}
|
|
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
477
|
function normalizeProfileRequest(request) {
|
|
464
478
|
if (typeof request?.displayName !== 'string' || typeof request.bio !== 'string' || !Array.isArray(request.tags))
|
|
465
479
|
return undefined;
|
|
@@ -510,6 +524,14 @@ function normalizeRecoveryPrepareRequest(request) {
|
|
|
510
524
|
return { ...operation, phone, otp };
|
|
511
525
|
}
|
|
512
526
|
const IDENTITY_ACCESS_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
527
|
+
const DID_URI = /^did:[a-z0-9]+:(?:[A-Za-z0-9._-]|%[0-9A-Fa-f]{2})+(?::(?:[A-Za-z0-9._-]|%[0-9A-Fa-f]{2})+)*$/u;
|
|
528
|
+
const DID_URI_MAX_CHARACTERS = 2_048;
|
|
529
|
+
const SERVER_INFO_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
530
|
+
function isBoundedDidUri(value) {
|
|
531
|
+
return typeof value === 'string'
|
|
532
|
+
&& value.length <= DID_URI_MAX_CHARACTERS
|
|
533
|
+
&& DID_URI.test(value);
|
|
534
|
+
}
|
|
513
535
|
/** Read one untrusted discovery response without buffering beyond the fixed Host limit. */
|
|
514
536
|
async function readBoundedResponseText(response, maxBytes) {
|
|
515
537
|
const declaredLength = response.headers.get('content-length');
|
|
@@ -559,7 +581,7 @@ function identityAccessTarget(request, configuredDomain) {
|
|
|
559
581
|
const dot = handle.indexOf('.');
|
|
560
582
|
const localPart = (dot < 0 ? handle : handle.slice(0, dot)).trim();
|
|
561
583
|
const domain = (dot < 0 ? configuredDomain : handle.slice(dot + 1)).trim().replace(/\.$/u, '');
|
|
562
|
-
if (localPart
|
|
584
|
+
if (registrationHandleLocalPart(localPart) !== localPart || domain !== configuredDomain)
|
|
563
585
|
return undefined;
|
|
564
586
|
return { localPart, fullHandle: `${localPart}.${configuredDomain}` };
|
|
565
587
|
}
|
|
@@ -716,7 +738,7 @@ function decodeAttachment(bytesBase64, maxBytes) {
|
|
|
716
738
|
const maxEncoded = Math.ceil(maxBytes / 3) * 4;
|
|
717
739
|
if (bytesBase64.length > maxEncoded)
|
|
718
740
|
return { ok: false, error: failure('attachment-too-large') };
|
|
719
|
-
if (bytesBase64.length % 4 !== 0 ||
|
|
741
|
+
if (bytesBase64.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(bytesBase64)) {
|
|
720
742
|
return { ok: false, error: failure('invalid-request') };
|
|
721
743
|
}
|
|
722
744
|
const bytes = Uint8Array.from(Buffer.from(bytesBase64, 'base64'));
|
|
@@ -731,6 +753,12 @@ let AwikiService = (() => {
|
|
|
731
753
|
let _classSuper = TypertRemoteService;
|
|
732
754
|
let _instanceExtraInitializers = [];
|
|
733
755
|
let _getConfig_decorators;
|
|
756
|
+
let _getIntegration_decorators;
|
|
757
|
+
let _createIntegration_decorators;
|
|
758
|
+
let _updateIntegration_decorators;
|
|
759
|
+
let _rotateIntegrationId_decorators;
|
|
760
|
+
let _closeIntegration_decorators;
|
|
761
|
+
let _reopenIntegration_decorators;
|
|
734
762
|
let _getIdentity_decorators;
|
|
735
763
|
let _getSession_decorators;
|
|
736
764
|
let _logout_decorators;
|
|
@@ -738,6 +766,18 @@ let AwikiService = (() => {
|
|
|
738
766
|
let _inspectIdentityAccess_decorators;
|
|
739
767
|
let _sendRegistrationOtp_decorators;
|
|
740
768
|
let _registerIdentity_decorators;
|
|
769
|
+
let _retireDeviceIdentityForRejoin_decorators;
|
|
770
|
+
let _beginDeviceJoin_decorators;
|
|
771
|
+
let _getIdentityAccessState_decorators;
|
|
772
|
+
let _getDeviceJoinStatus_decorators;
|
|
773
|
+
let _cancelDeviceJoin_decorators;
|
|
774
|
+
let _refreshDeviceManagement_decorators;
|
|
775
|
+
let _startDeviceJoinVerification_decorators;
|
|
776
|
+
let _approveDeviceJoin_decorators;
|
|
777
|
+
let _rejectDeviceJoin_decorators;
|
|
778
|
+
let _revokeDevice_decorators;
|
|
779
|
+
let _prepareRootTransfer_decorators;
|
|
780
|
+
let _confirmRootTransfer_decorators;
|
|
741
781
|
let _updateDisplayName_decorators;
|
|
742
782
|
let _getProfile_decorators;
|
|
743
783
|
let _updateProfile_decorators;
|
|
@@ -746,16 +786,17 @@ let AwikiService = (() => {
|
|
|
746
786
|
let _activateRecovery_decorators;
|
|
747
787
|
let _getRecoveryStatus_decorators;
|
|
748
788
|
let _resumeRecovery_decorators;
|
|
789
|
+
let _enterRecoveredSession_decorators;
|
|
749
790
|
let _discardRecovery_decorators;
|
|
750
791
|
let _resolvePeer_decorators;
|
|
751
792
|
let _createGroup_decorators;
|
|
752
793
|
let _getGroup_decorators;
|
|
753
794
|
let _joinGroup_decorators;
|
|
754
795
|
let _leaveGroup_decorators;
|
|
796
|
+
let _getDisplayProfiles_decorators;
|
|
755
797
|
let _listGroupMembers_decorators;
|
|
756
798
|
let _addGroupMember_decorators;
|
|
757
799
|
let _removeGroupMember_decorators;
|
|
758
|
-
let _resumeGroupRebindRecovery_decorators;
|
|
759
800
|
let _getConversationPreferences_decorators;
|
|
760
801
|
let _updateConversationPreference_decorators;
|
|
761
802
|
let _listConversations_decorators;
|
|
@@ -771,12 +812,17 @@ let AwikiService = (() => {
|
|
|
771
812
|
let _readMail_decorators;
|
|
772
813
|
let _markMailRead_decorators;
|
|
773
814
|
let _sendMail_decorators;
|
|
774
|
-
let _downloadMailAttachment_decorators;
|
|
775
815
|
let _clearLocalData_decorators;
|
|
776
816
|
return class AwikiService extends _classSuper {
|
|
777
817
|
static {
|
|
778
818
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
779
819
|
_getConfig_decorators = [Remote];
|
|
820
|
+
_getIntegration_decorators = [Remote];
|
|
821
|
+
_createIntegration_decorators = [Remote];
|
|
822
|
+
_updateIntegration_decorators = [Remote];
|
|
823
|
+
_rotateIntegrationId_decorators = [Remote];
|
|
824
|
+
_closeIntegration_decorators = [Remote];
|
|
825
|
+
_reopenIntegration_decorators = [Remote];
|
|
780
826
|
_getIdentity_decorators = [Remote];
|
|
781
827
|
_getSession_decorators = [Remote];
|
|
782
828
|
_logout_decorators = [Remote];
|
|
@@ -784,6 +830,18 @@ let AwikiService = (() => {
|
|
|
784
830
|
_inspectIdentityAccess_decorators = [Remote];
|
|
785
831
|
_sendRegistrationOtp_decorators = [Remote];
|
|
786
832
|
_registerIdentity_decorators = [Remote];
|
|
833
|
+
_retireDeviceIdentityForRejoin_decorators = [Remote];
|
|
834
|
+
_beginDeviceJoin_decorators = [Remote];
|
|
835
|
+
_getIdentityAccessState_decorators = [Remote];
|
|
836
|
+
_getDeviceJoinStatus_decorators = [Remote];
|
|
837
|
+
_cancelDeviceJoin_decorators = [Remote];
|
|
838
|
+
_refreshDeviceManagement_decorators = [Remote];
|
|
839
|
+
_startDeviceJoinVerification_decorators = [Remote];
|
|
840
|
+
_approveDeviceJoin_decorators = [Remote];
|
|
841
|
+
_rejectDeviceJoin_decorators = [Remote];
|
|
842
|
+
_revokeDevice_decorators = [Remote];
|
|
843
|
+
_prepareRootTransfer_decorators = [Remote];
|
|
844
|
+
_confirmRootTransfer_decorators = [Remote];
|
|
787
845
|
_updateDisplayName_decorators = [Remote];
|
|
788
846
|
_getProfile_decorators = [Remote];
|
|
789
847
|
_updateProfile_decorators = [Remote];
|
|
@@ -792,16 +850,17 @@ let AwikiService = (() => {
|
|
|
792
850
|
_activateRecovery_decorators = [Remote];
|
|
793
851
|
_getRecoveryStatus_decorators = [Remote];
|
|
794
852
|
_resumeRecovery_decorators = [Remote];
|
|
853
|
+
_enterRecoveredSession_decorators = [Remote];
|
|
795
854
|
_discardRecovery_decorators = [Remote];
|
|
796
855
|
_resolvePeer_decorators = [Remote];
|
|
797
856
|
_createGroup_decorators = [Remote];
|
|
798
857
|
_getGroup_decorators = [Remote];
|
|
799
858
|
_joinGroup_decorators = [Remote];
|
|
800
859
|
_leaveGroup_decorators = [Remote];
|
|
860
|
+
_getDisplayProfiles_decorators = [Remote];
|
|
801
861
|
_listGroupMembers_decorators = [Remote];
|
|
802
862
|
_addGroupMember_decorators = [Remote];
|
|
803
863
|
_removeGroupMember_decorators = [Remote];
|
|
804
|
-
_resumeGroupRebindRecovery_decorators = [Remote];
|
|
805
864
|
_getConversationPreferences_decorators = [Remote];
|
|
806
865
|
_updateConversationPreference_decorators = [Remote];
|
|
807
866
|
_listConversations_decorators = [Remote];
|
|
@@ -817,9 +876,14 @@ let AwikiService = (() => {
|
|
|
817
876
|
_readMail_decorators = [Remote];
|
|
818
877
|
_markMailRead_decorators = [Remote];
|
|
819
878
|
_sendMail_decorators = [Remote];
|
|
820
|
-
_downloadMailAttachment_decorators = [Remote];
|
|
821
879
|
_clearLocalData_decorators = [Remote];
|
|
822
880
|
__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);
|
|
881
|
+
__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);
|
|
882
|
+
__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);
|
|
883
|
+
__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);
|
|
884
|
+
__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);
|
|
885
|
+
__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);
|
|
886
|
+
__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
887
|
__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
888
|
__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
889
|
__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 +891,18 @@ let AwikiService = (() => {
|
|
|
827
891
|
__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
892
|
__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
893
|
__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);
|
|
894
|
+
__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);
|
|
895
|
+
__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);
|
|
896
|
+
__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);
|
|
897
|
+
__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);
|
|
898
|
+
__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);
|
|
899
|
+
__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);
|
|
900
|
+
__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);
|
|
901
|
+
__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);
|
|
902
|
+
__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);
|
|
903
|
+
__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);
|
|
904
|
+
__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);
|
|
905
|
+
__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
906
|
__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
907
|
__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
908
|
__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 +911,17 @@ let AwikiService = (() => {
|
|
|
835
911
|
__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
912
|
__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
913
|
__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);
|
|
914
|
+
__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
915
|
__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
916
|
__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
917
|
__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
918
|
__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
919
|
__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
920
|
__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);
|
|
921
|
+
__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
922
|
__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
923
|
__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
924
|
__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
925
|
__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
926
|
__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
927
|
__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 +937,49 @@ let AwikiService = (() => {
|
|
|
860
937
|
__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
938
|
__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
939
|
__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
940
|
__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
941
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
866
942
|
}
|
|
867
|
-
static inject = ['tools'
|
|
943
|
+
static inject = ['tools'];
|
|
868
944
|
static Config = Config;
|
|
869
945
|
resolved = __runInitializers(this, _instanceExtraInitializers);
|
|
870
|
-
|
|
871
|
-
|
|
946
|
+
sessionStore;
|
|
947
|
+
imageAttachmentCache;
|
|
948
|
+
conversationPreferenceStore;
|
|
872
949
|
startupUserServiceDomain;
|
|
873
950
|
settingsProvider;
|
|
951
|
+
tenantRegistry;
|
|
952
|
+
activeTenant;
|
|
953
|
+
activeClientOptions;
|
|
954
|
+
clientFactory;
|
|
874
955
|
provider;
|
|
956
|
+
runtimeGeneration = 0;
|
|
957
|
+
tenantSwitching = false;
|
|
958
|
+
tenantParticipants = new Set();
|
|
959
|
+
activeCapabilities;
|
|
960
|
+
updatePolicyStatus;
|
|
961
|
+
updatePolicyRequest;
|
|
962
|
+
updatePolicyTask;
|
|
963
|
+
updatePolicyTaskKey;
|
|
964
|
+
desktopDistribution;
|
|
875
965
|
signedOut;
|
|
876
966
|
sessionMutation = Promise.resolve();
|
|
877
967
|
sessionRevision = 0;
|
|
878
968
|
activeIdentityDid;
|
|
969
|
+
pendingDeviceJoin;
|
|
970
|
+
activeDeviceJoinSessionId;
|
|
971
|
+
requestRefs = new Map();
|
|
972
|
+
requestSessions = new Map();
|
|
973
|
+
deviceRefs = new Map();
|
|
974
|
+
deviceIds = new Map();
|
|
975
|
+
rootTransfers = new Map();
|
|
879
976
|
activeSummaryRequests = new Set();
|
|
880
977
|
summaryProvider;
|
|
881
|
-
recoveryReconciliationTarget;
|
|
882
978
|
hostContext;
|
|
883
979
|
/** Trusted same-process external HTTP authentication dispatcher. Never Remote. */
|
|
884
980
|
externalHttpAuth;
|
|
981
|
+
integrationClient;
|
|
982
|
+
mailListClient;
|
|
885
983
|
workspaceContext;
|
|
886
984
|
/**
|
|
887
985
|
* @param ctx - owning Host context.
|
|
@@ -893,39 +991,72 @@ let AwikiService = (() => {
|
|
|
893
991
|
this.resolved = resolveConfig(ctx, config);
|
|
894
992
|
this.externalHttpAuth = createAwikiExternalHttpAuth(() => this.acquireExternalHttpAuthSession());
|
|
895
993
|
this.startupUserServiceDomain = this.resolved.userServiceDomain;
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
994
|
+
ctx.inject(['settings'], (settingsCtx) => {
|
|
995
|
+
const provider = settingsCtx.settings;
|
|
996
|
+
const settingsScope = settingsCtx.settings.register(AWIKI_SETTINGS_NAMESPACE, AwikiSettingsSchema, {
|
|
997
|
+
base: { domain: this.resolved.userServiceDomain },
|
|
998
|
+
applies: 'restart',
|
|
999
|
+
validate: validateAwikiSettings,
|
|
1000
|
+
});
|
|
1001
|
+
this.settingsProvider = provider;
|
|
1002
|
+
this.startupUserServiceDomain = settingsScope.get().domain;
|
|
1003
|
+
settingsCtx.effect(() => () => {
|
|
1004
|
+
if (this.settingsProvider === provider) {
|
|
1005
|
+
this.settingsProvider = undefined;
|
|
1006
|
+
this.startupUserServiceDomain = this.resolved.userServiceDomain;
|
|
1007
|
+
}
|
|
1008
|
+
}, 'awiki: release settings namespace');
|
|
901
1009
|
});
|
|
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
1010
|
ctx.inject(['connection'], (connectionCtx) => {
|
|
911
|
-
connectionCtx.connection
|
|
1011
|
+
registerAwikiSettingsTransport(connectionCtx.connection, createAwikiSettingsRpcHandler(() => this.settingsProvider, {
|
|
1012
|
+
describe: () => this.getTenantRegistryView(),
|
|
1013
|
+
create: (displayName, domain) => this.createCustomTenant(displayName, domain),
|
|
1014
|
+
rename: (tenantId, displayName) => this.renameCustomTenant(tenantId, displayName),
|
|
1015
|
+
switch: tenantId => this.switchTenant(tenantId),
|
|
1016
|
+
archive: tenantId => this.archiveCustomTenant(tenantId),
|
|
1017
|
+
describeUpdate: () => this.getUpdatePolicyStatus(),
|
|
1018
|
+
refreshUpdate: () => this.refreshUpdatePolicy(),
|
|
1019
|
+
describeDesktopUpdate: () => this.getDesktopUpdate(),
|
|
1020
|
+
refreshDesktopUpdate: () => this.refreshDesktopUpdate(),
|
|
1021
|
+
}));
|
|
912
1022
|
});
|
|
913
1023
|
ctx.inject(['workspaceRegistry'], (workspaceCtx) => {
|
|
914
1024
|
this.workspaceContext = workspaceCtx;
|
|
915
1025
|
const provider = this.provider;
|
|
916
1026
|
if (provider !== undefined)
|
|
917
|
-
|
|
1027
|
+
this.ensureAgentConsumer(provider);
|
|
918
1028
|
workspaceCtx.effect(() => async () => {
|
|
919
1029
|
if (this.workspaceContext !== workspaceCtx)
|
|
920
1030
|
return;
|
|
921
1031
|
this.workspaceContext = undefined;
|
|
922
1032
|
const current = this.provider;
|
|
923
1033
|
if (current !== undefined)
|
|
924
|
-
await this.
|
|
1034
|
+
await this.stopAgentConsumer(current);
|
|
925
1035
|
}, 'awiki: release Workspace listener composition');
|
|
926
1036
|
});
|
|
1037
|
+
ctx.inject(['desktopDistribution'], desktopCtx => {
|
|
1038
|
+
const service = desktopCtx.get('desktopDistribution');
|
|
1039
|
+
if (typeof service !== 'object' || service === null
|
|
1040
|
+
|| !('getSnapshot' in service) || typeof service.getSnapshot !== 'function'
|
|
1041
|
+
|| !('check' in service) || typeof service.check !== 'function')
|
|
1042
|
+
return;
|
|
1043
|
+
const candidate = service;
|
|
1044
|
+
if (decodeDesktopDistribution(candidate.getSnapshot()) === undefined)
|
|
1045
|
+
return;
|
|
1046
|
+
this.desktopDistribution = candidate;
|
|
1047
|
+
desktopCtx.effect(() => () => {
|
|
1048
|
+
if (this.desktopDistribution === candidate)
|
|
1049
|
+
this.desktopDistribution = undefined;
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
ctx.effect(() => {
|
|
1053
|
+
const timer = setInterval(() => { void this.refreshUpdatePolicy().catch(() => undefined); }, 6 * 60 * 60 * 1000);
|
|
1054
|
+
timer.unref();
|
|
1055
|
+
return () => { clearInterval(timer); };
|
|
1056
|
+
});
|
|
927
1057
|
registerAwikiTools(ctx, this);
|
|
928
1058
|
ctx.effect(() => async () => {
|
|
1059
|
+
this.updatePolicyRequest?.abort();
|
|
929
1060
|
const provider = this.provider;
|
|
930
1061
|
if (provider !== undefined)
|
|
931
1062
|
await this.disposeProvider(provider);
|
|
@@ -942,36 +1073,351 @@ let AwikiService = (() => {
|
|
|
942
1073
|
* @returns asynchronous disposer for the exact registered client.
|
|
943
1074
|
*/
|
|
944
1075
|
registerClientFactory(factory) {
|
|
945
|
-
if (this.provider !== undefined)
|
|
1076
|
+
if (this.clientFactory !== undefined || this.provider !== undefined) {
|
|
946
1077
|
throw new Error('awiki: a client provider is already registered');
|
|
947
|
-
|
|
1078
|
+
}
|
|
1079
|
+
this.clientFactory = factory;
|
|
1080
|
+
try {
|
|
1081
|
+
this.openTenantProvider(this.ensureTenantRegistry().active(), factory);
|
|
1082
|
+
}
|
|
1083
|
+
catch (error) {
|
|
1084
|
+
this.clientFactory = undefined;
|
|
1085
|
+
throw error;
|
|
1086
|
+
}
|
|
1087
|
+
let active = true;
|
|
1088
|
+
return async () => {
|
|
1089
|
+
if (!active)
|
|
1090
|
+
return;
|
|
1091
|
+
active = false;
|
|
1092
|
+
this.clientFactory = undefined;
|
|
1093
|
+
const current = this.provider;
|
|
1094
|
+
if (current !== undefined)
|
|
1095
|
+
await this.disposeProvider(current);
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
ensureTenantRegistry() {
|
|
1099
|
+
if (this.tenantRegistry !== undefined)
|
|
1100
|
+
return this.tenantRegistry;
|
|
1101
|
+
const domain = this.startupUserServiceDomain;
|
|
1102
|
+
const configuredOrigin = `https://${this.resolved.userServiceDomain}`;
|
|
1103
|
+
const tenantOrigin = `https://${domain}`;
|
|
1104
|
+
const derive = (value) => value.replace(/\/$/u, '') === configuredOrigin ? tenantOrigin : value;
|
|
1105
|
+
const seed = {
|
|
1106
|
+
domain,
|
|
1107
|
+
configured: domain !== DEFAULT_AWIKI_DOMAIN
|
|
1108
|
+
|| this.resolved.userServiceUrl.replace(/\/$/u, '') !== configuredOrigin
|
|
1109
|
+
|| this.resolved.messageServiceUrl.replace(/\/$/u, '') !== configuredOrigin,
|
|
1110
|
+
...this.resolved.legacyTenantSlot === undefined
|
|
1111
|
+
? {}
|
|
1112
|
+
: { legacyTenantSlot: this.resolved.legacyTenantSlot },
|
|
1113
|
+
userServiceUrl: derive(this.resolved.userServiceUrl),
|
|
1114
|
+
messageServiceUrl: derive(this.resolved.messageServiceUrl),
|
|
1115
|
+
mailServiceUrl: derive(this.resolved.mailServiceUrl),
|
|
1116
|
+
messageServicePublicUrl: derive(this.resolved.messageServicePublicUrl),
|
|
1117
|
+
messageServiceDid: this.resolved.messageServiceDid === `did:wba:${this.resolved.userServiceDomain}`
|
|
1118
|
+
? `did:wba:${domain}`
|
|
1119
|
+
: this.resolved.messageServiceDid,
|
|
1120
|
+
};
|
|
1121
|
+
this.tenantRegistry = AwikiTenantRegistry.open(this.resolved.stateRoot, seed);
|
|
1122
|
+
return this.tenantRegistry;
|
|
1123
|
+
}
|
|
1124
|
+
optionsForTenant(tenant) {
|
|
1125
|
+
const endpoints = tenant.endpoints;
|
|
1126
|
+
const messageServicePublicOrigin = new URL(endpoints.messageServicePublicUrl).origin;
|
|
1127
|
+
return {
|
|
1128
|
+
...endpoints,
|
|
1129
|
+
userServiceDomain: tenant.didHost,
|
|
1130
|
+
allowedAttachmentOrigins: tenant.storageLayout === 'legacy-base'
|
|
1131
|
+
? this.resolved.allowedAttachmentOrigins
|
|
1132
|
+
: [messageServicePublicOrigin],
|
|
1133
|
+
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
1134
|
+
allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
|
|
1135
|
+
stateRoot: this.ensureTenantRegistry().stateRoot(tenant),
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
ensureTenantOptions() {
|
|
1139
|
+
if (this.activeClientOptions !== undefined)
|
|
1140
|
+
return this.activeClientOptions;
|
|
1141
|
+
const tenant = this.ensureTenantRegistry().active();
|
|
1142
|
+
const options = this.optionsForTenant(tenant);
|
|
1143
|
+
this.bindTenantState(tenant, options);
|
|
1144
|
+
return options;
|
|
1145
|
+
}
|
|
1146
|
+
bindTenantState(tenant, options) {
|
|
1147
|
+
this.updatePolicyRequest?.abort();
|
|
1148
|
+
this.updatePolicyStatus = undefined;
|
|
1149
|
+
this.updatePolicyTask = undefined;
|
|
1150
|
+
this.activeTenant = tenant;
|
|
1151
|
+
this.activeClientOptions = options;
|
|
1152
|
+
this.startupUserServiceDomain = tenant.didHost;
|
|
1153
|
+
this.sessionStore = new AwikiSessionStore(options.stateRoot);
|
|
1154
|
+
this.imageAttachmentCache = new AwikiImageAttachmentCache(options.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes);
|
|
1155
|
+
this.conversationPreferenceStore = new AwikiConversationPreferenceStore(options.stateRoot);
|
|
1156
|
+
this.mailListClient = new AwikiMailListClient(options.mailServiceUrl, this.externalHttpAuth);
|
|
1157
|
+
this.integrationClient = undefined;
|
|
1158
|
+
this.activeCapabilities = {
|
|
1159
|
+
tenantId: tenant.tenantId,
|
|
1160
|
+
generation: this.runtimeGeneration,
|
|
1161
|
+
online: false,
|
|
1162
|
+
handleRecoveryPhoneEnabled: false,
|
|
1163
|
+
};
|
|
1164
|
+
this.signedOut = undefined;
|
|
1165
|
+
this.activeIdentityDid = undefined;
|
|
1166
|
+
this.pendingDeviceJoin = undefined;
|
|
1167
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
1168
|
+
}
|
|
1169
|
+
openTenantProvider(tenant, factory, startRuntime = true) {
|
|
1170
|
+
const options = this.optionsForTenant(tenant);
|
|
1171
|
+
const client = factory(options);
|
|
948
1172
|
const provider = {
|
|
949
1173
|
client,
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1174
|
+
realtimeGeneration: 0,
|
|
1175
|
+
agentConsumerGeneration: 0,
|
|
1176
|
+
localDeviceJoinRequestCountAfterSync: 0,
|
|
953
1177
|
};
|
|
1178
|
+
this.bindTenantState(tenant, options);
|
|
954
1179
|
this.provider = provider;
|
|
955
|
-
void this.
|
|
956
|
-
|
|
1180
|
+
void this.refreshUpdatePolicy().catch(() => undefined);
|
|
1181
|
+
if (startRuntime)
|
|
1182
|
+
this.ensureProviderRuntime(provider);
|
|
1183
|
+
return provider;
|
|
1184
|
+
}
|
|
1185
|
+
/** Read the Host-owned catalog for trusted loopback settings surfaces. */
|
|
1186
|
+
getTenantRegistryView() {
|
|
1187
|
+
return this.ensureTenantRegistry().snapshot(this.tenantSwitching);
|
|
1188
|
+
}
|
|
1189
|
+
/** Browser-safe, same-process update state for Desktop and loopback settings. */
|
|
1190
|
+
getUpdatePolicyStatus() {
|
|
1191
|
+
const tenant = this.activeTenant ?? this.ensureTenantRegistry().active();
|
|
1192
|
+
const modelProxyVersion = this.currentModelProxyVersion();
|
|
1193
|
+
return this.updatePolicyStatus ?? readAwikiUpdatePolicyStatus({
|
|
1194
|
+
tenant, generation: this.runtimeGeneration, stateRoot: this.resolved.stateRoot,
|
|
1195
|
+
currentPluginVersion: DSH_AWIKI_VERSION,
|
|
1196
|
+
...modelProxyVersion === undefined ? {} : { currentModelProxyVersion: modelProxyVersion },
|
|
1197
|
+
});
|
|
957
1198
|
}
|
|
958
|
-
/**
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
const
|
|
964
|
-
|
|
1199
|
+
/** Only the Desktop provider owns its release source; tenants cannot override it. */
|
|
1200
|
+
getDesktopUpdate() {
|
|
1201
|
+
return decodeDesktopDistribution(this.desktopDistribution?.getSnapshot());
|
|
1202
|
+
}
|
|
1203
|
+
async refreshDesktopUpdate() {
|
|
1204
|
+
const provider = this.desktopDistribution;
|
|
1205
|
+
if (provider === undefined)
|
|
1206
|
+
return undefined;
|
|
1207
|
+
const value = await provider.check();
|
|
1208
|
+
return provider === this.desktopDistribution ? decodeDesktopDistribution(value) : this.getDesktopUpdate();
|
|
1209
|
+
}
|
|
1210
|
+
/** Refresh only the active generation; late results from old tenants are discarded. */
|
|
1211
|
+
refreshUpdatePolicy() {
|
|
1212
|
+
const key = JSON.stringify([this.runtimeGeneration, this.currentModelProxyVersion()]);
|
|
1213
|
+
if (this.updatePolicyTask !== undefined && this.updatePolicyTaskKey === key)
|
|
1214
|
+
return this.updatePolicyTask;
|
|
1215
|
+
const task = this.performUpdatePolicyRefresh().finally(() => {
|
|
1216
|
+
if (this.updatePolicyTask === task)
|
|
1217
|
+
this.updatePolicyTask = undefined;
|
|
1218
|
+
});
|
|
1219
|
+
this.updatePolicyTask = task;
|
|
1220
|
+
this.updatePolicyTaskKey = key;
|
|
1221
|
+
return task;
|
|
1222
|
+
}
|
|
1223
|
+
async performUpdatePolicyRefresh() {
|
|
1224
|
+
const tenant = this.activeTenant ?? this.ensureTenantRegistry().active();
|
|
1225
|
+
const generation = this.runtimeGeneration;
|
|
1226
|
+
this.updatePolicyRequest?.abort();
|
|
1227
|
+
const request = new AbortController();
|
|
1228
|
+
this.updatePolicyRequest = request;
|
|
1229
|
+
const currentModelProxyVersion = this.currentModelProxyVersion();
|
|
1230
|
+
const status = await checkAwikiUpdatePolicy({
|
|
1231
|
+
tenant,
|
|
1232
|
+
generation,
|
|
1233
|
+
stateRoot: this.resolved.stateRoot,
|
|
1234
|
+
currentPluginVersion: DSH_AWIKI_VERSION,
|
|
1235
|
+
...currentModelProxyVersion === undefined ? {} : { currentModelProxyVersion },
|
|
1236
|
+
allowInsecureLoopback: this.resolved.allowInsecureLoopbackForTesting,
|
|
1237
|
+
signal: request.signal,
|
|
965
1238
|
});
|
|
966
|
-
this.
|
|
1239
|
+
if (this.updatePolicyRequest === request
|
|
1240
|
+
&& this.runtimeGeneration === generation
|
|
1241
|
+
&& this.activeTenant?.tenantId === tenant.tenantId) {
|
|
1242
|
+
const wasRestricted = this.getUpdatePolicyStatus().restricted;
|
|
1243
|
+
this.updatePolicyStatus = status;
|
|
1244
|
+
this.hostContext.emit('awiki/update-policy', status);
|
|
1245
|
+
if (status.restricted) {
|
|
1246
|
+
this.invalidateSummaries();
|
|
1247
|
+
const provider = this.provider;
|
|
1248
|
+
if (provider !== undefined) {
|
|
1249
|
+
try {
|
|
1250
|
+
await this.stopProviderRuntime(provider);
|
|
1251
|
+
}
|
|
1252
|
+
catch (error) {
|
|
1253
|
+
this.ctx.logger('awiki-update').warn('AWiki restricted-version runtime shutdown failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
else if (wasRestricted && this.provider !== undefined && !this.tenantSwitching) {
|
|
1258
|
+
this.ensureProviderRuntime(this.provider);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
return this.getUpdatePolicyStatus();
|
|
1262
|
+
}
|
|
1263
|
+
currentModelProxyVersion() {
|
|
1264
|
+
for (const participant of this.tenantParticipants) {
|
|
1265
|
+
if (participant.component?.product === 'dsh-awiki-model-proxy')
|
|
1266
|
+
return participant.component.version;
|
|
1267
|
+
}
|
|
1268
|
+
return undefined;
|
|
1269
|
+
}
|
|
1270
|
+
createCustomTenant(displayName, domain) {
|
|
1271
|
+
this.assertVersionSupported();
|
|
1272
|
+
this.ensureTenantRegistry().createCustom(displayName, domain);
|
|
1273
|
+
return this.getTenantRegistryView();
|
|
1274
|
+
}
|
|
1275
|
+
renameCustomTenant(tenantId, displayName) {
|
|
1276
|
+
this.assertVersionSupported();
|
|
1277
|
+
this.ensureTenantRegistry().renameCustom(tenantId, displayName);
|
|
1278
|
+
return this.getTenantRegistryView();
|
|
1279
|
+
}
|
|
1280
|
+
archiveCustomTenant(tenantId) {
|
|
1281
|
+
this.assertVersionSupported();
|
|
1282
|
+
this.ensureTenantRegistry().archiveCustom(tenantId);
|
|
1283
|
+
return this.getTenantRegistryView();
|
|
1284
|
+
}
|
|
1285
|
+
/** Register one same-process resource owner in every transactional tenant change. */
|
|
1286
|
+
registerTenantLifecycleParticipant(participant) {
|
|
1287
|
+
if (this.tenantParticipants.has(participant))
|
|
1288
|
+
throw new Error('awiki: tenant lifecycle participant is already registered');
|
|
1289
|
+
this.tenantParticipants.add(participant);
|
|
1290
|
+
if (participant.component !== undefined)
|
|
1291
|
+
this.updatePolicyStatus = undefined;
|
|
1292
|
+
void this.refreshUpdatePolicy().catch(() => undefined);
|
|
967
1293
|
let active = true;
|
|
968
1294
|
return () => {
|
|
969
1295
|
if (!active)
|
|
970
1296
|
return;
|
|
971
1297
|
active = false;
|
|
972
|
-
|
|
973
|
-
|
|
1298
|
+
this.tenantParticipants.delete(participant);
|
|
1299
|
+
if (participant.component !== undefined)
|
|
1300
|
+
this.updatePolicyStatus = undefined;
|
|
1301
|
+
void this.refreshUpdatePolicy().catch(() => undefined);
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
/** Replace the complete Core runtime and commit the active tenant only after opening succeeds. */
|
|
1305
|
+
async switchTenant(tenantId) {
|
|
1306
|
+
if (this.tenantSwitching)
|
|
1307
|
+
throw new Error('awiki: a tenant switch is already in progress');
|
|
1308
|
+
const registry = this.ensureTenantRegistry();
|
|
1309
|
+
const previousTenant = registry.active();
|
|
1310
|
+
if (previousTenant.tenantId === tenantId)
|
|
1311
|
+
return this.getTenantRegistryView();
|
|
1312
|
+
const target = registry.find(tenantId);
|
|
1313
|
+
if (target === undefined || target.lifecycle === 'archived')
|
|
1314
|
+
throw new Error('awiki: target tenant is unavailable');
|
|
1315
|
+
const factory = this.clientFactory;
|
|
1316
|
+
const previousProvider = this.provider;
|
|
1317
|
+
if (factory === undefined || previousProvider === undefined)
|
|
1318
|
+
throw new Error('awiki: client provider is unavailable');
|
|
1319
|
+
this.tenantSwitching = true;
|
|
1320
|
+
this.updatePolicyRequest?.abort();
|
|
1321
|
+
this.updatePolicyTask = undefined;
|
|
1322
|
+
const context = {
|
|
1323
|
+
from: previousTenant,
|
|
1324
|
+
to: target,
|
|
1325
|
+
generation: ++this.runtimeGeneration,
|
|
1326
|
+
};
|
|
1327
|
+
const prepared = [];
|
|
1328
|
+
let targetOpened = false;
|
|
1329
|
+
let committed = false;
|
|
1330
|
+
try {
|
|
1331
|
+
await this.sessionMutation;
|
|
1332
|
+
this.invalidateSummaries();
|
|
1333
|
+
for (const participant of this.tenantParticipants) {
|
|
1334
|
+
await participant.prepareSwitch(context);
|
|
1335
|
+
prepared.push(participant);
|
|
974
1336
|
}
|
|
1337
|
+
await this.disposeProvider(previousProvider);
|
|
1338
|
+
const targetProvider = this.openTenantProvider(target, factory, false);
|
|
1339
|
+
targetOpened = true;
|
|
1340
|
+
// The factory returns an adapter immediately; its native Core opens asynchronously.
|
|
1341
|
+
// A local identity read proves both open and Registry loading before committing the switch.
|
|
1342
|
+
await targetProvider.client.getIdentity();
|
|
1343
|
+
await Promise.all([this.discoverTenantCapabilities(3_000), this.refreshUpdatePolicy()]);
|
|
1344
|
+
registry.commitActive(target.tenantId);
|
|
1345
|
+
committed = true;
|
|
1346
|
+
for (const participant of prepared)
|
|
1347
|
+
await participant.commitSwitch?.(context);
|
|
1348
|
+
this.tenantSwitching = false;
|
|
1349
|
+
this.ensureProviderRuntime(targetProvider);
|
|
1350
|
+
const view = this.getTenantRegistryView();
|
|
1351
|
+
this.hostContext.emit('awiki/tenant', view);
|
|
1352
|
+
return view;
|
|
1353
|
+
}
|
|
1354
|
+
catch (error) {
|
|
1355
|
+
const rollbackFailures = [];
|
|
1356
|
+
if (targetOpened) {
|
|
1357
|
+
const current = this.provider;
|
|
1358
|
+
if (current !== undefined) {
|
|
1359
|
+
try {
|
|
1360
|
+
await this.disposeProvider(current);
|
|
1361
|
+
}
|
|
1362
|
+
catch (rollbackError) {
|
|
1363
|
+
rollbackFailures.push(rollbackError);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
if (committed) {
|
|
1368
|
+
try {
|
|
1369
|
+
registry.commitActive(previousTenant.tenantId);
|
|
1370
|
+
}
|
|
1371
|
+
catch (rollbackError) {
|
|
1372
|
+
rollbackFailures.push(rollbackError);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
if (this.provider === undefined) {
|
|
1376
|
+
try {
|
|
1377
|
+
const restored = this.openTenantProvider(previousTenant, factory, false);
|
|
1378
|
+
await restored.client.getIdentity();
|
|
1379
|
+
this.ensureProviderRuntime(restored);
|
|
1380
|
+
}
|
|
1381
|
+
catch (rollbackError) {
|
|
1382
|
+
rollbackFailures.push(rollbackError);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
for (const participant of prepared.reverse()) {
|
|
1386
|
+
try {
|
|
1387
|
+
await participant.rollbackSwitch?.(context);
|
|
1388
|
+
}
|
|
1389
|
+
catch (rollbackError) {
|
|
1390
|
+
rollbackFailures.push(rollbackError);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (rollbackFailures.length > 0) {
|
|
1394
|
+
throw new AggregateError([error, ...rollbackFailures], 'awiki: tenant switch failed and rollback was incomplete');
|
|
1395
|
+
}
|
|
1396
|
+
this.tenantSwitching = false;
|
|
1397
|
+
if (this.provider !== undefined)
|
|
1398
|
+
this.ensureProviderRuntime(this.provider);
|
|
1399
|
+
throw new Error('awiki: tenant switch failed; the previous tenant was restored', { cause: error });
|
|
1400
|
+
}
|
|
1401
|
+
finally {
|
|
1402
|
+
this.tenantSwitching = false;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
/** Safe same-process diagnostics for focused E2E. Never exposed through Typert Remote. */
|
|
1406
|
+
getRealtimeDiagnostics() {
|
|
1407
|
+
const provider = this.provider;
|
|
1408
|
+
const realtime = provider?.realtimeSupervisor?.diagnostics() ?? {
|
|
1409
|
+
connected: false,
|
|
1410
|
+
activeSessionCount: 0,
|
|
1411
|
+
startCount: 0,
|
|
1412
|
+
stopCount: 0,
|
|
1413
|
+
maxActiveSessionCount: 0,
|
|
1414
|
+
generation: 0,
|
|
1415
|
+
retryCount: 0,
|
|
1416
|
+
lifecyclePhase: 'idle',
|
|
1417
|
+
};
|
|
1418
|
+
return {
|
|
1419
|
+
...realtime,
|
|
1420
|
+
localDeviceJoinRequestCountAfterSync: provider?.localDeviceJoinRequestCountAfterSync ?? 0,
|
|
975
1421
|
};
|
|
976
1422
|
}
|
|
977
1423
|
/** Register one replaceable conversation-summary provider for this deployment. */
|
|
@@ -992,58 +1438,145 @@ let AwikiService = (() => {
|
|
|
992
1438
|
* Read settings needed by the browser presentation.
|
|
993
1439
|
* @returns Browser-safe polling configuration without SDK endpoints or state paths.
|
|
994
1440
|
*/
|
|
995
|
-
getConfig() {
|
|
996
|
-
|
|
1441
|
+
async getConfig() {
|
|
1442
|
+
this.ensureTenantOptions();
|
|
1443
|
+
const capabilities = await this.discoverTenantCapabilities(10_000);
|
|
1444
|
+
if (this.tenantSwitching
|
|
1445
|
+
|| capabilities.generation !== this.runtimeGeneration
|
|
1446
|
+
|| capabilities.tenantId !== this.activeTenant?.tenantId) {
|
|
1447
|
+
return { ok: false, error: failure('conflict') };
|
|
1448
|
+
}
|
|
1449
|
+
return {
|
|
997
1450
|
ok: true,
|
|
998
1451
|
value: {
|
|
999
|
-
tenantDomain: this.activeTenant().domain,
|
|
1000
1452
|
pollIntervalMs: this.resolved.pollIntervalMs,
|
|
1001
1453
|
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1454
|
+
handleRecoveryPhoneEnabled: capabilities.handleRecoveryPhoneEnabled,
|
|
1455
|
+
...capabilities.guestGatewayBaseUrl === undefined
|
|
1456
|
+
? {}
|
|
1457
|
+
: { integrationGuideUrl: new URL('/guest/guide/integration', capabilities.guestGatewayBaseUrl).toString() },
|
|
1458
|
+
tenantId: capabilities.tenantId,
|
|
1459
|
+
tenantGeneration: capabilities.generation,
|
|
1460
|
+
tenantOnline: capabilities.online,
|
|
1461
|
+
services: {
|
|
1462
|
+
modelProxy: { enabled: capabilities.modelProxyBaseUrl !== undefined },
|
|
1463
|
+
guestGateway: { enabled: capabilities.guestGatewayBaseUrl !== undefined },
|
|
1008
1464
|
},
|
|
1009
1465
|
},
|
|
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;
|
|
1466
|
+
};
|
|
1041
1467
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1468
|
+
/** Trusted same-process capability binding for optional tenant participants. */
|
|
1469
|
+
getTenantCapabilities() {
|
|
1470
|
+
const capabilities = this.activeCapabilities;
|
|
1471
|
+
if (capabilities === undefined)
|
|
1472
|
+
throw new Error('awiki: tenant capabilities are unavailable');
|
|
1473
|
+
return { ...capabilities };
|
|
1474
|
+
}
|
|
1475
|
+
/** Refresh the active tenant's optional service advertisement without affecting Core availability. */
|
|
1476
|
+
refreshTenantCapabilities() {
|
|
1477
|
+
this.ensureTenantOptions();
|
|
1478
|
+
return this.discoverTenantCapabilities(10_000);
|
|
1479
|
+
}
|
|
1480
|
+
async discoverTenantCapabilities(timeoutMs) {
|
|
1481
|
+
const options = this.activeClientOptions;
|
|
1482
|
+
const tenant = this.activeTenant;
|
|
1483
|
+
if (options === undefined || tenant === undefined)
|
|
1484
|
+
throw new ProviderUnavailableError();
|
|
1485
|
+
const generation = this.runtimeGeneration;
|
|
1486
|
+
const fallback = {
|
|
1487
|
+
tenantId: tenant.tenantId,
|
|
1488
|
+
generation,
|
|
1489
|
+
online: false,
|
|
1490
|
+
handleRecoveryPhoneEnabled: false,
|
|
1491
|
+
};
|
|
1492
|
+
const publish = (capabilities) => {
|
|
1493
|
+
if (this.runtimeGeneration === generation && this.activeTenant?.tenantId === tenant.tenantId) {
|
|
1494
|
+
this.activeCapabilities = capabilities;
|
|
1495
|
+
this.integrationClient = capabilities.guestGatewayBaseUrl === undefined
|
|
1496
|
+
? undefined
|
|
1497
|
+
: new AwikiIntegrationClient(capabilities.guestGatewayBaseUrl, this.externalHttpAuth);
|
|
1498
|
+
}
|
|
1499
|
+
return capabilities;
|
|
1500
|
+
};
|
|
1501
|
+
const abort = new AbortController();
|
|
1502
|
+
const timeout = setTimeout(() => { abort.abort(); }, timeoutMs);
|
|
1503
|
+
try {
|
|
1504
|
+
const endpoint = new URL('/user-service/v1/server-info', options.userServiceUrl);
|
|
1505
|
+
endpoint.searchParams.set('client_platform', 'dsh');
|
|
1506
|
+
const response = await fetch(endpoint, {
|
|
1507
|
+
method: 'GET',
|
|
1508
|
+
headers: { accept: 'application/json', 'cache-control': 'no-store' },
|
|
1509
|
+
cache: 'no-store',
|
|
1510
|
+
redirect: 'error',
|
|
1511
|
+
signal: abort.signal,
|
|
1512
|
+
});
|
|
1513
|
+
if (!response.ok)
|
|
1514
|
+
return publish(fallback);
|
|
1515
|
+
const text = await readBoundedResponseText(response, SERVER_INFO_RESPONSE_MAX_BYTES);
|
|
1516
|
+
const value = text === undefined ? undefined : JSON.parse(text);
|
|
1517
|
+
if (typeof value !== 'object' || value === null || value.schema_version !== 1)
|
|
1518
|
+
return publish(fallback);
|
|
1519
|
+
const methods = value.identity?.handle_recovery?.methods;
|
|
1520
|
+
const handleRecoveryPhoneEnabled = Array.isArray(methods) && methods.some((method) => (typeof method === 'object' && method !== null
|
|
1521
|
+
&& method.id === 'phone'
|
|
1522
|
+
&& method.enabled === true
|
|
1523
|
+
&& method.verification?.required === true
|
|
1524
|
+
&& method.verification?.type === 'sms_otp'));
|
|
1525
|
+
const services = value.services;
|
|
1526
|
+
const modelProxyBaseUrl = publishedServiceBaseUrl(services, 'model_proxy', this.resolved.allowInsecureLoopbackForTesting);
|
|
1527
|
+
const guestGatewayBaseUrl = publishedServiceBaseUrl(services, 'guest_gateway', false);
|
|
1528
|
+
const capabilities = {
|
|
1529
|
+
tenantId: tenant.tenantId,
|
|
1530
|
+
generation,
|
|
1531
|
+
online: true,
|
|
1532
|
+
handleRecoveryPhoneEnabled,
|
|
1533
|
+
...modelProxyBaseUrl === undefined ? {} : { modelProxyBaseUrl },
|
|
1534
|
+
...guestGatewayBaseUrl === undefined ? {} : { guestGatewayBaseUrl },
|
|
1535
|
+
};
|
|
1536
|
+
return publish(capabilities);
|
|
1537
|
+
}
|
|
1538
|
+
catch {
|
|
1539
|
+
return publish(fallback);
|
|
1540
|
+
}
|
|
1541
|
+
finally {
|
|
1542
|
+
clearTimeout(timeout);
|
|
1543
|
+
}
|
|
1044
1544
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1545
|
+
/** Read the Integration owned by the active full Handle through the fixed Host client. */
|
|
1546
|
+
getIntegration() {
|
|
1547
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
1548
|
+
return Promise.resolve(integrationUnavailable());
|
|
1549
|
+
return this.integrationClient?.read() ?? Promise.resolve(integrationUnavailable());
|
|
1550
|
+
}
|
|
1551
|
+
/** Create the active full Handle's only Integration. */
|
|
1552
|
+
createIntegration(request) {
|
|
1553
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
1554
|
+
return Promise.resolve(integrationUnavailable());
|
|
1555
|
+
return this.integrationClient?.create(request) ?? Promise.resolve(integrationUnavailable());
|
|
1556
|
+
}
|
|
1557
|
+
/** Update editable Integration fields with optimistic concurrency. */
|
|
1558
|
+
updateIntegration(request) {
|
|
1559
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
1560
|
+
return Promise.resolve(integrationUnavailable());
|
|
1561
|
+
return this.integrationClient?.update(request) ?? Promise.resolve(integrationUnavailable());
|
|
1562
|
+
}
|
|
1563
|
+
/** Atomically replace the current public Integration id. */
|
|
1564
|
+
rotateIntegrationId(request) {
|
|
1565
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
1566
|
+
return Promise.resolve(integrationUnavailable());
|
|
1567
|
+
return this.integrationClient?.rotate(request) ?? Promise.resolve(integrationUnavailable());
|
|
1568
|
+
}
|
|
1569
|
+
/** Close the Integration and revoke its current public id. */
|
|
1570
|
+
closeIntegration(request) {
|
|
1571
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
1572
|
+
return Promise.resolve(integrationUnavailable());
|
|
1573
|
+
return this.integrationClient?.close(request) ?? Promise.resolve(integrationUnavailable());
|
|
1574
|
+
}
|
|
1575
|
+
/** Revalidate one closed Integration and issue a new public id. */
|
|
1576
|
+
reopenIntegration(request) {
|
|
1577
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
1578
|
+
return Promise.resolve(integrationUnavailable());
|
|
1579
|
+
return this.integrationClient?.reopen(request) ?? Promise.resolve(integrationUnavailable());
|
|
1047
1580
|
}
|
|
1048
1581
|
/**
|
|
1049
1582
|
* Read the deployment's identity status.
|
|
@@ -1065,6 +1598,9 @@ let AwikiService = (() => {
|
|
|
1065
1598
|
if (!identity.ok)
|
|
1066
1599
|
return identity;
|
|
1067
1600
|
this.activeIdentityDid = identity.value?.did;
|
|
1601
|
+
const provider = this.provider;
|
|
1602
|
+
if (identity.value !== null && provider !== undefined)
|
|
1603
|
+
this.ensureProviderRuntime(provider);
|
|
1068
1604
|
return identity.value === null
|
|
1069
1605
|
? { ok: true, value: { status: 'unregistered' } }
|
|
1070
1606
|
: { ok: true, value: { status: 'active', identity: identity.value } };
|
|
@@ -1074,6 +1610,8 @@ let AwikiService = (() => {
|
|
|
1074
1610
|
if (request?.confirmation !== AWIKI_LOGOUT_CONFIRMATION) {
|
|
1075
1611
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1076
1612
|
}
|
|
1613
|
+
// Fence pending post-Recovery work before the serialized sign-out reaches the head of the queue.
|
|
1614
|
+
this.sessionRevision += 1;
|
|
1077
1615
|
return this.mutateSession(async () => {
|
|
1078
1616
|
if (await this.isSignedOut())
|
|
1079
1617
|
return { ok: true, value: { status: 'signed-out' } };
|
|
@@ -1091,7 +1629,7 @@ let AwikiService = (() => {
|
|
|
1091
1629
|
this.publishSession(session);
|
|
1092
1630
|
const provider = this.provider;
|
|
1093
1631
|
if (provider !== undefined)
|
|
1094
|
-
await this.
|
|
1632
|
+
await this.stopProviderRuntime(provider);
|
|
1095
1633
|
return { ok: true, value: session };
|
|
1096
1634
|
}
|
|
1097
1635
|
catch {
|
|
@@ -1116,7 +1654,7 @@ let AwikiService = (() => {
|
|
|
1116
1654
|
this.publishSession(session);
|
|
1117
1655
|
const provider = this.provider;
|
|
1118
1656
|
if (provider !== undefined)
|
|
1119
|
-
|
|
1657
|
+
this.ensureProviderRuntime(provider);
|
|
1120
1658
|
return { ok: true, value: session };
|
|
1121
1659
|
}
|
|
1122
1660
|
catch {
|
|
@@ -1126,10 +1664,13 @@ let AwikiService = (() => {
|
|
|
1126
1664
|
}
|
|
1127
1665
|
/** Classify one configured-domain Handle before selecting the registration or recovery OTP purpose. */
|
|
1128
1666
|
async inspectIdentityAccess(request) {
|
|
1129
|
-
const
|
|
1667
|
+
const options = this.activeClientOptions;
|
|
1668
|
+
if (options === undefined)
|
|
1669
|
+
return { ok: false, error: normalizeFailure(new ProviderUnavailableError()) };
|
|
1670
|
+
const target = identityAccessTarget(request, options.userServiceDomain);
|
|
1130
1671
|
if (target === undefined)
|
|
1131
1672
|
return { ok: false, error: failure('invalid-request') };
|
|
1132
|
-
const endpoint = new URL(`/.well-known/handle/${encodeURIComponent(target.localPart)}`,
|
|
1673
|
+
const endpoint = new URL(`/.well-known/handle/${encodeURIComponent(target.localPart)}`, options.userServiceUrl);
|
|
1133
1674
|
const abort = new AbortController();
|
|
1134
1675
|
const timeout = setTimeout(() => { abort.abort(); }, 10_000);
|
|
1135
1676
|
let response;
|
|
@@ -1151,7 +1692,7 @@ let AwikiService = (() => {
|
|
|
1151
1692
|
if (response.status === 404) {
|
|
1152
1693
|
return { ok: true, value: { status: 'available', fullHandle: target.fullHandle } };
|
|
1153
1694
|
}
|
|
1154
|
-
if (!response.ok)
|
|
1695
|
+
if (!response.ok && response.status !== 410)
|
|
1155
1696
|
return { ok: false, error: failure('remote') };
|
|
1156
1697
|
try {
|
|
1157
1698
|
const text = await readBoundedResponseText(response, IDENTITY_ACCESS_RESPONSE_MAX_BYTES);
|
|
@@ -1162,10 +1703,10 @@ let AwikiService = (() => {
|
|
|
1162
1703
|
return { ok: false, error: failure('remote') };
|
|
1163
1704
|
const binding = value;
|
|
1164
1705
|
if (binding.handle !== target.fullHandle
|
|
1165
|
-
||
|
|
1166
|
-
|| !binding.did.startsWith('did:')
|
|
1706
|
+
|| !isBoundedDidUri(binding.did)
|
|
1167
1707
|
|| typeof binding.status !== 'string'
|
|
1168
|
-
|| binding.status.length === 0
|
|
1708
|
+
|| binding.status.length === 0
|
|
1709
|
+
|| (response.status === 410 && binding.status !== 'revoked')) {
|
|
1169
1710
|
return { ok: false, error: failure('remote') };
|
|
1170
1711
|
}
|
|
1171
1712
|
return { ok: true, value: { status: 'existing', fullHandle: target.fullHandle } };
|
|
@@ -1174,8 +1715,21 @@ let AwikiService = (() => {
|
|
|
1174
1715
|
return { ok: false, error: failure('remote') };
|
|
1175
1716
|
}
|
|
1176
1717
|
}
|
|
1177
|
-
sendRegistrationOtp(request) {
|
|
1178
|
-
|
|
1718
|
+
async sendRegistrationOtp(request) {
|
|
1719
|
+
if (shortHandleInviteRequired(request.handle)) {
|
|
1720
|
+
const inspection = await this.inspectIdentityAccess({ handle: request.handle });
|
|
1721
|
+
if (!inspection.ok)
|
|
1722
|
+
return inspection;
|
|
1723
|
+
if (inspection.value.status === 'available')
|
|
1724
|
+
return { ok: false, error: failure('short-handle-invite-required') };
|
|
1725
|
+
}
|
|
1726
|
+
this.pendingDeviceJoin = undefined;
|
|
1727
|
+
return this.run(async (client) => {
|
|
1728
|
+
if (await this.selectDeviceJoinSession(client) !== null) {
|
|
1729
|
+
throw Object.assign(new Error('join already exists'), { name: 'AwikiSdkError', code: 'conflict' });
|
|
1730
|
+
}
|
|
1731
|
+
return client.sendRegistrationOtp(request);
|
|
1732
|
+
});
|
|
1179
1733
|
}
|
|
1180
1734
|
/**
|
|
1181
1735
|
* Register and persist the deployment's only AWiki identity.
|
|
@@ -1183,10 +1737,313 @@ let AwikiService = (() => {
|
|
|
1183
1737
|
* @returns The new public identity or a closed failure.
|
|
1184
1738
|
*/
|
|
1185
1739
|
async registerIdentity(request) {
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1740
|
+
if (shortHandleInviteRequired(request.handle)) {
|
|
1741
|
+
const inspection = await this.inspectIdentityAccess({ handle: request.handle });
|
|
1742
|
+
if (!inspection.ok)
|
|
1743
|
+
return inspection;
|
|
1744
|
+
if (inspection.value.status === 'available')
|
|
1745
|
+
return { ok: false, error: failure('short-handle-invite-required') };
|
|
1746
|
+
}
|
|
1747
|
+
this.pendingDeviceJoin = undefined;
|
|
1748
|
+
const result = await this.run(async (client) => {
|
|
1749
|
+
if (await this.selectDeviceJoinSession(client) !== null) {
|
|
1750
|
+
throw Object.assign(new Error('join already exists'), { name: 'AwikiSdkError', code: 'conflict' });
|
|
1751
|
+
}
|
|
1752
|
+
return client.registerIdentity(request);
|
|
1753
|
+
});
|
|
1754
|
+
if (!result.ok)
|
|
1755
|
+
return result;
|
|
1756
|
+
if (result.value.status === 'registered') {
|
|
1757
|
+
await this.activateRegisteredIdentity(result.value.identity);
|
|
1758
|
+
return { ok: true, value: { status: 'registered', identity: result.value.identity } };
|
|
1759
|
+
}
|
|
1760
|
+
this.pendingDeviceJoin = {
|
|
1761
|
+
continuationId: result.value.continuationId,
|
|
1762
|
+
fullHandle: result.value.fullHandle,
|
|
1763
|
+
mode: result.value.mode,
|
|
1764
|
+
requiresUserPresence: result.value.requiresUserPresence,
|
|
1765
|
+
};
|
|
1766
|
+
return {
|
|
1767
|
+
ok: true,
|
|
1768
|
+
value: {
|
|
1769
|
+
status: 'join-required',
|
|
1770
|
+
fullHandle: result.value.fullHandle,
|
|
1771
|
+
mode: result.value.mode,
|
|
1772
|
+
requiresUserPresence: result.value.requiresUserPresence,
|
|
1773
|
+
},
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
/** Retire only this revoked device credential; ordinary local messages remain for the rejoined identity. */
|
|
1777
|
+
retireDeviceIdentityForRejoin() {
|
|
1778
|
+
return this.mutateSession(async () => {
|
|
1779
|
+
const provider = this.provider;
|
|
1780
|
+
if (provider !== undefined)
|
|
1781
|
+
await this.stopProviderRuntime(provider);
|
|
1782
|
+
const result = await this.run(client => client.retireDefaultIdentityForRejoin(), { allowSignedOut: true });
|
|
1783
|
+
if (!result.ok) {
|
|
1784
|
+
if (provider !== undefined && this.provider === provider)
|
|
1785
|
+
this.ensureProviderRuntime(provider);
|
|
1786
|
+
return result;
|
|
1787
|
+
}
|
|
1788
|
+
try {
|
|
1789
|
+
await this.sessionStore.signIn();
|
|
1790
|
+
}
|
|
1791
|
+
catch {
|
|
1792
|
+
try {
|
|
1793
|
+
await this.sessionStore.signIn();
|
|
1794
|
+
}
|
|
1795
|
+
catch {
|
|
1796
|
+
this.ctx.logger('awiki').warn('AWiki local session marker could not be refreshed after device retirement');
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
this.signedOut = false;
|
|
1800
|
+
this.activeIdentityDid = undefined;
|
|
1801
|
+
this.pendingDeviceJoin = undefined;
|
|
1802
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
1803
|
+
this.invalidateSummaries();
|
|
1804
|
+
this.publishSession({ status: 'unregistered' });
|
|
1805
|
+
return { ok: true, value: { completed: true } };
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
/** Consume the exact in-memory continuation; ordinary Join never claims rebind user presence. */
|
|
1809
|
+
async beginDeviceJoin() {
|
|
1810
|
+
const continuation = this.pendingDeviceJoin;
|
|
1811
|
+
if (continuation === undefined)
|
|
1812
|
+
return { ok: false, error: failure('conflict') };
|
|
1813
|
+
if ((continuation.mode === 'ordinary' && continuation.requiresUserPresence)
|
|
1814
|
+
|| (continuation.mode === 'handle-recovery-rebind' && !continuation.requiresUserPresence)) {
|
|
1815
|
+
return { ok: false, error: failure('forbidden') };
|
|
1816
|
+
}
|
|
1817
|
+
const result = await this.run(async (client) => {
|
|
1818
|
+
let userPresenceConfirmed = false;
|
|
1819
|
+
if (continuation.mode === 'handle-recovery-rebind') {
|
|
1820
|
+
if (!client.trustedUserPresenceSupported) {
|
|
1821
|
+
throw Object.assign(new Error('trusted user presence unsupported'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1822
|
+
}
|
|
1823
|
+
userPresenceConfirmed = await client.confirmUserPresence('Rejoin your recovered AWiki identity on this device');
|
|
1824
|
+
if (!userPresenceConfirmed) {
|
|
1825
|
+
throw Object.assign(new Error('user presence denied'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
this.pendingDeviceJoin = undefined;
|
|
1829
|
+
return client.beginDeviceJoin({
|
|
1830
|
+
continuationId: continuation.continuationId,
|
|
1831
|
+
operationId: `dsh-device-join-${randomUUID()}`,
|
|
1832
|
+
userPresenceConfirmed,
|
|
1833
|
+
});
|
|
1834
|
+
});
|
|
1835
|
+
if (!result.ok)
|
|
1836
|
+
return result;
|
|
1837
|
+
this.activeDeviceJoinSessionId = result.value.joinSessionId;
|
|
1838
|
+
return this.applyCandidateJoinProgress(result.value);
|
|
1839
|
+
}
|
|
1840
|
+
/** Read local workflow authority before offering a new login. No remote mutations. */
|
|
1841
|
+
getIdentityAccessState() {
|
|
1842
|
+
return this.run(async (client) => {
|
|
1843
|
+
const joining = await this.selectDeviceJoinSession(client) !== null;
|
|
1844
|
+
const recoveries = await client.listPendingRecoveries();
|
|
1845
|
+
const domain = this.activeClientOptions?.userServiceDomain;
|
|
1846
|
+
const choice = this.pendingDeviceJoin;
|
|
1847
|
+
return {
|
|
1848
|
+
choice: joining || choice === undefined ? null : {
|
|
1849
|
+
status: 'join-required',
|
|
1850
|
+
fullHandle: choice.fullHandle,
|
|
1851
|
+
mode: choice.mode, requiresUserPresence: choice.requiresUserPresence,
|
|
1852
|
+
},
|
|
1853
|
+
joining,
|
|
1854
|
+
recoveries: recoveries.filter(value => domain !== undefined && value.fullHandle.slice(value.fullHandle.indexOf('.') + 1) === domain),
|
|
1855
|
+
};
|
|
1856
|
+
}, { allowSignedOut: true });
|
|
1857
|
+
}
|
|
1858
|
+
/** Restore from Core local_sessions and advance only the exact resumable Join. */
|
|
1859
|
+
async getDeviceJoinStatus() {
|
|
1860
|
+
const result = await this.run(async (client) => {
|
|
1861
|
+
const joinSessionId = await this.selectDeviceJoinSession(client);
|
|
1862
|
+
return joinSessionId === null ? null : client.getDeviceJoinStatus(joinSessionId);
|
|
1863
|
+
});
|
|
1864
|
+
if (!result.ok)
|
|
1865
|
+
return result;
|
|
1866
|
+
if (result.value === null)
|
|
1867
|
+
return { ok: true, value: null };
|
|
1868
|
+
this.activeDeviceJoinSessionId = result.value.joinSessionId;
|
|
1869
|
+
return this.applyCandidateJoinProgress(result.value);
|
|
1870
|
+
}
|
|
1871
|
+
async cancelDeviceJoin() {
|
|
1872
|
+
const result = await this.run(async (client) => {
|
|
1873
|
+
const joinSessionId = await this.selectDeviceJoinSession(client);
|
|
1874
|
+
if (joinSessionId === null)
|
|
1875
|
+
return null;
|
|
1876
|
+
return client.cancelDeviceJoin(joinSessionId);
|
|
1877
|
+
});
|
|
1878
|
+
if (!result.ok)
|
|
1879
|
+
return result;
|
|
1880
|
+
this.pendingDeviceJoin = undefined;
|
|
1881
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
1882
|
+
return { ok: true, value: { completed: true } };
|
|
1883
|
+
}
|
|
1884
|
+
/** Reliable-sync and project only Host-opaque device/request references. */
|
|
1885
|
+
refreshDeviceManagement() {
|
|
1886
|
+
return this.run(client => this.deviceManagementSnapshot(client));
|
|
1887
|
+
}
|
|
1888
|
+
async startDeviceJoinVerification(request) {
|
|
1889
|
+
const joinSessionId = this.requestSessions.get(request?.requestRef);
|
|
1890
|
+
if (joinSessionId === undefined)
|
|
1891
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1892
|
+
const result = await this.run(async (client) => {
|
|
1893
|
+
await this.requireDeviceManager(client);
|
|
1894
|
+
await client.syncDeviceManagement();
|
|
1895
|
+
let notice = (await client.listLocalDeviceJoinRequests())
|
|
1896
|
+
.find(value => value.joinSessionId === joinSessionId);
|
|
1897
|
+
if (notice === undefined) {
|
|
1898
|
+
throw Object.assign(new Error('request not found'), { name: 'AwikiSdkError', code: 'not-found' });
|
|
1899
|
+
}
|
|
1900
|
+
const localProgress = await this.localAdminJoinProgress(client, notice);
|
|
1901
|
+
if (localProgress !== undefined)
|
|
1902
|
+
return localProgress;
|
|
1903
|
+
if (!notice.canStartVerification) {
|
|
1904
|
+
throw Object.assign(new Error('request unavailable'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1905
|
+
}
|
|
1906
|
+
const operationId = `dsh-device-verify-${createHash('sha256').update(joinSessionId).digest('hex').slice(0, 32)}`;
|
|
1907
|
+
let started;
|
|
1908
|
+
try {
|
|
1909
|
+
started = await client.startDeviceJoinVerification({
|
|
1910
|
+
joinSessionId,
|
|
1911
|
+
operationId,
|
|
1912
|
+
challengeTtlSeconds: DEVICE_JOIN_CHALLENGE_TTL_SECONDS,
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
catch (error) {
|
|
1916
|
+
await client.syncDeviceManagement();
|
|
1917
|
+
notice = (await client.listLocalDeviceJoinRequests())
|
|
1918
|
+
.find(value => value.joinSessionId === joinSessionId);
|
|
1919
|
+
const recovered = notice === undefined ? undefined : await this.localAdminJoinProgress(client, notice);
|
|
1920
|
+
if (recovered !== undefined)
|
|
1921
|
+
return recovered;
|
|
1922
|
+
throw error;
|
|
1923
|
+
}
|
|
1924
|
+
await client.syncDeviceManagement();
|
|
1925
|
+
notice = (await client.listLocalDeviceJoinRequests())
|
|
1926
|
+
.find(value => value.joinSessionId === joinSessionId);
|
|
1927
|
+
if (notice === undefined)
|
|
1928
|
+
return started;
|
|
1929
|
+
return (await this.localAdminJoinProgress(client, notice)) ?? started;
|
|
1930
|
+
});
|
|
1931
|
+
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
1932
|
+
}
|
|
1933
|
+
async approveDeviceJoin(request) {
|
|
1934
|
+
if (request?.confirmation !== DEVICE_JOIN_APPROVAL_CONFIRMATION) {
|
|
1935
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1936
|
+
}
|
|
1937
|
+
const joinSessionId = this.requestSessions.get(request.requestRef);
|
|
1938
|
+
if (joinSessionId === undefined || !/^\d{6}$/u.test(request.enteredSas)) {
|
|
1939
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1940
|
+
}
|
|
1941
|
+
const result = await this.run(async (client) => {
|
|
1942
|
+
await this.requireDeviceManager(client);
|
|
1943
|
+
const progress = await client.getLocalDeviceJoinVerificationProgress(joinSessionId);
|
|
1944
|
+
if (progress.sas === undefined || !constantTimeSasMatches(progress.sas, request.enteredSas)) {
|
|
1945
|
+
throw Object.assign(new Error('sas mismatch'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1946
|
+
}
|
|
1947
|
+
const prompt = await client.prepareDeviceJoinApproval(joinSessionId);
|
|
1948
|
+
try {
|
|
1949
|
+
return await client.confirmDeviceJoinApproval(prompt.approvalHandle);
|
|
1950
|
+
}
|
|
1951
|
+
catch (error) {
|
|
1952
|
+
// Approval is a remote mutation: its response may fail after Core has
|
|
1953
|
+
// already committed both the provider document and the local terminal
|
|
1954
|
+
// Join state. Re-read the authoritative terminal state before exposing
|
|
1955
|
+
// a false failure to the administrator.
|
|
1956
|
+
try {
|
|
1957
|
+
await client.syncDeviceManagement();
|
|
1958
|
+
}
|
|
1959
|
+
catch { }
|
|
1960
|
+
try {
|
|
1961
|
+
const recovered = await client.getLocalDeviceJoinVerificationProgress(joinSessionId);
|
|
1962
|
+
if (adminJoinPhase(recovered) === 'authorized')
|
|
1963
|
+
return recovered;
|
|
1964
|
+
}
|
|
1965
|
+
catch { }
|
|
1966
|
+
throw error;
|
|
1967
|
+
}
|
|
1968
|
+
});
|
|
1969
|
+
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
1970
|
+
}
|
|
1971
|
+
async rejectDeviceJoin(request) {
|
|
1972
|
+
const joinSessionId = this.requestSessions.get(request?.requestRef);
|
|
1973
|
+
if (joinSessionId === undefined
|
|
1974
|
+
|| (request.reason !== 'user_rejected' && request.reason !== 'sas_mismatch')) {
|
|
1975
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1976
|
+
}
|
|
1977
|
+
const result = await this.run(async (client) => {
|
|
1978
|
+
await this.requireDeviceManager(client);
|
|
1979
|
+
return client.rejectDeviceJoin(joinSessionId, request.reason);
|
|
1980
|
+
});
|
|
1981
|
+
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
1982
|
+
}
|
|
1983
|
+
async revokeDevice(request) {
|
|
1984
|
+
if (request?.confirmation !== DEVICE_REVOKE_CONFIRMATION) {
|
|
1985
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1986
|
+
}
|
|
1987
|
+
const deviceId = this.deviceIds.get(request.deviceRef);
|
|
1988
|
+
if (deviceId === undefined)
|
|
1989
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1990
|
+
return this.run(async (client) => {
|
|
1991
|
+
await this.requireDeviceManager(client);
|
|
1992
|
+
const devices = await client.getDeviceRegistry();
|
|
1993
|
+
const target = devices.find(device => device.deviceId === deviceId);
|
|
1994
|
+
if (target === undefined || target.isCurrent) {
|
|
1995
|
+
throw Object.assign(new Error('device unavailable'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
1996
|
+
}
|
|
1997
|
+
await client.revokeDevice(deviceId);
|
|
1998
|
+
return this.deviceManagementSnapshot(client);
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
/** Prepare a short-lived Core authorization without exposing it to Browser. */
|
|
2002
|
+
async prepareRootTransfer(request) {
|
|
2003
|
+
const deviceId = this.deviceIds.get(request?.deviceRef);
|
|
2004
|
+
if (deviceId === undefined)
|
|
2005
|
+
return { ok: false, error: failure('invalid-request') };
|
|
2006
|
+
return this.run(async (client) => {
|
|
2007
|
+
const did = await this.requireRootTransferRecipient(client, deviceId);
|
|
2008
|
+
const preparation = await client.prepareRootKeyTransfer(deviceId);
|
|
2009
|
+
if (preparation.recipient.did !== did || preparation.recipient.deviceId !== deviceId) {
|
|
2010
|
+
throw Object.assign(new Error('root transfer recipient mismatch'), { name: 'AwikiSdkError', code: 'remote' });
|
|
2011
|
+
}
|
|
2012
|
+
for (const [reference, pending] of this.rootTransfers) {
|
|
2013
|
+
if (pending.deviceId === deviceId)
|
|
2014
|
+
this.rootTransfers.delete(reference);
|
|
2015
|
+
}
|
|
2016
|
+
const transferRef = `root-transfer-${randomUUID()}`;
|
|
2017
|
+
this.rootTransfers.set(transferRef, {
|
|
2018
|
+
authorizationHandle: preparation.authorizationHandle,
|
|
2019
|
+
deviceId,
|
|
2020
|
+
deviceRef: request.deviceRef,
|
|
2021
|
+
did,
|
|
2022
|
+
});
|
|
2023
|
+
return { transferRef, deviceRef: request.deviceRef, expiresAt: preparation.expiresAt };
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
/** Authenticate locally, recheck fresh context, then consume one exact Core authorization. */
|
|
2027
|
+
async confirmRootTransfer(request) {
|
|
2028
|
+
const pending = this.rootTransfers.get(request?.transferRef);
|
|
2029
|
+
if (pending === undefined)
|
|
2030
|
+
return { ok: false, error: failure('invalid-request') };
|
|
2031
|
+
this.rootTransfers.delete(request.transferRef);
|
|
2032
|
+
return this.run(async (client) => {
|
|
2033
|
+
const confirmed = await client.confirmUserPresence('Grant AWiki device management access');
|
|
2034
|
+
if (!confirmed) {
|
|
2035
|
+
throw Object.assign(new Error('user presence denied'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2036
|
+
}
|
|
2037
|
+
const did = await this.requireRootTransferRecipient(client, pending.deviceId);
|
|
2038
|
+
if (did !== pending.did) {
|
|
2039
|
+
throw Object.assign(new Error('root transfer identity changed'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2040
|
+
}
|
|
2041
|
+
const sent = await client.confirmAndSendRootKeyTransfer(pending.authorizationHandle);
|
|
2042
|
+
if (sent.recipientDeviceId !== pending.deviceId) {
|
|
2043
|
+
throw Object.assign(new Error('root transfer receipt mismatch'), { name: 'AwikiSdkError', code: 'remote' });
|
|
2044
|
+
}
|
|
2045
|
+
return { deviceRef: pending.deviceRef, acceptedAt: sent.acceptedAt };
|
|
2046
|
+
});
|
|
1190
2047
|
}
|
|
1191
2048
|
/**
|
|
1192
2049
|
* Update the deployment identity's public WNS display name.
|
|
@@ -1212,10 +2069,16 @@ let AwikiService = (() => {
|
|
|
1212
2069
|
}
|
|
1213
2070
|
/** Start durable phone recovery for one existing full Handle. */
|
|
1214
2071
|
sendRecoveryOtp(request) {
|
|
2072
|
+
this.pendingDeviceJoin = undefined;
|
|
1215
2073
|
const normalized = normalizeRecoveryOtpRequest(request);
|
|
1216
2074
|
if (normalized === undefined)
|
|
1217
2075
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1218
|
-
return this.run(client =>
|
|
2076
|
+
return this.run(async (client) => {
|
|
2077
|
+
if (await this.selectDeviceJoinSession(client) !== null) {
|
|
2078
|
+
throw Object.assign(new Error('join already exists'), { name: 'AwikiSdkError', code: 'conflict' });
|
|
2079
|
+
}
|
|
2080
|
+
return client.sendRecoveryOtp(normalized);
|
|
2081
|
+
}, { allowSignedOut: true });
|
|
1219
2082
|
}
|
|
1220
2083
|
/** Verify a recovery OTP and freeze its exact intent before the remote commit. */
|
|
1221
2084
|
prepareRecovery(request) {
|
|
@@ -1229,33 +2092,35 @@ let AwikiService = (() => {
|
|
|
1229
2092
|
const normalized = normalizeRecoveryOperation(request);
|
|
1230
2093
|
if (normalized === undefined)
|
|
1231
2094
|
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;
|
|
2095
|
+
return this.run(client => client.activateRecovery(normalized), { allowSignedOut: true });
|
|
1237
2096
|
}
|
|
1238
2097
|
/** Read durable recovery state before deciding whether a retry is valid. */
|
|
1239
2098
|
async getRecoveryStatus(request) {
|
|
1240
2099
|
const normalized = normalizeRecoveryOperation(request);
|
|
1241
2100
|
if (normalized === undefined)
|
|
1242
2101
|
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;
|
|
2102
|
+
return this.run(client => client.getRecoveryStatus(normalized), { allowSignedOut: true });
|
|
1248
2103
|
}
|
|
1249
2104
|
/** Resume only the exact Core-owned operation selected by the browser. */
|
|
1250
2105
|
async resumeRecovery(request) {
|
|
1251
2106
|
const normalized = normalizeRecoveryOperation(request);
|
|
1252
2107
|
if (normalized === undefined)
|
|
1253
2108
|
return { ok: false, error: failure('invalid-request') };
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
2109
|
+
return this.run(client => client.resumeRecovery(normalized), { allowSignedOut: true });
|
|
2110
|
+
}
|
|
2111
|
+
/** Explicitly enter an applied identity; reads and late Core completions never sign in. */
|
|
2112
|
+
async enterRecoveredSession(request) {
|
|
2113
|
+
const normalized = normalizeRecoveryOperation(request);
|
|
2114
|
+
if (normalized === undefined)
|
|
2115
|
+
return { ok: false, error: failure('invalid-request') };
|
|
2116
|
+
const requestGeneration = this.sessionRevision;
|
|
2117
|
+
const result = await this.run(client => client.getRecoveryStatus(normalized), { allowSignedOut: true });
|
|
2118
|
+
if (!result.ok)
|
|
2119
|
+
return result;
|
|
2120
|
+
if (result.value.phase !== 'applied' || !result.value.allowedActions?.includes('activate_identity')) {
|
|
2121
|
+
return { ok: false, error: failure('conflict') };
|
|
1257
2122
|
}
|
|
1258
|
-
return result;
|
|
2123
|
+
return this.applyRecoveredSession(result.value, requestGeneration);
|
|
1259
2124
|
}
|
|
1260
2125
|
/** Discard only a pre-attempt operation; Core rejects post-attempt deletion. */
|
|
1261
2126
|
async discardRecovery(request) {
|
|
@@ -1322,6 +2187,12 @@ let AwikiService = (() => {
|
|
|
1322
2187
|
return result.ok ? { ok: true, value: { completed: true } } : result;
|
|
1323
2188
|
}
|
|
1324
2189
|
/** Read one authoritative versioned member page. */
|
|
2190
|
+
getDisplayProfiles(peers) {
|
|
2191
|
+
if (!Array.isArray(peers) || peers.length > 100 || peers.some(peer => typeof peer !== 'string' || !peer.startsWith('did:'))) {
|
|
2192
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
2193
|
+
}
|
|
2194
|
+
return this.run(client => client.getDisplayProfiles(peers));
|
|
2195
|
+
}
|
|
1325
2196
|
listGroupMembers(request) {
|
|
1326
2197
|
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
1327
2198
|
if (groupDid === undefined
|
|
@@ -1353,16 +2224,6 @@ let AwikiService = (() => {
|
|
|
1353
2224
|
}
|
|
1354
2225
|
return this.run(client => client.removeGroupMember(groupDid, member));
|
|
1355
2226
|
}
|
|
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
2227
|
/** Read presentation-only roster preferences for the active identity. */
|
|
1367
2228
|
getConversationPreferences() {
|
|
1368
2229
|
return this.run(async (client) => this.conversationPreferenceStore.get(await this.ownerDid(client)));
|
|
@@ -1583,7 +2444,7 @@ let AwikiService = (() => {
|
|
|
1583
2444
|
}
|
|
1584
2445
|
/** Return the deployment identity's public mailbox state. */
|
|
1585
2446
|
getMailAccount() {
|
|
1586
|
-
return this.run(client => client.getMailAccount()
|
|
2447
|
+
return this.run(client => client.getMailAccount());
|
|
1587
2448
|
}
|
|
1588
2449
|
/** List one bounded mailbox page on explicit browser/tool demand. */
|
|
1589
2450
|
async listMailInbox(request) {
|
|
@@ -1594,11 +2455,15 @@ let AwikiService = (() => {
|
|
|
1594
2455
|
catch {
|
|
1595
2456
|
return { ok: false, error: failure('invalid-request') };
|
|
1596
2457
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
2458
|
+
const requestGeneration = this.sessionRevision;
|
|
2459
|
+
return this.run(async (client) => {
|
|
2460
|
+
const page = normalized.folder === 'sent'
|
|
2461
|
+
? await this.mailListClient.listOutbound(normalized)
|
|
2462
|
+
: await client.listMailInbox(normalized);
|
|
2463
|
+
if (this.sessionRevision !== requestGeneration)
|
|
2464
|
+
throw new AwikiSdkError('network');
|
|
2465
|
+
return page;
|
|
2466
|
+
});
|
|
1602
2467
|
}
|
|
1603
2468
|
/** Read one bounded plain-text mail message. */
|
|
1604
2469
|
async readMail(request) {
|
|
@@ -1609,116 +2474,22 @@ let AwikiService = (() => {
|
|
|
1609
2474
|
catch {
|
|
1610
2475
|
return { ok: false, error: failure('invalid-request') };
|
|
1611
2476
|
}
|
|
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 });
|
|
2477
|
+
return this.run(client => client.readMail(normalized));
|
|
1621
2478
|
}
|
|
1622
2479
|
/** Mark explicitly selected mail messages read. Browser callers require an explicit click. */
|
|
1623
2480
|
markMailRead(request) {
|
|
1624
|
-
return this.runValidatedMail(() => mailMarkReadRequest(request), (client, normalized) => client.markMailRead(normalized)
|
|
2481
|
+
return this.runValidatedMail(() => mailMarkReadRequest(request), (client, normalized) => client.markMailRead(normalized));
|
|
1625
2482
|
}
|
|
1626
|
-
/** Send one mail once. Browser callers require an explicit confirmation. */
|
|
2483
|
+
/** Send one plain-text mail once. Browser callers require an explicit confirmation. */
|
|
1627
2484
|
async sendMail(request) {
|
|
1628
2485
|
let normalized;
|
|
1629
2486
|
try {
|
|
1630
|
-
normalized = mailSendRequest(request
|
|
1631
|
-
maxCount: this.resolved.mailAttachmentMaxCount,
|
|
1632
|
-
maxBytes: this.resolved.mailAttachmentMaxBytes,
|
|
1633
|
-
totalMaxBytes: this.resolved.mailAttachmentTotalMaxBytes,
|
|
1634
|
-
});
|
|
2487
|
+
normalized = mailSendRequest(request);
|
|
1635
2488
|
}
|
|
1636
2489
|
catch {
|
|
1637
2490
|
return { ok: false, error: failure('invalid-request') };
|
|
1638
2491
|
}
|
|
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 });
|
|
2492
|
+
return this.run(client => client.sendMail(normalized));
|
|
1722
2493
|
}
|
|
1723
2494
|
/**
|
|
1724
2495
|
* Permanently remove the exact SDK-owned local state after an explicit browser acknowledgement.
|
|
@@ -1730,27 +2501,35 @@ let AwikiService = (() => {
|
|
|
1730
2501
|
if (request?.confirmation !== AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION) {
|
|
1731
2502
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1732
2503
|
}
|
|
2504
|
+
// Fence pending post-Recovery work before the serialized clear reaches the head of the queue.
|
|
2505
|
+
this.sessionRevision += 1;
|
|
1733
2506
|
return this.mutateSession(async () => {
|
|
1734
2507
|
const provider = this.provider;
|
|
1735
2508
|
if (provider !== undefined)
|
|
1736
|
-
await this.
|
|
1737
|
-
|
|
2509
|
+
await this.stopProviderRuntime(provider);
|
|
2510
|
+
let result = await this.run(client => client.clearLocalData(), { allowSignedOut: true });
|
|
2511
|
+
// Core's clear is local and idempotent. A provider mutation that was still
|
|
2512
|
+
// settling can surface once as the deliberately opaque `remote` failure.
|
|
2513
|
+
// Retry only that class once after the runtime has already been stopped.
|
|
2514
|
+
if (!result.ok && result.error.code === 'remote') {
|
|
2515
|
+
result = await this.run(client => client.clearLocalData(), { allowSignedOut: true });
|
|
2516
|
+
}
|
|
1738
2517
|
if (!result.ok) {
|
|
1739
2518
|
if (provider !== undefined && this.provider === provider)
|
|
1740
|
-
|
|
2519
|
+
this.ensureProviderRuntime(provider);
|
|
1741
2520
|
return result;
|
|
1742
2521
|
}
|
|
1743
2522
|
try {
|
|
1744
2523
|
await this.imageAttachmentCache.clear();
|
|
1745
|
-
await this.
|
|
2524
|
+
await clearLegacySentMailCache(this.activeClientOptions?.stateRoot ?? this.resolved.stateRoot);
|
|
1746
2525
|
await this.conversationPreferenceStore.clear();
|
|
1747
2526
|
await this.sessionStore.signIn();
|
|
1748
2527
|
this.signedOut = false;
|
|
1749
2528
|
this.activeIdentityDid = undefined;
|
|
2529
|
+
this.pendingDeviceJoin = undefined;
|
|
2530
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
1750
2531
|
this.invalidateSummaries();
|
|
1751
2532
|
this.publishSession({ status: 'unregistered' });
|
|
1752
|
-
if (provider !== undefined && this.provider === provider)
|
|
1753
|
-
void this.startListener(provider);
|
|
1754
2533
|
return result;
|
|
1755
2534
|
}
|
|
1756
2535
|
catch {
|
|
@@ -1758,53 +2537,291 @@ let AwikiService = (() => {
|
|
|
1758
2537
|
}
|
|
1759
2538
|
});
|
|
1760
2539
|
}
|
|
1761
|
-
/** Re-enter after Core
|
|
1762
|
-
async applyRecoveredSession(progress) {
|
|
2540
|
+
/** Re-enter only after Core confirms that the exact recovered identity is applied locally. */
|
|
2541
|
+
async applyRecoveredSession(progress, requestGeneration) {
|
|
1763
2542
|
if (progress.phase !== 'applied')
|
|
1764
|
-
return true;
|
|
2543
|
+
return { ok: true, value: progress };
|
|
1765
2544
|
return this.mutateSession(async () => {
|
|
1766
2545
|
const provider = this.provider;
|
|
1767
2546
|
if (provider === undefined)
|
|
1768
|
-
return false;
|
|
2547
|
+
return { ok: false, error: failure('remote') };
|
|
2548
|
+
if (this.sessionRevision !== requestGeneration) {
|
|
2549
|
+
return {
|
|
2550
|
+
ok: false,
|
|
2551
|
+
error: {
|
|
2552
|
+
...failure('remote'),
|
|
2553
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(this.activeIdentityDid === progress.currentDid, 'replaced'),
|
|
2554
|
+
},
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
1769
2557
|
try {
|
|
1770
2558
|
const identity = await provider.client.getIdentity();
|
|
1771
|
-
|
|
1772
|
-
|
|
2559
|
+
const currentPrincipalMatchesRecovery = identity !== null && identity.did === progress.currentDid;
|
|
2560
|
+
if (this.provider !== provider || this.sessionRevision !== requestGeneration) {
|
|
2561
|
+
return {
|
|
2562
|
+
ok: false,
|
|
2563
|
+
error: {
|
|
2564
|
+
...failure('remote'),
|
|
2565
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(currentPrincipalMatchesRecovery, 'replaced'),
|
|
2566
|
+
},
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
if (!currentPrincipalMatchesRecovery) {
|
|
2570
|
+
return {
|
|
2571
|
+
ok: false,
|
|
2572
|
+
error: {
|
|
2573
|
+
...failure('remote'),
|
|
2574
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(false, 'current'),
|
|
2575
|
+
},
|
|
2576
|
+
};
|
|
2577
|
+
}
|
|
1773
2578
|
const alreadyActive = this.signedOut === false && this.activeIdentityDid === identity.did;
|
|
1774
2579
|
if (!alreadyActive) {
|
|
1775
2580
|
await this.sessionStore.signIn();
|
|
2581
|
+
if (this.provider !== provider || this.sessionRevision !== requestGeneration) {
|
|
2582
|
+
return {
|
|
2583
|
+
ok: false,
|
|
2584
|
+
error: {
|
|
2585
|
+
...failure('remote'),
|
|
2586
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(true, 'replaced'),
|
|
2587
|
+
},
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
1776
2590
|
this.signedOut = false;
|
|
1777
2591
|
this.activeIdentityDid = identity.did;
|
|
1778
2592
|
this.invalidateSummaries();
|
|
2593
|
+
requestGeneration = this.sessionRevision;
|
|
2594
|
+
}
|
|
2595
|
+
const mailRecoveryObservability = await this.reconcileRecoveredMailbox(provider);
|
|
2596
|
+
if (this.provider !== provider || this.sessionRevision !== requestGeneration) {
|
|
2597
|
+
return {
|
|
2598
|
+
ok: false,
|
|
2599
|
+
error: {
|
|
2600
|
+
...failure('remote'),
|
|
2601
|
+
mailRecoveryObservability: fencedMailRecoveryObservability(true, 'replaced'),
|
|
2602
|
+
},
|
|
2603
|
+
};
|
|
1779
2604
|
}
|
|
1780
|
-
if (this.provider !== provider)
|
|
1781
|
-
return false;
|
|
1782
2605
|
if (!alreadyActive) {
|
|
1783
2606
|
const session = { status: 'active', identity };
|
|
1784
2607
|
this.publishSession(session);
|
|
1785
|
-
|
|
1786
|
-
await this.startListener(provider);
|
|
2608
|
+
this.ensureProviderRuntime(provider);
|
|
1787
2609
|
}
|
|
1788
|
-
return true;
|
|
2610
|
+
return { ok: true, value: { ...progress, mailRecoveryObservability } };
|
|
1789
2611
|
}
|
|
1790
2612
|
catch {
|
|
1791
|
-
return false;
|
|
2613
|
+
return { ok: false, error: failure('remote') };
|
|
1792
2614
|
}
|
|
1793
2615
|
});
|
|
1794
2616
|
}
|
|
1795
|
-
/**
|
|
2617
|
+
/** Rebind Mail first-use ownership and retain only its closed, secret-free classification. */
|
|
2618
|
+
async reconcileRecoveredMailbox(provider) {
|
|
2619
|
+
try {
|
|
2620
|
+
await provider.client.getMailAccount();
|
|
2621
|
+
return successfulMailRecoveryObservability();
|
|
2622
|
+
}
|
|
2623
|
+
catch (error) {
|
|
2624
|
+
return failedMailRecoveryObservability(error);
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
/** Select the only resumable new-device session; Core local_sessions is the sole restart SoT. */
|
|
2628
|
+
async selectDeviceJoinSession(client) {
|
|
2629
|
+
const sessions = await client.listLocalDeviceJoinSessions();
|
|
2630
|
+
const resumable = sessions.filter(value => (value.side === 'new_device' && RESUMABLE_JOIN_PHASES.has(value.localPhase)));
|
|
2631
|
+
if (this.activeDeviceJoinSessionId !== undefined) {
|
|
2632
|
+
const tracked = resumable.find(value => value.joinSessionId === this.activeDeviceJoinSessionId);
|
|
2633
|
+
if (tracked !== undefined)
|
|
2634
|
+
return tracked.joinSessionId;
|
|
2635
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
2636
|
+
}
|
|
2637
|
+
if (resumable.length > 1) {
|
|
2638
|
+
throw Object.assign(new Error('multiple local joins'), { name: 'AwikiSdkError', code: 'conflict' });
|
|
2639
|
+
}
|
|
2640
|
+
const selected = resumable[0];
|
|
2641
|
+
this.activeDeviceJoinSessionId = selected?.joinSessionId;
|
|
2642
|
+
return selected?.joinSessionId ?? null;
|
|
2643
|
+
}
|
|
2644
|
+
async applyCandidateJoinProgress(value) {
|
|
2645
|
+
const phase = candidateJoinPhase(value);
|
|
2646
|
+
if (phase === undefined)
|
|
2647
|
+
return { ok: false, error: failure('remote') };
|
|
2648
|
+
if (phase === 'cancelled' || phase === 'rejected' || phase === 'expired') {
|
|
2649
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
2650
|
+
}
|
|
2651
|
+
if (phase === 'authorized') {
|
|
2652
|
+
if (value.identity === undefined)
|
|
2653
|
+
return { ok: false, error: failure('remote') };
|
|
2654
|
+
await this.activateRegisteredIdentity(value.identity);
|
|
2655
|
+
}
|
|
2656
|
+
return {
|
|
2657
|
+
ok: true,
|
|
2658
|
+
value: {
|
|
2659
|
+
phase,
|
|
2660
|
+
expiresAt: value.expiresAt,
|
|
2661
|
+
...(phase === 'sas-ready' && value.sas !== undefined ? { sas: value.sas } : {}),
|
|
2662
|
+
completed: phase === 'authorized',
|
|
2663
|
+
},
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
publicAdminJoinProgress(requestRef, value) {
|
|
2667
|
+
const phase = adminJoinPhase(value);
|
|
2668
|
+
if (phase === undefined)
|
|
2669
|
+
return { ok: false, error: failure('remote') };
|
|
2670
|
+
return {
|
|
2671
|
+
ok: true,
|
|
2672
|
+
value: {
|
|
2673
|
+
requestRef,
|
|
2674
|
+
phase,
|
|
2675
|
+
expiresAt: value.expiresAt,
|
|
2676
|
+
...(phase === 'sas-ready' && value.sas !== undefined ? { sas: value.sas } : {}),
|
|
2677
|
+
},
|
|
2678
|
+
};
|
|
2679
|
+
}
|
|
2680
|
+
async localAdminJoinProgress(client, notice) {
|
|
2681
|
+
const local = (await client.listLocalDeviceJoinSessions())
|
|
2682
|
+
.find(session => session.side === 'admin' && session.joinSessionId === notice.joinSessionId);
|
|
2683
|
+
if (local === undefined) {
|
|
2684
|
+
return notice.claimedByCurrentDevice
|
|
2685
|
+
? client.getLocalDeviceJoinVerificationProgress(notice.joinSessionId)
|
|
2686
|
+
: undefined;
|
|
2687
|
+
}
|
|
2688
|
+
if (local.localPhase === 'challenge_prepared') {
|
|
2689
|
+
return {
|
|
2690
|
+
joinSessionId: notice.joinSessionId,
|
|
2691
|
+
localPhase: local.localPhase,
|
|
2692
|
+
remoteState: notice.state,
|
|
2693
|
+
expiresAt: local.expiresAt,
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
return client.getLocalDeviceJoinVerificationProgress(notice.joinSessionId);
|
|
2697
|
+
}
|
|
2698
|
+
async requireDeviceManager(client) {
|
|
2699
|
+
const current = await client.getCurrentDeviceSummary();
|
|
2700
|
+
if (!current.canManage || current.role !== 'admin' || current.readiness !== 'admin_ready') {
|
|
2701
|
+
throw Object.assign(new Error('device manager required'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
async requireRootTransferRecipient(client, deviceId) {
|
|
2705
|
+
if (!client.trustedUserPresenceSupported) {
|
|
2706
|
+
throw Object.assign(new Error('trusted user presence unsupported'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2707
|
+
}
|
|
2708
|
+
await this.requireDeviceManager(client);
|
|
2709
|
+
await client.syncDeviceManagement();
|
|
2710
|
+
const identity = await client.getIdentity();
|
|
2711
|
+
if (identity === null) {
|
|
2712
|
+
throw Object.assign(new Error('identity required'), { name: 'AwikiSdkError', code: 'not-registered' });
|
|
2713
|
+
}
|
|
2714
|
+
const target = (await client.getDeviceRegistry()).find(device => device.deviceId === deviceId);
|
|
2715
|
+
if (target === undefined
|
|
2716
|
+
|| target.isCurrent
|
|
2717
|
+
|| target.status !== 'active'
|
|
2718
|
+
|| target.role !== 'member'
|
|
2719
|
+
|| target.managementReady) {
|
|
2720
|
+
throw Object.assign(new Error('root transfer recipient unavailable'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2721
|
+
}
|
|
2722
|
+
return identity.did;
|
|
2723
|
+
}
|
|
2724
|
+
requestRef(joinSessionId) {
|
|
2725
|
+
const existing = this.requestRefs.get(joinSessionId);
|
|
2726
|
+
if (existing !== undefined)
|
|
2727
|
+
return existing;
|
|
2728
|
+
const reference = `join-${randomUUID()}`;
|
|
2729
|
+
this.requestRefs.set(joinSessionId, reference);
|
|
2730
|
+
this.requestSessions.set(reference, joinSessionId);
|
|
2731
|
+
return reference;
|
|
2732
|
+
}
|
|
2733
|
+
deviceRef(deviceId) {
|
|
2734
|
+
const existing = this.deviceRefs.get(deviceId);
|
|
2735
|
+
if (existing !== undefined)
|
|
2736
|
+
return existing;
|
|
2737
|
+
const reference = `device-${randomUUID()}`;
|
|
2738
|
+
this.deviceRefs.set(deviceId, reference);
|
|
2739
|
+
this.deviceIds.set(reference, deviceId);
|
|
2740
|
+
return reference;
|
|
2741
|
+
}
|
|
2742
|
+
async deviceManagementSnapshot(client) {
|
|
2743
|
+
await client.syncDeviceManagement();
|
|
2744
|
+
const current = await client.getCurrentDeviceSummary();
|
|
2745
|
+
if (!current.canManage || current.role !== 'admin' || current.readiness !== 'admin_ready') {
|
|
2746
|
+
this.requestRefs.clear();
|
|
2747
|
+
this.requestSessions.clear();
|
|
2748
|
+
this.deviceRefs.clear();
|
|
2749
|
+
this.deviceIds.clear();
|
|
2750
|
+
this.rootTransfers.clear();
|
|
2751
|
+
return {
|
|
2752
|
+
canManage: false,
|
|
2753
|
+
rootTransferSupported: client.trustedUserPresenceSupported,
|
|
2754
|
+
...current.role === undefined ? {} : { role: current.role },
|
|
2755
|
+
readiness: current.readiness,
|
|
2756
|
+
devices: [],
|
|
2757
|
+
requests: [],
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
const requests = await client.listLocalDeviceJoinRequests();
|
|
2761
|
+
await Promise.all(requests
|
|
2762
|
+
.filter(request => request.claimedByCurrentDevice)
|
|
2763
|
+
.map(request => client.getLocalDeviceJoinVerificationProgress(request.joinSessionId).catch(() => undefined)));
|
|
2764
|
+
const devices = await client.getDeviceRegistry();
|
|
2765
|
+
const identity = await client.getIdentity();
|
|
2766
|
+
const joinedAtByDeviceId = new Map();
|
|
2767
|
+
for (const request of requests) {
|
|
2768
|
+
const previous = joinedAtByDeviceId.get(request.protocolDeviceId);
|
|
2769
|
+
if (previous === undefined || request.issuedAt > previous) {
|
|
2770
|
+
joinedAtByDeviceId.set(request.protocolDeviceId, request.issuedAt);
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
const initialDeviceJoinedAt = identity === null
|
|
2774
|
+
? undefined
|
|
2775
|
+
: new Date(identity.registeredAt).toISOString();
|
|
2776
|
+
return {
|
|
2777
|
+
canManage: true,
|
|
2778
|
+
rootTransferSupported: client.trustedUserPresenceSupported,
|
|
2779
|
+
role: 'admin',
|
|
2780
|
+
readiness: 'admin_ready',
|
|
2781
|
+
devices: devices
|
|
2782
|
+
.filter(device => device.status === 'active')
|
|
2783
|
+
.map(device => this.publicDevice(device, joinedAtByDeviceId.get(device.deviceId) ?? (device.isCurrent ? initialDeviceJoinedAt : undefined))),
|
|
2784
|
+
requests: requests.map(request => ({
|
|
2785
|
+
requestRef: this.requestRef(request.joinSessionId),
|
|
2786
|
+
candidateKeyFingerprint: request.candidateKeyFingerprint,
|
|
2787
|
+
issuedAt: request.issuedAt,
|
|
2788
|
+
expiresAt: request.expiresAt,
|
|
2789
|
+
state: requestJoinPhase(request),
|
|
2790
|
+
claimedByCurrentDevice: request.claimedByCurrentDevice,
|
|
2791
|
+
canStartVerification: request.canStartVerification,
|
|
2792
|
+
})),
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
publicDevice(device, joinedAt) {
|
|
2796
|
+
const fingerprint = createHash('sha256').update(device.deviceId).digest('hex').slice(0, 8).toUpperCase();
|
|
2797
|
+
return {
|
|
2798
|
+
deviceRef: this.deviceRef(device.deviceId),
|
|
2799
|
+
displayId: `${fingerprint.slice(0, 4)}-${fingerprint.slice(4)}`,
|
|
2800
|
+
...joinedAt === undefined ? {} : { joinedAt },
|
|
2801
|
+
status: device.status,
|
|
2802
|
+
role: device.role,
|
|
2803
|
+
managementReady: device.managementReady,
|
|
2804
|
+
isCurrent: device.isCurrent,
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
/** Publish one newly registered identity, then start realtime only in the background. */
|
|
1796
2808
|
async activateRegisteredIdentity(identity) {
|
|
2809
|
+
this.pendingDeviceJoin = undefined;
|
|
2810
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
1797
2811
|
this.activeIdentityDid = identity.did;
|
|
1798
2812
|
this.publishSession({ status: 'active', identity });
|
|
1799
2813
|
const provider = this.provider;
|
|
1800
|
-
if (provider !== undefined)
|
|
1801
|
-
|
|
1802
|
-
await this.startListener(provider);
|
|
1803
|
-
}
|
|
2814
|
+
if (provider !== undefined)
|
|
2815
|
+
this.ensureProviderRuntime(provider);
|
|
1804
2816
|
}
|
|
1805
2817
|
/** Invalidate cached session work and cancel every model request still owned by the old session. */
|
|
1806
2818
|
invalidateSummaries() {
|
|
1807
2819
|
this.sessionRevision += 1;
|
|
2820
|
+
this.requestRefs.clear();
|
|
2821
|
+
this.requestSessions.clear();
|
|
2822
|
+
this.deviceRefs.clear();
|
|
2823
|
+
this.deviceIds.clear();
|
|
2824
|
+
this.rootTransfers.clear();
|
|
1808
2825
|
for (const controller of this.activeSummaryRequests)
|
|
1809
2826
|
controller.abort();
|
|
1810
2827
|
this.activeSummaryRequests.clear();
|
|
@@ -1815,40 +2832,31 @@ let AwikiService = (() => {
|
|
|
1815
2832
|
}
|
|
1816
2833
|
/** Resolve and cache the owner binding required by private Host-side projections. */
|
|
1817
2834
|
async ownerDid(client) {
|
|
1818
|
-
const identity = await client.getIdentity();
|
|
1819
|
-
const ownerDid = identity?.did;
|
|
2835
|
+
const identity = this.activeIdentityDid === undefined ? await client.getIdentity() : undefined;
|
|
2836
|
+
const ownerDid = this.activeIdentityDid ?? identity?.did;
|
|
1820
2837
|
if (ownerDid === undefined) {
|
|
1821
2838
|
throw Object.assign(new Error('not registered'), { name: 'AwikiSdkError', code: 'not-registered' });
|
|
1822
2839
|
}
|
|
1823
|
-
if (this.activeIdentityDid !== undefined && this.activeIdentityDid !== ownerDid) {
|
|
1824
|
-
throw new ProviderUnavailableError();
|
|
1825
|
-
}
|
|
1826
2840
|
this.activeIdentityDid = ownerDid;
|
|
1827
2841
|
return ownerDid;
|
|
1828
2842
|
}
|
|
1829
2843
|
/** Invoke the current client and normalize every rejection to a public result. */
|
|
1830
2844
|
async run(operation, options = {}) {
|
|
1831
2845
|
try {
|
|
1832
|
-
const
|
|
2846
|
+
const generation = this.runtimeGeneration;
|
|
2847
|
+
if (this.getUpdatePolicyStatus().restricted) {
|
|
2848
|
+
return { ok: false, error: failure('client-version-unsupported') };
|
|
2849
|
+
}
|
|
1833
2850
|
if (options.allowSignedOut !== true && await this.isSignedOut()) {
|
|
1834
2851
|
return { ok: false, error: failure('signed-out') };
|
|
1835
2852
|
}
|
|
1836
2853
|
const provider = this.provider;
|
|
1837
2854
|
if (provider === undefined)
|
|
1838
2855
|
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();
|
|
2856
|
+
const value = await operation(provider.client);
|
|
2857
|
+
if (this.runtimeGeneration !== generation || this.provider !== provider) {
|
|
2858
|
+
return { ok: false, error: failure('conflict') };
|
|
2859
|
+
}
|
|
1852
2860
|
if (options.skipAttachmentByteValidation !== true && containsUnexpectedBinary(value, new Set())) {
|
|
1853
2861
|
return { ok: false, error: failure('remote') };
|
|
1854
2862
|
}
|
|
@@ -1858,8 +2866,13 @@ let AwikiService = (() => {
|
|
|
1858
2866
|
return { ok: false, error: normalizeFailure(error) };
|
|
1859
2867
|
}
|
|
1860
2868
|
}
|
|
2869
|
+
assertVersionSupported() {
|
|
2870
|
+
if (this.getUpdatePolicyStatus().restricted) {
|
|
2871
|
+
throw new Error('awiki: this plugin version must be updated before using the active tenant');
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
1861
2874
|
/** Validate mail input before entering the provider and preserve fixed public failures. */
|
|
1862
|
-
runValidatedMail(validate, operation
|
|
2875
|
+
runValidatedMail(validate, operation) {
|
|
1863
2876
|
let request;
|
|
1864
2877
|
try {
|
|
1865
2878
|
request = validate();
|
|
@@ -1867,7 +2880,7 @@ let AwikiService = (() => {
|
|
|
1867
2880
|
catch {
|
|
1868
2881
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1869
2882
|
}
|
|
1870
|
-
return this.run(client => operation(client, request)
|
|
2883
|
+
return this.run(client => operation(client, request));
|
|
1871
2884
|
}
|
|
1872
2885
|
/** Read and cache the private Host-owned session marker. */
|
|
1873
2886
|
async isSignedOut() {
|
|
@@ -1918,165 +2931,211 @@ let AwikiService = (() => {
|
|
|
1918
2931
|
}
|
|
1919
2932
|
/** Serialize sign-in, sign-out, and destructive clear transitions. */
|
|
1920
2933
|
mutateSession(operation) {
|
|
1921
|
-
const
|
|
2934
|
+
const generation = this.runtimeGeneration;
|
|
2935
|
+
if (this.tenantSwitching)
|
|
2936
|
+
return Promise.resolve({ ok: false, error: failure('conflict') });
|
|
2937
|
+
const invoke = () => this.tenantSwitching || generation !== this.runtimeGeneration
|
|
2938
|
+
? Promise.resolve({ ok: false, error: failure('conflict') })
|
|
2939
|
+
: operation();
|
|
2940
|
+
const pending = this.sessionMutation.then(invoke, invoke);
|
|
1922
2941
|
this.sessionMutation = pending.then(() => undefined, () => undefined);
|
|
1923
2942
|
return pending;
|
|
1924
2943
|
}
|
|
1925
|
-
/** Start
|
|
1926
|
-
|
|
1927
|
-
if (
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
return
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
return
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
2944
|
+
/** Start identity realtime and the optional Agent consumer without blocking identity success. */
|
|
2945
|
+
ensureProviderRuntime(provider) {
|
|
2946
|
+
if (this.tenantSwitching)
|
|
2947
|
+
return;
|
|
2948
|
+
const identityDid = this.activeIdentityDid;
|
|
2949
|
+
if (provider.runtimeReplacement !== undefined)
|
|
2950
|
+
return;
|
|
2951
|
+
if (identityDid !== undefined
|
|
2952
|
+
&& provider.realtimeIdentityDid !== undefined
|
|
2953
|
+
&& provider.realtimeIdentityDid !== identityDid) {
|
|
2954
|
+
this.replaceProviderRuntime(provider, identityDid);
|
|
2955
|
+
return;
|
|
2956
|
+
}
|
|
2957
|
+
this.ensureRealtimeSupervisor(provider);
|
|
2958
|
+
this.ensureAgentConsumer(provider);
|
|
2959
|
+
}
|
|
2960
|
+
replaceProviderRuntime(provider, identityDid) {
|
|
2961
|
+
if (provider.runtimeReplacement !== undefined || this.provider !== provider)
|
|
2962
|
+
return;
|
|
2963
|
+
const replacement = (async () => {
|
|
2964
|
+
await this.stopProviderRuntime(provider);
|
|
2965
|
+
if (this.provider !== provider || this.activeIdentityDid !== identityDid)
|
|
2966
|
+
return;
|
|
2967
|
+
this.ensureRealtimeSupervisor(provider);
|
|
2968
|
+
this.ensureAgentConsumer(provider);
|
|
2969
|
+
})();
|
|
2970
|
+
const observed = replacement
|
|
2971
|
+
.catch((error) => {
|
|
2972
|
+
this.ctx.logger('awiki-realtime').warn('AWiki identity realtime replacement failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
2973
|
+
})
|
|
2974
|
+
.finally(() => {
|
|
2975
|
+
if (provider.runtimeReplacement === observed)
|
|
2976
|
+
delete provider.runtimeReplacement;
|
|
2977
|
+
if (this.provider === provider)
|
|
2978
|
+
this.ensureProviderRuntime(provider);
|
|
2979
|
+
});
|
|
2980
|
+
provider.runtimeReplacement = observed;
|
|
2981
|
+
}
|
|
2982
|
+
ensureRealtimeSupervisor(provider) {
|
|
2983
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
2984
|
+
return;
|
|
2985
|
+
if (!this.resolved.realtimeEnabled
|
|
2986
|
+
|| provider.realtimeSupervisor !== undefined
|
|
2987
|
+
|| provider.realtimeStartup !== undefined
|
|
2988
|
+
|| this.provider !== provider
|
|
2989
|
+
|| (provider.client.realtime ?? provider.client.listener) === undefined)
|
|
2990
|
+
return;
|
|
2991
|
+
const generation = provider.realtimeGeneration;
|
|
2992
|
+
const source = provider.client.realtime ?? provider.client.listener;
|
|
2993
|
+
if (source === undefined)
|
|
2994
|
+
return;
|
|
2995
|
+
const logger = this.ctx.logger('awiki-realtime');
|
|
1943
2996
|
const startup = (async () => {
|
|
1944
2997
|
if (await this.isSignedOut())
|
|
1945
2998
|
return;
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
if (!this.listenerFenceMatches(provider, workspaceContext, generation))
|
|
2999
|
+
const identity = await provider.client.getIdentity();
|
|
3000
|
+
if (identity === null || !this.realtimeFenceMatches(provider, generation))
|
|
1949
3001
|
return;
|
|
1950
|
-
|
|
1951
|
-
const
|
|
1952
|
-
|
|
1953
|
-
stateRoot: this.activeTenant().stateRoot,
|
|
3002
|
+
this.activeIdentityDid = identity.did;
|
|
3003
|
+
const supervisor = new IdentityRealtimeSupervisor(source, {
|
|
3004
|
+
onSynchronized: cause => this.onRealtimeSynchronized(provider, generation, cause),
|
|
1954
3005
|
}, logger);
|
|
1955
|
-
provider.
|
|
3006
|
+
provider.realtimeSupervisor = supervisor;
|
|
3007
|
+
provider.realtimeIdentityDid = identity.did;
|
|
3008
|
+
supervisor.start();
|
|
3009
|
+
this.ensureAgentConsumer(provider);
|
|
3010
|
+
})();
|
|
3011
|
+
const observed = startup
|
|
3012
|
+
.catch((error) => {
|
|
3013
|
+
logger.warn('AWiki realtime startup failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
3014
|
+
})
|
|
3015
|
+
.finally(() => {
|
|
3016
|
+
if (provider.realtimeStartup === observed)
|
|
3017
|
+
delete provider.realtimeStartup;
|
|
3018
|
+
});
|
|
3019
|
+
provider.realtimeStartup = observed;
|
|
3020
|
+
}
|
|
3021
|
+
realtimeFenceMatches(provider, generation) {
|
|
3022
|
+
return this.provider === provider && provider.realtimeGeneration === generation;
|
|
3023
|
+
}
|
|
3024
|
+
async onRealtimeSynchronized(provider, generation, cause) {
|
|
3025
|
+
if (!this.realtimeFenceMatches(provider, generation))
|
|
3026
|
+
return;
|
|
3027
|
+
if (cause === 'system_notification' || cause === 'stream_recovery') {
|
|
1956
3028
|
try {
|
|
1957
|
-
await
|
|
3029
|
+
provider.localDeviceJoinRequestCountAfterSync = (await provider.client.listLocalDeviceJoinRequests()).length;
|
|
1958
3030
|
}
|
|
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;
|
|
3031
|
+
catch {
|
|
3032
|
+
this.ctx.logger('awiki-realtime').debug('AWiki realtime could not observe local device Join requests');
|
|
1972
3033
|
}
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
3034
|
+
}
|
|
3035
|
+
if (!['session_start', 'connection_ready', 'reconnected', 'message', 'message_update'].includes(cause))
|
|
3036
|
+
return;
|
|
3037
|
+
this.ensureAgentConsumer(provider);
|
|
3038
|
+
await provider.agentConsumerStartup;
|
|
3039
|
+
if (!this.realtimeFenceMatches(provider, generation))
|
|
3040
|
+
return;
|
|
3041
|
+
await provider.agentConsumer?.reconcileOnce();
|
|
3042
|
+
}
|
|
3043
|
+
ensureAgentConsumer(provider) {
|
|
3044
|
+
if (this.getUpdatePolicyStatus().restricted)
|
|
3045
|
+
return;
|
|
3046
|
+
const workspaceContext = this.workspaceContext;
|
|
3047
|
+
const source = provider.client.agentInbox ?? provider.client.listener;
|
|
3048
|
+
const identityDid = this.activeIdentityDid;
|
|
3049
|
+
if (!this.resolved.listenerEnabled
|
|
3050
|
+
|| workspaceContext === undefined
|
|
3051
|
+
|| source === undefined
|
|
3052
|
+
|| identityDid === undefined
|
|
3053
|
+
|| this.provider !== provider)
|
|
3054
|
+
return;
|
|
3055
|
+
if (provider.agentConsumer !== undefined && provider.agentConsumerIdentityDid === identityDid)
|
|
3056
|
+
return;
|
|
3057
|
+
if (provider.agentConsumerStartup !== undefined)
|
|
3058
|
+
return;
|
|
3059
|
+
const generation = ++provider.agentConsumerGeneration;
|
|
3060
|
+
const logger = this.ctx.logger('awiki-listener');
|
|
3061
|
+
const startup = (async () => {
|
|
3062
|
+
await this.detachAgentConsumer(provider);
|
|
3063
|
+
if (!this.agentConsumerFenceMatches(provider, workspaceContext, identityDid, generation))
|
|
1983
3064
|
return;
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
3065
|
+
const agents = new DshAwikiListenerAgentRuntime(workspaceContext, this.resolved.listener.workspacePath);
|
|
3066
|
+
const consumer = new AwikiAgentListener(source, agents, {
|
|
3067
|
+
...this.resolved.listener,
|
|
3068
|
+
stateRoot: this.activeClientOptions?.stateRoot ?? this.resolved.listener.stateRoot,
|
|
3069
|
+
identityScope: identityDid,
|
|
3070
|
+
}, logger);
|
|
3071
|
+
provider.agentConsumer = consumer;
|
|
3072
|
+
provider.agentConsumerIdentityDid = identityDid;
|
|
3073
|
+
await consumer.reconcileOnce();
|
|
1991
3074
|
})();
|
|
1992
3075
|
const observed = startup
|
|
1993
3076
|
.catch((error) => {
|
|
1994
|
-
logger.warn('AWiki
|
|
3077
|
+
logger.warn('AWiki Agent consumer startup failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
1995
3078
|
})
|
|
1996
3079
|
.finally(() => {
|
|
1997
|
-
if (provider.
|
|
1998
|
-
delete provider.
|
|
3080
|
+
if (provider.agentConsumerStartup === observed)
|
|
3081
|
+
delete provider.agentConsumerStartup;
|
|
1999
3082
|
});
|
|
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
|
-
}
|
|
3083
|
+
provider.agentConsumerStartup = observed;
|
|
2025
3084
|
}
|
|
2026
|
-
|
|
3085
|
+
agentConsumerFenceMatches(provider, workspaceContext, identityDid, generation) {
|
|
2027
3086
|
return this.provider === provider
|
|
2028
3087
|
&& this.workspaceContext === workspaceContext
|
|
2029
|
-
&&
|
|
3088
|
+
&& this.activeIdentityDid === identityDid
|
|
3089
|
+
&& provider.agentConsumerGeneration === generation;
|
|
2030
3090
|
}
|
|
2031
|
-
|
|
2032
|
-
|
|
3091
|
+
async detachAgentConsumer(provider) {
|
|
3092
|
+
const consumer = provider.agentConsumer;
|
|
3093
|
+
if (consumer === undefined)
|
|
2033
3094
|
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
|
-
});
|
|
3095
|
+
delete provider.agentConsumer;
|
|
3096
|
+
delete provider.agentConsumerIdentityDid;
|
|
3097
|
+
const cleanup = consumer.dispose();
|
|
2048
3098
|
const observed = cleanup.finally(() => {
|
|
2049
|
-
if (provider.
|
|
2050
|
-
delete provider.
|
|
2051
|
-
if (disposed)
|
|
2052
|
-
this.scheduleListenerRestart(provider, workspaceContext, generation);
|
|
3099
|
+
if (provider.agentConsumerCleanup === observed)
|
|
3100
|
+
delete provider.agentConsumerCleanup;
|
|
2053
3101
|
});
|
|
2054
|
-
provider.
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
3102
|
+
provider.agentConsumerCleanup = observed;
|
|
3103
|
+
await observed;
|
|
3104
|
+
}
|
|
3105
|
+
async stopAgentConsumer(provider) {
|
|
3106
|
+
provider.agentConsumerGeneration += 1;
|
|
3107
|
+
await provider.agentConsumerStartup;
|
|
3108
|
+
await provider.agentConsumerCleanup;
|
|
3109
|
+
await this.detachAgentConsumer(provider);
|
|
3110
|
+
}
|
|
3111
|
+
async stopRealtimeSupervisor(provider) {
|
|
3112
|
+
provider.realtimeGeneration += 1;
|
|
3113
|
+
await provider.realtimeStartup;
|
|
3114
|
+
const supervisor = provider.realtimeSupervisor;
|
|
3115
|
+
if (supervisor === undefined)
|
|
2059
3116
|
return;
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
void this.startListener(provider);
|
|
2070
|
-
}, delay);
|
|
2071
|
-
provider.listenerRestartTimer = timer;
|
|
3117
|
+
delete provider.realtimeSupervisor;
|
|
3118
|
+
delete provider.realtimeIdentityDid;
|
|
3119
|
+
await supervisor.dispose();
|
|
3120
|
+
}
|
|
3121
|
+
async stopProviderRuntime(provider) {
|
|
3122
|
+
await Promise.all([
|
|
3123
|
+
this.stopRealtimeSupervisor(provider),
|
|
3124
|
+
this.stopAgentConsumer(provider),
|
|
3125
|
+
]);
|
|
2072
3126
|
}
|
|
2073
3127
|
/** Clear one exact provider slot before joining its one shared disposal. */
|
|
2074
3128
|
disposeProvider(provider) {
|
|
2075
|
-
if (this.provider === provider)
|
|
3129
|
+
if (this.provider === provider) {
|
|
2076
3130
|
this.provider = undefined;
|
|
3131
|
+
this.pendingDeviceJoin = undefined;
|
|
3132
|
+
this.activeDeviceJoinSessionId = undefined;
|
|
3133
|
+
this.invalidateSummaries();
|
|
3134
|
+
}
|
|
2077
3135
|
provider.disposal ??= (async () => {
|
|
2078
3136
|
try {
|
|
2079
|
-
await
|
|
3137
|
+
await provider.runtimeReplacement;
|
|
3138
|
+
await this.stopProviderRuntime(provider);
|
|
2080
3139
|
}
|
|
2081
3140
|
finally {
|
|
2082
3141
|
await provider.client.dispose();
|