@awiki/dsh-plugin 0.3.17 → 0.3.19
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 +4 -0
- package/README.zh.md +11 -0
- package/THIRD_PARTY_NOTICES.md +6 -0
- package/lib/client.js +4012 -671
- package/lib/client.js.map +1 -1
- package/lib/index.js +603 -61
- package/lib/{sdk-adapter-DGcTiEZz.mjs → package-version.generated-nZcAVnm8.mjs} +103 -10
- package/lib/provider.js +5 -3
- package/lib/typert.host.js +553 -133
- package/lib/typert.remote-client.d.ts +11 -3
- package/lib/typert.remote-client.d.ts.map +1 -1
- package/lib/typert.remote-client.js +553 -133
- package/lib/types/backend-capabilities.d.ts +12 -0
- package/lib/types/backend-capabilities.d.ts.map +1 -0
- package/lib/types/backend-capabilities.js +48 -0
- package/lib/types/backend-capabilities.js.map +1 -0
- package/lib/types/client/AwikiAvatar.d.ts +40 -0
- package/lib/types/client/AwikiAvatar.d.ts.map +1 -0
- package/lib/types/client/AwikiAvatar.js +201 -0
- package/lib/types/client/AwikiAvatar.js.map +1 -0
- package/lib/types/client/AwikiAvatarEditor.d.ts +11 -0
- package/lib/types/client/AwikiAvatarEditor.d.ts.map +1 -0
- package/lib/types/client/AwikiAvatarEditor.js +113 -0
- package/lib/types/client/AwikiAvatarEditor.js.map +1 -0
- package/lib/types/client/AwikiAvatarPreview.d.ts +8 -0
- package/lib/types/client/AwikiAvatarPreview.d.ts.map +1 -0
- package/lib/types/client/AwikiAvatarPreview.js +68 -0
- package/lib/types/client/AwikiAvatarPreview.js.map +1 -0
- package/lib/types/client/AwikiGroupDetails.js +2 -2
- package/lib/types/client/AwikiGroupDetails.js.map +1 -1
- package/lib/types/client/AwikiIdentityAccess.d.ts +1 -0
- package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
- package/lib/types/client/AwikiIdentityAccess.js +21 -4
- package/lib/types/client/AwikiIdentityAccess.js.map +1 -1
- package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
- package/lib/types/client/AwikiOverlay.js +97 -29
- package/lib/types/client/AwikiOverlay.js.map +1 -1
- package/lib/types/client/AwikiProfileCard.d.ts +1 -1
- package/lib/types/client/AwikiProfileCard.d.ts.map +1 -1
- package/lib/types/client/AwikiProfileCard.js +6 -2
- package/lib/types/client/AwikiProfileCard.js.map +1 -1
- package/lib/types/client/AwikiSettingsSection.d.ts +1 -0
- package/lib/types/client/AwikiSettingsSection.d.ts.map +1 -1
- package/lib/types/client/AwikiSettingsSection.js +5 -4
- package/lib/types/client/AwikiSettingsSection.js.map +1 -1
- package/lib/types/client/avatar-cache.d.ts +24 -0
- package/lib/types/client/avatar-cache.d.ts.map +1 -0
- package/lib/types/client/avatar-cache.js +336 -0
- package/lib/types/client/avatar-cache.js.map +1 -0
- package/lib/types/client/avatar-dialog.d.ts +3 -0
- package/lib/types/client/avatar-dialog.d.ts.map +1 -0
- package/lib/types/client/avatar-dialog.js +18 -0
- package/lib/types/client/avatar-dialog.js.map +1 -0
- package/lib/types/client/avatar-image.d.ts +26 -0
- package/lib/types/client/avatar-image.d.ts.map +1 -0
- package/lib/types/client/avatar-image.js +193 -0
- package/lib/types/client/avatar-image.js.map +1 -0
- package/lib/types/client/controller.d.ts +37 -4
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +306 -59
- package/lib/types/client/controller.js.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +11 -2
- package/lib/types/client/index.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 +26 -2
- package/lib/types/client/settings-locales.js.map +1 -1
- package/lib/types/client/slots.d.ts +12 -3
- package/lib/types/client/slots.d.ts.map +1 -1
- package/lib/types/conversation-preferences.d.ts.map +1 -1
- package/lib/types/conversation-preferences.js +27 -0
- package/lib/types/conversation-preferences.js.map +1 -1
- package/lib/types/index.d.ts +26 -3
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +321 -33
- package/lib/types/index.js.map +1 -1
- package/lib/types/listener.d.ts.map +1 -1
- package/lib/types/listener.js +9 -8
- package/lib/types/listener.js.map +1 -1
- package/lib/types/package-version.generated.d.ts +2 -2
- package/lib/types/package-version.generated.js +2 -2
- package/lib/types/provider-api.d.ts +28 -3
- package/lib/types/provider-api.d.ts.map +1 -1
- package/lib/types/provider.d.ts.map +1 -1
- package/lib/types/provider.js +5 -2
- package/lib/types/provider.js.map +1 -1
- package/lib/types/sdk-adapter.d.ts +10 -3
- package/lib/types/sdk-adapter.d.ts.map +1 -1
- package/lib/types/sdk-adapter.js +78 -7
- package/lib/types/sdk-adapter.js.map +1 -1
- package/lib/types/tenant-capability-cache.d.ts +15 -0
- package/lib/types/tenant-capability-cache.d.ts.map +1 -0
- package/lib/types/tenant-capability-cache.js +45 -0
- package/lib/types/tenant-capability-cache.js.map +1 -0
- package/lib/types/tenant-registry.d.ts +2 -0
- package/lib/types/tenant-registry.d.ts.map +1 -1
- package/lib/types/tenant-registry.js +83 -16
- package/lib/types/tenant-registry.js.map +1 -1
- package/lib/types/types.d.ts +70 -7
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js.map +1 -1
- package/lib/types/update-policy.d.ts +2 -2
- package/lib/types/update-policy.d.ts.map +1 -1
- package/lib/types/update-policy.js +10 -1
- package/lib/types/update-policy.js.map +1 -1
- package/package.json +33 -34
package/lib/types/index.js
CHANGED
|
@@ -32,6 +32,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
32
32
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
33
|
done = true;
|
|
34
34
|
};
|
|
35
|
+
import { TenantCapabilityCache } from "./tenant-capability-cache.js";
|
|
36
|
+
import { backendAttachmentMaxBytes, projectBackendFeatures, projectBackendSnapshot, requireBackendFeature } from "./backend-capabilities.js";
|
|
35
37
|
export { registerAwikiLoopbackRpc } from "./settings-transport.js";
|
|
36
38
|
import { registerAwikiSettingsTransport } from "./settings-transport.js";
|
|
37
39
|
import { registrationHandleLocalPart } from "./registration-policy.js";
|
|
@@ -115,6 +117,8 @@ export const Config = z.object({
|
|
|
115
117
|
summaryMaxInputBytes: z.number().default(DEFAULT_SUMMARY_MAX_INPUT_BYTES),
|
|
116
118
|
});
|
|
117
119
|
const FAILURE_CODES = new Set([
|
|
120
|
+
'backend-not-supported',
|
|
121
|
+
'backend-unconfirmed',
|
|
118
122
|
'client-version-unsupported',
|
|
119
123
|
'not-registered',
|
|
120
124
|
'signed-out',
|
|
@@ -132,6 +136,7 @@ const FAILURE_CODES = new Set([
|
|
|
132
136
|
'state-in-use',
|
|
133
137
|
'rate-limited',
|
|
134
138
|
'group-membership-required',
|
|
139
|
+
'group-member-encryption-not-ready',
|
|
135
140
|
'group-identity-stale',
|
|
136
141
|
'attachment-too-large',
|
|
137
142
|
'summary-unavailable',
|
|
@@ -154,6 +159,8 @@ const RESUMABLE_JOIN_PHASES = new Set([
|
|
|
154
159
|
'approval_prepared',
|
|
155
160
|
]);
|
|
156
161
|
const FAILURE_MESSAGES = {
|
|
162
|
+
'backend-not-supported': 'This server does not support this feature.',
|
|
163
|
+
'backend-unconfirmed': 'Server features are not confirmed yet. Please retry.',
|
|
157
164
|
'client-version-unsupported': 'This AWiki plugin version is not supported by the active tenant. Update the plugin or switch tenants.',
|
|
158
165
|
'not-registered': 'No AWiki identity is registered for this deployment.',
|
|
159
166
|
'signed-out': 'This installation is signed out of AWiki.',
|
|
@@ -170,6 +177,7 @@ const FAILURE_MESSAGES = {
|
|
|
170
177
|
'conflict': 'The AWiki operation conflicts with current state.',
|
|
171
178
|
'state-in-use': 'AWiki data is already open in another client.',
|
|
172
179
|
'rate-limited': 'The AWiki service rate-limited the request.',
|
|
180
|
+
'group-member-encryption-not-ready': 'The member has not prepared encrypted messaging.',
|
|
173
181
|
'group-membership-required': 'The active AWiki identity is not a member of this group.',
|
|
174
182
|
'group-identity-stale': 'The AWiki group identity binding is still recovering.',
|
|
175
183
|
'attachment-too-large': 'The attachment exceeds this deployment\'s size limit.',
|
|
@@ -272,7 +280,7 @@ function publishedServiceBaseUrl(services, name, allowInsecureLoopbackForTesting
|
|
|
272
280
|
if (typeof candidate !== 'object' || candidate === null || Array.isArray(candidate))
|
|
273
281
|
return undefined;
|
|
274
282
|
const value = candidate;
|
|
275
|
-
if (value.enabled !== true || typeof value.base_url !== 'string')
|
|
283
|
+
if (value.enabled !== true || value.protocol_version !== 1 || typeof value.base_url !== 'string')
|
|
276
284
|
return undefined;
|
|
277
285
|
try {
|
|
278
286
|
const baseUrl = serviceUrl(`server-info.services.${name}.base_url`, value.base_url, allowInsecureLoopbackForTesting);
|
|
@@ -470,6 +478,10 @@ const MAX_PROFILE_TAGS = 5;
|
|
|
470
478
|
const MAX_PROFILE_TAG_CHARACTERS = 30;
|
|
471
479
|
const MAX_MESSAGE_CHARACTERS = 20_000;
|
|
472
480
|
function normalizeCreateGroupRequest(request) {
|
|
481
|
+
if (request?.operationId !== undefined && (typeof request.operationId !== 'string' || !/^[a-zA-Z0-9_-]{1,100}$/.test(request.operationId)))
|
|
482
|
+
return undefined;
|
|
483
|
+
if (request?.encrypted !== undefined && typeof request.encrypted !== 'boolean')
|
|
484
|
+
return undefined;
|
|
473
485
|
if (typeof request?.name !== 'string' || !Array.isArray(request.members))
|
|
474
486
|
return undefined;
|
|
475
487
|
const name = request.name.trim();
|
|
@@ -490,7 +502,7 @@ function normalizeCreateGroupRequest(request) {
|
|
|
490
502
|
seen.add(member);
|
|
491
503
|
members.push(member);
|
|
492
504
|
}
|
|
493
|
-
return { name, members };
|
|
505
|
+
return { name, members, ...request.operationId === undefined ? {} : { operationId: request.operationId }, ...request.encrypted === undefined ? {} : { encrypted: request.encrypted } };
|
|
494
506
|
}
|
|
495
507
|
function normalizeMember(value) {
|
|
496
508
|
if (typeof value !== 'string')
|
|
@@ -505,6 +517,24 @@ function normalizeGroupDid(value) {
|
|
|
505
517
|
return undefined;
|
|
506
518
|
return value;
|
|
507
519
|
}
|
|
520
|
+
function validAvatarRequest(request, image) {
|
|
521
|
+
if (request === null || typeof request !== 'object' || Array.isArray(request))
|
|
522
|
+
return false;
|
|
523
|
+
const value = request;
|
|
524
|
+
const keys = image ? ['requestId', 'expectedProfileVersion', 'imageBase64'] : ['requestId', 'expectedProfileVersion'];
|
|
525
|
+
if (Object.keys(value).length !== keys.length || Object.keys(value).some(key => !keys.includes(key)))
|
|
526
|
+
return false;
|
|
527
|
+
if (typeof value.requestId !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u.test(value.requestId))
|
|
528
|
+
return false;
|
|
529
|
+
if (typeof value.expectedProfileVersion !== 'string' || !/^(0|[1-9][0-9]{0,18})$/u.test(value.expectedProfileVersion) || BigInt(value.expectedProfileVersion) > 9223372036854775807n)
|
|
530
|
+
return false;
|
|
531
|
+
if (!image)
|
|
532
|
+
return true;
|
|
533
|
+
if (typeof value.imageBase64 !== 'string' || value.imageBase64.length === 0 || value.imageBase64.length > 699052)
|
|
534
|
+
return false;
|
|
535
|
+
const bytes = Buffer.from(value.imageBase64, 'base64');
|
|
536
|
+
return bytes.length <= 512 * 1024 && bytes.length >= 4 && bytes[0] === 255 && bytes[1] === 216 && bytes[bytes.length - 2] === 255 && bytes[bytes.length - 1] === 217 && bytes.toString('base64') === value.imageBase64;
|
|
537
|
+
}
|
|
508
538
|
function normalizeProfileRequest(request) {
|
|
509
539
|
if (typeof request?.displayName !== 'string' || typeof request.bio !== 'string' || !Array.isArray(request.tags))
|
|
510
540
|
return undefined;
|
|
@@ -617,6 +647,8 @@ function identityAccessTarget(request, configuredDomain) {
|
|
|
617
647
|
return { localPart, fullHandle: `${localPart}.${configuredDomain}` };
|
|
618
648
|
}
|
|
619
649
|
function normalizeSendTextRequest(request) {
|
|
650
|
+
if (request?.security !== undefined && request.security !== 'plain' && request.security !== 'e2ee-required')
|
|
651
|
+
return undefined;
|
|
620
652
|
if (typeof request?.text !== 'string' || typeof request.idempotencyKey !== 'string'
|
|
621
653
|
|| !isMessageTarget(request.target))
|
|
622
654
|
return undefined;
|
|
@@ -778,6 +810,7 @@ let AwikiService = (() => {
|
|
|
778
810
|
let _classSuper = TypertRemoteService;
|
|
779
811
|
let _instanceExtraInitializers = [];
|
|
780
812
|
let _getConfig_decorators;
|
|
813
|
+
let _refreshBackendCapabilities_decorators;
|
|
781
814
|
let _getIntegration_decorators;
|
|
782
815
|
let _createIntegration_decorators;
|
|
783
816
|
let _updateIntegration_decorators;
|
|
@@ -808,6 +841,8 @@ let AwikiService = (() => {
|
|
|
808
841
|
let _prepareRootTransfer_decorators;
|
|
809
842
|
let _confirmRootTransfer_decorators;
|
|
810
843
|
let _updateDisplayName_decorators;
|
|
844
|
+
let _setAvatar_decorators;
|
|
845
|
+
let _clearAvatar_decorators;
|
|
811
846
|
let _getProfile_decorators;
|
|
812
847
|
let _updateProfile_decorators;
|
|
813
848
|
let _sendRecoveryOtp_decorators;
|
|
@@ -820,6 +855,7 @@ let AwikiService = (() => {
|
|
|
820
855
|
let _resolvePeer_decorators;
|
|
821
856
|
let _createGroup_decorators;
|
|
822
857
|
let _getGroup_decorators;
|
|
858
|
+
let _repairGroupSecurity_decorators;
|
|
823
859
|
let _joinGroup_decorators;
|
|
824
860
|
let _leaveGroup_decorators;
|
|
825
861
|
let _getDisplayProfiles_decorators;
|
|
@@ -847,6 +883,7 @@ let AwikiService = (() => {
|
|
|
847
883
|
static {
|
|
848
884
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
849
885
|
_getConfig_decorators = [Remote];
|
|
886
|
+
_refreshBackendCapabilities_decorators = [Remote];
|
|
850
887
|
_getIntegration_decorators = [Remote];
|
|
851
888
|
_createIntegration_decorators = [Remote];
|
|
852
889
|
_updateIntegration_decorators = [Remote];
|
|
@@ -877,6 +914,8 @@ let AwikiService = (() => {
|
|
|
877
914
|
_prepareRootTransfer_decorators = [Remote];
|
|
878
915
|
_confirmRootTransfer_decorators = [Remote];
|
|
879
916
|
_updateDisplayName_decorators = [Remote];
|
|
917
|
+
_setAvatar_decorators = [Remote];
|
|
918
|
+
_clearAvatar_decorators = [Remote];
|
|
880
919
|
_getProfile_decorators = [Remote];
|
|
881
920
|
_updateProfile_decorators = [Remote];
|
|
882
921
|
_sendRecoveryOtp_decorators = [Remote];
|
|
@@ -889,6 +928,7 @@ let AwikiService = (() => {
|
|
|
889
928
|
_resolvePeer_decorators = [Remote];
|
|
890
929
|
_createGroup_decorators = [Remote];
|
|
891
930
|
_getGroup_decorators = [Remote];
|
|
931
|
+
_repairGroupSecurity_decorators = [Remote];
|
|
892
932
|
_joinGroup_decorators = [Remote];
|
|
893
933
|
_leaveGroup_decorators = [Remote];
|
|
894
934
|
_getDisplayProfiles_decorators = [Remote];
|
|
@@ -913,6 +953,7 @@ let AwikiService = (() => {
|
|
|
913
953
|
_downloadMailAttachment_decorators = [Remote];
|
|
914
954
|
_clearLocalData_decorators = [Remote];
|
|
915
955
|
__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);
|
|
956
|
+
__esDecorate(this, null, _refreshBackendCapabilities_decorators, { kind: "method", name: "refreshBackendCapabilities", static: false, private: false, access: { has: obj => "refreshBackendCapabilities" in obj, get: obj => obj.refreshBackendCapabilities }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
916
957
|
__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);
|
|
917
958
|
__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);
|
|
918
959
|
__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);
|
|
@@ -943,6 +984,8 @@ let AwikiService = (() => {
|
|
|
943
984
|
__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);
|
|
944
985
|
__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);
|
|
945
986
|
__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);
|
|
987
|
+
__esDecorate(this, null, _setAvatar_decorators, { kind: "method", name: "setAvatar", static: false, private: false, access: { has: obj => "setAvatar" in obj, get: obj => obj.setAvatar }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
988
|
+
__esDecorate(this, null, _clearAvatar_decorators, { kind: "method", name: "clearAvatar", static: false, private: false, access: { has: obj => "clearAvatar" in obj, get: obj => obj.clearAvatar }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
946
989
|
__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);
|
|
947
990
|
__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);
|
|
948
991
|
__esDecorate(this, null, _sendRecoveryOtp_decorators, { kind: "method", name: "sendRecoveryOtp", static: false, private: false, access: { has: obj => "sendRecoveryOtp" in obj, get: obj => obj.sendRecoveryOtp }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -955,6 +998,7 @@ let AwikiService = (() => {
|
|
|
955
998
|
__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);
|
|
956
999
|
__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);
|
|
957
1000
|
__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);
|
|
1001
|
+
__esDecorate(this, null, _repairGroupSecurity_decorators, { kind: "method", name: "repairGroupSecurity", static: false, private: false, access: { has: obj => "repairGroupSecurity" in obj, get: obj => obj.repairGroupSecurity }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
958
1002
|
__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);
|
|
959
1003
|
__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);
|
|
960
1004
|
__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);
|
|
@@ -997,6 +1041,15 @@ let AwikiService = (() => {
|
|
|
997
1041
|
tenantSwitching = false;
|
|
998
1042
|
tenantParticipants = new Set();
|
|
999
1043
|
activeCapabilities;
|
|
1044
|
+
backendFeatures = {};
|
|
1045
|
+
backendDiagnostics = {};
|
|
1046
|
+
backendFeaturesTask;
|
|
1047
|
+
backendFeaturesRevision = -1;
|
|
1048
|
+
backendSubscriptions = new Map();
|
|
1049
|
+
backendSubscriptionPending = new Set();
|
|
1050
|
+
backendAttachmentLimit;
|
|
1051
|
+
capabilityDiscoveryTask;
|
|
1052
|
+
capabilityDiscoveryAt = 0;
|
|
1000
1053
|
updatePolicyStatus;
|
|
1001
1054
|
updatePolicyRequest;
|
|
1002
1055
|
updatePolicyTask;
|
|
@@ -1097,6 +1150,7 @@ let AwikiService = (() => {
|
|
|
1097
1150
|
registerAwikiTools(ctx, this);
|
|
1098
1151
|
ctx.effect(() => async () => {
|
|
1099
1152
|
this.updatePolicyRequest?.abort();
|
|
1153
|
+
this.stopBackendSubscriptions();
|
|
1100
1154
|
const provider = this.provider;
|
|
1101
1155
|
if (provider !== undefined)
|
|
1102
1156
|
await this.disposeProvider(provider);
|
|
@@ -1196,12 +1250,38 @@ let AwikiService = (() => {
|
|
|
1196
1250
|
this.conversationPreferenceStore = new AwikiConversationPreferenceStore(options.stateRoot);
|
|
1197
1251
|
this.mailListClient = new AwikiMailListClient(options.mailServiceUrl, this.externalHttpAuth);
|
|
1198
1252
|
this.integrationClient = undefined;
|
|
1253
|
+
this.stopBackendSubscriptions();
|
|
1254
|
+
this.backendFeatures = {};
|
|
1255
|
+
this.backendDiagnostics = {};
|
|
1256
|
+
this.backendFeaturesRevision = -1;
|
|
1257
|
+
this.backendFeaturesTask = undefined;
|
|
1258
|
+
this.backendAttachmentLimit = undefined;
|
|
1259
|
+
this.capabilityDiscoveryTask = undefined;
|
|
1260
|
+
this.capabilityDiscoveryAt = 0;
|
|
1199
1261
|
this.activeCapabilities = {
|
|
1200
1262
|
tenantId: tenant.tenantId,
|
|
1201
1263
|
generation: this.runtimeGeneration,
|
|
1202
1264
|
online: false,
|
|
1203
1265
|
handleRecoveryPhoneEnabled: false,
|
|
1204
1266
|
};
|
|
1267
|
+
const metadata = new TenantCapabilityCache(options.stateRoot, options.userServiceUrl).read(value => {
|
|
1268
|
+
const services = {
|
|
1269
|
+
model_proxy: { enabled: true, protocol_version: 1, base_url: value.modelProxyBaseUrl },
|
|
1270
|
+
guest_gateway: { enabled: true, protocol_version: 1, base_url: value.guestGatewayBaseUrl },
|
|
1271
|
+
};
|
|
1272
|
+
const modelProxyBaseUrl = publishedServiceBaseUrl(services, 'model_proxy', this.resolved.allowInsecureLoopbackForTesting);
|
|
1273
|
+
const guestGatewayBaseUrl = publishedServiceBaseUrl(services, 'guest_gateway', false);
|
|
1274
|
+
return {
|
|
1275
|
+
handleRecoveryPhoneEnabled: value.handleRecoveryPhoneEnabled,
|
|
1276
|
+
...modelProxyBaseUrl === undefined ? {} : { modelProxyBaseUrl },
|
|
1277
|
+
...guestGatewayBaseUrl === undefined ? {} : { guestGatewayBaseUrl },
|
|
1278
|
+
};
|
|
1279
|
+
});
|
|
1280
|
+
if (metadata !== undefined) {
|
|
1281
|
+
this.activeCapabilities = { ...this.activeCapabilities, ...metadata, online: true };
|
|
1282
|
+
this.integrationClient = metadata.guestGatewayBaseUrl === undefined ? undefined
|
|
1283
|
+
: new AwikiIntegrationClient(metadata.guestGatewayBaseUrl, this.externalHttpAuth);
|
|
1284
|
+
}
|
|
1205
1285
|
this.signedOut = undefined;
|
|
1206
1286
|
this.activeIdentityDid = undefined;
|
|
1207
1287
|
this.pendingDeviceJoin = undefined;
|
|
@@ -1380,6 +1460,12 @@ let AwikiService = (() => {
|
|
|
1380
1460
|
let committed = false;
|
|
1381
1461
|
try {
|
|
1382
1462
|
await this.sessionMutation;
|
|
1463
|
+
// Lock both scopes before disposal/open. A crash or rollback must not log in.
|
|
1464
|
+
await this.sessionStore.signOut();
|
|
1465
|
+
this.signedOut = true;
|
|
1466
|
+
this.sessionRevision += 1;
|
|
1467
|
+
this.activeIdentityDid = undefined;
|
|
1468
|
+
await new AwikiSessionStore(registry.stateRoot(target)).signOut();
|
|
1383
1469
|
this.invalidateSummaries();
|
|
1384
1470
|
for (const participant of this.tenantParticipants) {
|
|
1385
1471
|
await participant.prepareSwitch(context);
|
|
@@ -1390,8 +1476,14 @@ let AwikiService = (() => {
|
|
|
1390
1476
|
targetOpened = true;
|
|
1391
1477
|
// The factory returns an adapter immediately; its native Core opens asynchronously.
|
|
1392
1478
|
// A local identity read proves both open and Registry loading before committing the switch.
|
|
1393
|
-
await targetProvider.client.getIdentity();
|
|
1394
|
-
|
|
1479
|
+
const targetIdentity = await targetProvider.client.getIdentity();
|
|
1480
|
+
if (targetIdentity === null) {
|
|
1481
|
+
// An empty scope has no session to restore. Keep the normal registration
|
|
1482
|
+
// entry available; only scopes with retained credentials need the lock.
|
|
1483
|
+
await this.sessionStore.signIn();
|
|
1484
|
+
this.signedOut = false;
|
|
1485
|
+
}
|
|
1486
|
+
void Promise.all([this.refreshTenantCapabilities(), this.refreshUpdatePolicy()]).catch(() => undefined);
|
|
1395
1487
|
registry.commitActive(target.tenantId);
|
|
1396
1488
|
committed = true;
|
|
1397
1489
|
for (const participant of prepared)
|
|
@@ -1491,17 +1583,22 @@ let AwikiService = (() => {
|
|
|
1491
1583
|
*/
|
|
1492
1584
|
async getConfig() {
|
|
1493
1585
|
this.ensureTenantOptions();
|
|
1494
|
-
const
|
|
1495
|
-
if (this.
|
|
1496
|
-
|| capabilities.generation !== this.runtimeGeneration
|
|
1497
|
-
|| capabilities.tenantId !== this.activeTenant?.tenantId) {
|
|
1586
|
+
const requestedGeneration = this.runtimeGeneration;
|
|
1587
|
+
if (requestedGeneration !== this.runtimeGeneration)
|
|
1498
1588
|
return { ok: false, error: failure('conflict') };
|
|
1499
|
-
|
|
1589
|
+
// Config and local session/history reads never join discovery's network wait.
|
|
1590
|
+
const capabilities = this.activeCapabilities;
|
|
1591
|
+
this.refreshBackendProjection();
|
|
1592
|
+
void this.refreshTenantCapabilities(false).catch(() => undefined);
|
|
1593
|
+
const serverFeatures = this.backendFeatures;
|
|
1594
|
+
const attachmentMaxBytes = this.backendAttachmentLimit ?? this.resolved.attachmentMaxBytes;
|
|
1500
1595
|
return {
|
|
1501
1596
|
ok: true,
|
|
1502
1597
|
value: {
|
|
1598
|
+
serverFeatures,
|
|
1599
|
+
serverFeatureDiagnostics: this.backendDiagnostics,
|
|
1503
1600
|
pollIntervalMs: this.resolved.pollIntervalMs,
|
|
1504
|
-
attachmentMaxBytes
|
|
1601
|
+
attachmentMaxBytes,
|
|
1505
1602
|
mailAttachmentMaxCount: this.resolved.mailAttachmentMaxCount,
|
|
1506
1603
|
mailAttachmentMaxBytes: Math.min(this.resolved.mailAttachmentMaxBytes, MAIL_HTTP_UPLOAD_MAX_BYTES),
|
|
1507
1604
|
mailAttachmentDownloadMaxBytes: this.resolved.mailAttachmentMaxBytes,
|
|
@@ -1511,6 +1608,7 @@ let AwikiService = (() => {
|
|
|
1511
1608
|
? {}
|
|
1512
1609
|
: { integrationGuideUrl: new URL('/guest/guide/integration', capabilities.guestGatewayBaseUrl).toString() },
|
|
1513
1610
|
tenantId: capabilities.tenantId,
|
|
1611
|
+
tenantStorageScopeId: this.activeTenant.storageScopeId,
|
|
1514
1612
|
tenantGeneration: capabilities.generation,
|
|
1515
1613
|
tenantOnline: capabilities.online,
|
|
1516
1614
|
services: {
|
|
@@ -1520,6 +1618,15 @@ let AwikiService = (() => {
|
|
|
1520
1618
|
},
|
|
1521
1619
|
};
|
|
1522
1620
|
}
|
|
1621
|
+
/** Explicit discovery retry; ordinary config reads retain their zero-argument contract. */
|
|
1622
|
+
async refreshBackendCapabilities() {
|
|
1623
|
+
this.ensureTenantOptions();
|
|
1624
|
+
const generation = this.runtimeGeneration;
|
|
1625
|
+
await Promise.allSettled([this.refreshTenantCapabilities(true), this.refreshBackendProjection(true)]);
|
|
1626
|
+
if (generation !== this.runtimeGeneration)
|
|
1627
|
+
return { ok: false, error: failure('conflict') };
|
|
1628
|
+
return this.getConfig();
|
|
1629
|
+
}
|
|
1523
1630
|
/** Trusted same-process capability binding for optional tenant participants. */
|
|
1524
1631
|
getTenantCapabilities() {
|
|
1525
1632
|
const capabilities = this.activeCapabilities;
|
|
@@ -1528,9 +1635,124 @@ let AwikiService = (() => {
|
|
|
1528
1635
|
return { ...capabilities };
|
|
1529
1636
|
}
|
|
1530
1637
|
/** Refresh the active tenant's optional service advertisement without affecting Core availability. */
|
|
1531
|
-
refreshTenantCapabilities() {
|
|
1638
|
+
refreshTenantCapabilities(force = true) {
|
|
1532
1639
|
this.ensureTenantOptions();
|
|
1533
|
-
|
|
1640
|
+
if (this.capabilityDiscoveryTask !== undefined)
|
|
1641
|
+
return this.capabilityDiscoveryTask;
|
|
1642
|
+
if (!force && Date.now() - this.capabilityDiscoveryAt < 300_000 && this.activeCapabilities !== undefined)
|
|
1643
|
+
return Promise.resolve(this.activeCapabilities);
|
|
1644
|
+
const generation = this.runtimeGeneration;
|
|
1645
|
+
const task = this.discoverTenantCapabilities(10_000).finally(() => {
|
|
1646
|
+
if (this.runtimeGeneration === generation && this.capabilityDiscoveryTask === task) {
|
|
1647
|
+
this.capabilityDiscoveryTask = undefined;
|
|
1648
|
+
this.capabilityDiscoveryAt = Date.now();
|
|
1649
|
+
}
|
|
1650
|
+
});
|
|
1651
|
+
this.capabilityDiscoveryTask = task;
|
|
1652
|
+
return task;
|
|
1653
|
+
}
|
|
1654
|
+
refreshBackendProjection(force = false) {
|
|
1655
|
+
const provider = this.provider;
|
|
1656
|
+
if (provider === undefined)
|
|
1657
|
+
return Promise.resolve();
|
|
1658
|
+
if (this.backendFeaturesTask !== undefined)
|
|
1659
|
+
return this.backendFeaturesTask;
|
|
1660
|
+
const revision = this.sessionRevision;
|
|
1661
|
+
const generation = this.runtimeGeneration;
|
|
1662
|
+
if (this.backendFeaturesRevision !== revision) {
|
|
1663
|
+
// Only public facts survive a sign-out or identity change in this tenant.
|
|
1664
|
+
this.backendFeatures = Object.fromEntries(Object.entries(this.backendFeatures).filter(([key]) => key.includes('.')));
|
|
1665
|
+
this.backendAttachmentLimit = undefined;
|
|
1666
|
+
this.backendDiagnostics = this.backendDiagnostics.public === undefined ? {} : { public: this.backendDiagnostics.public };
|
|
1667
|
+
this.backendFeaturesRevision = revision;
|
|
1668
|
+
}
|
|
1669
|
+
const current = () => provider === this.provider && revision === this.sessionRevision && generation === this.runtimeGeneration;
|
|
1670
|
+
const task = (async () => {
|
|
1671
|
+
const authenticated = !await this.isSignedOut() && await provider.client.getIdentity() !== null;
|
|
1672
|
+
await Promise.all([false, ...(authenticated ? [true] : [])].map(async (local) => {
|
|
1673
|
+
try {
|
|
1674
|
+
this.observeBackendProjection(provider, local, current);
|
|
1675
|
+
const snapshot = await provider.client.backendCapabilities?.(local, force);
|
|
1676
|
+
if (!current())
|
|
1677
|
+
return;
|
|
1678
|
+
if (snapshot === undefined)
|
|
1679
|
+
return;
|
|
1680
|
+
// A subscribed source is authoritative: a query started before a
|
|
1681
|
+
// disable notification must never restore its older result.
|
|
1682
|
+
if (this.backendSubscriptions.has(local))
|
|
1683
|
+
return;
|
|
1684
|
+
this.recordBackendDiagnostics(local, snapshot);
|
|
1685
|
+
if (snapshot.status === 'unavailable')
|
|
1686
|
+
return;
|
|
1687
|
+
this.backendFeatures = { ...this.backendFeatures, ...projectBackendSnapshot(snapshot, local) };
|
|
1688
|
+
if (local)
|
|
1689
|
+
this.backendAttachmentLimit = backendAttachmentMaxBytes(snapshot, this.resolved.attachmentMaxBytes);
|
|
1690
|
+
if (this.backendFeatures['agents.managed_agents'] === 'unsupported')
|
|
1691
|
+
await this.detachAgentConsumer(provider);
|
|
1692
|
+
else if (this.backendFeatures['agents.managed_agents'] === 'supported')
|
|
1693
|
+
this.ensureAgentConsumer(provider);
|
|
1694
|
+
}
|
|
1695
|
+
catch (error) {
|
|
1696
|
+
// Preserve successful facts; auth failures keep their established category.
|
|
1697
|
+
if (current())
|
|
1698
|
+
this.ctx.logger('awiki-capabilities').debug('AWiki capability refresh failed: %s', error instanceof AwikiSdkError ? error.code : 'unavailable');
|
|
1699
|
+
}
|
|
1700
|
+
}));
|
|
1701
|
+
})().finally(() => { if (this.backendFeaturesTask === task)
|
|
1702
|
+
this.backendFeaturesTask = undefined; });
|
|
1703
|
+
this.backendFeaturesTask = task;
|
|
1704
|
+
void task.catch(() => undefined);
|
|
1705
|
+
return task;
|
|
1706
|
+
}
|
|
1707
|
+
recordBackendDiagnostics(local, snapshot) {
|
|
1708
|
+
this.backendDiagnostics = { ...this.backendDiagnostics, [local ? 'message' : 'public']: {
|
|
1709
|
+
...typeof snapshot.fetched_at === 'number' ? { fetchedAt: snapshot.fetched_at } : {},
|
|
1710
|
+
...snapshot.refresh_error === undefined ? {} : { refreshError: snapshot.refresh_error },
|
|
1711
|
+
} };
|
|
1712
|
+
}
|
|
1713
|
+
stopBackendSubscriptions() {
|
|
1714
|
+
for (const subscription of this.backendSubscriptions.values())
|
|
1715
|
+
subscription.stop();
|
|
1716
|
+
this.backendSubscriptions.clear();
|
|
1717
|
+
this.backendSubscriptionPending.clear();
|
|
1718
|
+
}
|
|
1719
|
+
observeBackendProjection(provider, local, current) {
|
|
1720
|
+
const subscribe = provider.client.subscribeBackendCapabilities;
|
|
1721
|
+
if (subscribe === undefined || this.backendSubscriptions.has(local) || this.backendSubscriptionPending.has(local))
|
|
1722
|
+
return;
|
|
1723
|
+
this.backendSubscriptionPending.add(local);
|
|
1724
|
+
void (async () => {
|
|
1725
|
+
const subscription = await subscribe.call(provider.client, local);
|
|
1726
|
+
if (!current()) {
|
|
1727
|
+
subscription.stop();
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
this.backendSubscriptionPending.delete(local);
|
|
1731
|
+
this.backendSubscriptions.set(local, subscription);
|
|
1732
|
+
try {
|
|
1733
|
+
while (current()) {
|
|
1734
|
+
const state = await subscription.nextUpdate();
|
|
1735
|
+
if (state === null || !current())
|
|
1736
|
+
break;
|
|
1737
|
+
this.recordBackendDiagnostics(local, state.snapshot);
|
|
1738
|
+
if (state.snapshot.status === 'unavailable')
|
|
1739
|
+
continue;
|
|
1740
|
+
this.backendFeatures = { ...this.backendFeatures, ...projectBackendSnapshot(state.snapshot, local) };
|
|
1741
|
+
if (local)
|
|
1742
|
+
this.backendAttachmentLimit = backendAttachmentMaxBytes(state.snapshot, this.resolved.attachmentMaxBytes);
|
|
1743
|
+
if (this.backendFeatures['agents.managed_agents'] === 'unsupported')
|
|
1744
|
+
await this.detachAgentConsumer(provider);
|
|
1745
|
+
else if (this.backendFeatures['agents.managed_agents'] === 'supported')
|
|
1746
|
+
this.ensureAgentConsumer(provider);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
finally {
|
|
1750
|
+
subscription.stop();
|
|
1751
|
+
if (this.backendSubscriptions.get(local) === subscription)
|
|
1752
|
+
this.backendSubscriptions.delete(local);
|
|
1753
|
+
}
|
|
1754
|
+
})().catch(() => { if (current())
|
|
1755
|
+
this.backendSubscriptionPending.delete(local); });
|
|
1534
1756
|
}
|
|
1535
1757
|
async discoverTenantCapabilities(timeoutMs) {
|
|
1536
1758
|
const options = this.activeClientOptions;
|
|
@@ -1538,12 +1760,10 @@ let AwikiService = (() => {
|
|
|
1538
1760
|
if (options === undefined || tenant === undefined)
|
|
1539
1761
|
throw new ProviderUnavailableError();
|
|
1540
1762
|
const generation = this.runtimeGeneration;
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
online: false,
|
|
1545
|
-
handleRecoveryPhoneEnabled: false,
|
|
1546
|
-
};
|
|
1763
|
+
const previous = this.activeCapabilities;
|
|
1764
|
+
const fallback = previous?.tenantId === tenant.tenantId && previous.generation === generation
|
|
1765
|
+
? { ...previous }
|
|
1766
|
+
: { tenantId: tenant.tenantId, generation, online: false, handleRecoveryPhoneEnabled: false };
|
|
1547
1767
|
const publish = (capabilities) => {
|
|
1548
1768
|
if (this.runtimeGeneration === generation && this.activeTenant?.tenantId === tenant.tenantId) {
|
|
1549
1769
|
this.activeCapabilities = capabilities;
|
|
@@ -1558,6 +1778,7 @@ let AwikiService = (() => {
|
|
|
1558
1778
|
try {
|
|
1559
1779
|
const endpoint = new URL('/user-service/v1/server-info', options.userServiceUrl);
|
|
1560
1780
|
endpoint.searchParams.set('client_platform', 'dsh');
|
|
1781
|
+
endpoint.searchParams.set('capabilities_version', '1');
|
|
1561
1782
|
const response = await fetch(endpoint, {
|
|
1562
1783
|
method: 'GET',
|
|
1563
1784
|
headers: { accept: 'application/json', 'cache-control': 'no-store' },
|
|
@@ -1571,13 +1792,19 @@ let AwikiService = (() => {
|
|
|
1571
1792
|
const value = text === undefined ? undefined : JSON.parse(text);
|
|
1572
1793
|
if (typeof value !== 'object' || value === null || value.schema_version !== 1)
|
|
1573
1794
|
return publish(fallback);
|
|
1795
|
+
const versioned = value.capabilities_version === 1;
|
|
1796
|
+
if (!versioned) {
|
|
1797
|
+
if ('capabilities_version' in value)
|
|
1798
|
+
return publish({ tenantId: tenant.tenantId, generation, online: true, handleRecoveryPhoneEnabled: false });
|
|
1799
|
+
return publish(fallback);
|
|
1800
|
+
}
|
|
1574
1801
|
const methods = value.identity?.handle_recovery?.methods;
|
|
1575
|
-
const handleRecoveryPhoneEnabled = Array.isArray(methods) && methods.some((method) => (typeof method === 'object' && method !== null
|
|
1802
|
+
const handleRecoveryPhoneEnabled = versioned && Array.isArray(methods) && methods.some((method) => (typeof method === 'object' && method !== null
|
|
1576
1803
|
&& method.id === 'phone'
|
|
1577
1804
|
&& method.enabled === true
|
|
1578
1805
|
&& method.verification?.required === true
|
|
1579
1806
|
&& method.verification?.type === 'sms_otp'));
|
|
1580
|
-
const services = value.services;
|
|
1807
|
+
const services = versioned ? value.services : undefined;
|
|
1581
1808
|
const modelProxyBaseUrl = publishedServiceBaseUrl(services, 'model_proxy', this.resolved.allowInsecureLoopbackForTesting);
|
|
1582
1809
|
const guestGatewayBaseUrl = publishedServiceBaseUrl(services, 'guest_gateway', false);
|
|
1583
1810
|
const capabilities = {
|
|
@@ -1588,6 +1815,15 @@ let AwikiService = (() => {
|
|
|
1588
1815
|
...modelProxyBaseUrl === undefined ? {} : { modelProxyBaseUrl },
|
|
1589
1816
|
...guestGatewayBaseUrl === undefined ? {} : { guestGatewayBaseUrl },
|
|
1590
1817
|
};
|
|
1818
|
+
if (this.runtimeGeneration === generation && this.activeTenant?.tenantId === tenant.tenantId) {
|
|
1819
|
+
const saved = new TenantCapabilityCache(options.stateRoot, options.userServiceUrl).write({
|
|
1820
|
+
handleRecoveryPhoneEnabled,
|
|
1821
|
+
...modelProxyBaseUrl === undefined ? {} : { modelProxyBaseUrl },
|
|
1822
|
+
...guestGatewayBaseUrl === undefined ? {} : { guestGatewayBaseUrl },
|
|
1823
|
+
});
|
|
1824
|
+
if (!saved)
|
|
1825
|
+
this.ctx.logger('awiki-capabilities').debug('AWiki capability metadata persistence failed');
|
|
1826
|
+
}
|
|
1591
1827
|
return publish(capabilities);
|
|
1592
1828
|
}
|
|
1593
1829
|
catch {
|
|
@@ -1670,11 +1906,6 @@ let AwikiService = (() => {
|
|
|
1670
1906
|
return this.mutateSession(async () => {
|
|
1671
1907
|
if (await this.isSignedOut())
|
|
1672
1908
|
return { ok: true, value: { status: 'signed-out' } };
|
|
1673
|
-
const identity = await this.run(client => client.getIdentity(), { allowSignedOut: true });
|
|
1674
|
-
if (!identity.ok)
|
|
1675
|
-
return identity;
|
|
1676
|
-
if (identity.value === null)
|
|
1677
|
-
return { ok: false, error: failure('not-registered') };
|
|
1678
1909
|
try {
|
|
1679
1910
|
await this.sessionStore.signOut();
|
|
1680
1911
|
this.signedOut = true;
|
|
@@ -1683,8 +1914,10 @@ let AwikiService = (() => {
|
|
|
1683
1914
|
const session = { status: 'signed-out' };
|
|
1684
1915
|
this.publishSession(session);
|
|
1685
1916
|
const provider = this.provider;
|
|
1686
|
-
if (provider !== undefined)
|
|
1917
|
+
if (provider !== undefined) {
|
|
1687
1918
|
await this.stopProviderRuntime(provider);
|
|
1919
|
+
await this.updateBackendDiscoveryLifecycle(provider, false);
|
|
1920
|
+
}
|
|
1688
1921
|
return { ok: true, value: session };
|
|
1689
1922
|
}
|
|
1690
1923
|
catch {
|
|
@@ -1708,8 +1941,10 @@ let AwikiService = (() => {
|
|
|
1708
1941
|
const session = { status: 'active', identity: identity.value };
|
|
1709
1942
|
this.publishSession(session);
|
|
1710
1943
|
const provider = this.provider;
|
|
1711
|
-
if (provider !== undefined)
|
|
1944
|
+
if (provider !== undefined) {
|
|
1712
1945
|
this.ensureProviderRuntime(provider);
|
|
1946
|
+
await this.updateBackendDiscoveryLifecycle(provider, true);
|
|
1947
|
+
}
|
|
1713
1948
|
return { ok: true, value: session };
|
|
1714
1949
|
}
|
|
1715
1950
|
catch {
|
|
@@ -1896,6 +2131,9 @@ let AwikiService = (() => {
|
|
|
1896
2131
|
const recoveries = await client.listPendingRecoveries();
|
|
1897
2132
|
const pendingRegistrations = await client.pendingIdentityRegistrations();
|
|
1898
2133
|
const creationMethods = await client.identityCreationMethods().catch(() => []);
|
|
2134
|
+
const backend = await client.backendCapabilities?.(false);
|
|
2135
|
+
const registrationHandleOnly = backend?.capabilities['registration.handle_only']?.support === 'supported'
|
|
2136
|
+
&& (backend.status === 'confirmed' || backend.status === 'stale');
|
|
1899
2137
|
const identity = await client.getIdentity();
|
|
1900
2138
|
const capabilities = identity === null ? undefined : await client.identityMethodCapabilities(identity.did);
|
|
1901
2139
|
const domain = this.activeClientOptions?.userServiceDomain;
|
|
@@ -1909,6 +2147,7 @@ let AwikiService = (() => {
|
|
|
1909
2147
|
},
|
|
1910
2148
|
joining,
|
|
1911
2149
|
creationMethods,
|
|
2150
|
+
registrationHandleOnly,
|
|
1912
2151
|
pendingRegistrations,
|
|
1913
2152
|
...capabilities === undefined ? {} : { methodCapabilities: capabilities },
|
|
1914
2153
|
recoveries: recoveries.filter(value => domain !== undefined && value.fullHandle.slice(value.fullHandle.indexOf('.') + 1) === domain),
|
|
@@ -2164,6 +2403,18 @@ let AwikiService = (() => {
|
|
|
2164
2403
|
this.activeIdentityDid = result.value.did;
|
|
2165
2404
|
return result;
|
|
2166
2405
|
}
|
|
2406
|
+
/** Replace only the current authenticated human account's avatar. */
|
|
2407
|
+
setAvatar(request) {
|
|
2408
|
+
if (!validAvatarRequest(request, true))
|
|
2409
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
2410
|
+
return this.run(client => client.setAvatar(request));
|
|
2411
|
+
}
|
|
2412
|
+
/** Restore the current account's default character avatar. */
|
|
2413
|
+
clearAvatar(request) {
|
|
2414
|
+
if (!validAvatarRequest(request, false))
|
|
2415
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
2416
|
+
return this.run(client => client.clearAvatar(request));
|
|
2417
|
+
}
|
|
2167
2418
|
/** Return the public editable profile for the active identity. */
|
|
2168
2419
|
getProfile() {
|
|
2169
2420
|
return this.run(client => client.getProfile());
|
|
@@ -2255,7 +2506,14 @@ let AwikiService = (() => {
|
|
|
2255
2506
|
const normalized = normalizeCreateGroupRequest(request);
|
|
2256
2507
|
if (normalized === undefined)
|
|
2257
2508
|
return { ok: false, error: failure('invalid-request') };
|
|
2258
|
-
const created = await this.run(client =>
|
|
2509
|
+
const created = await this.run(async (client) => {
|
|
2510
|
+
await requireBackendFeature(client, 'groups');
|
|
2511
|
+
if (normalized.encrypted)
|
|
2512
|
+
await requireBackendFeature(client, 'group_e2ee');
|
|
2513
|
+
if (normalized.members.length > 0)
|
|
2514
|
+
await requireBackendFeature(client, 'group_admin_add');
|
|
2515
|
+
return client.createGroup(normalized.name, normalized.encrypted, normalized.operationId);
|
|
2516
|
+
});
|
|
2259
2517
|
if (!created.ok)
|
|
2260
2518
|
return created;
|
|
2261
2519
|
const addedMembers = [];
|
|
@@ -2279,12 +2537,19 @@ let AwikiService = (() => {
|
|
|
2279
2537
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
2280
2538
|
return this.run(client => client.getGroup(groupDid));
|
|
2281
2539
|
}
|
|
2540
|
+
/** Resume owner-held group encryption maintenance through Core. */
|
|
2541
|
+
repairGroupSecurity(request) {
|
|
2542
|
+
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
2543
|
+
if (groupDid === undefined)
|
|
2544
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
2545
|
+
return this.run(async (client) => { await requireBackendFeature(client, 'group_e2ee'); return client.repairGroupSecurity(groupDid); });
|
|
2546
|
+
}
|
|
2282
2547
|
/** Join one open group and return its authoritative membership state. */
|
|
2283
2548
|
joinGroup(request) {
|
|
2284
2549
|
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
2285
2550
|
if (groupDid === undefined)
|
|
2286
2551
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
2287
|
-
return this.run(client => client.joinGroup(groupDid));
|
|
2552
|
+
return this.run(async (client) => { await requireBackendFeature(client, 'group_open_join'); return client.joinGroup(groupDid); });
|
|
2288
2553
|
}
|
|
2289
2554
|
/** Leave one group. Core rejects owner leave and unsupported security profiles. */
|
|
2290
2555
|
async leaveGroup(request) {
|
|
@@ -2295,11 +2560,11 @@ let AwikiService = (() => {
|
|
|
2295
2560
|
return result.ok ? { ok: true, value: { completed: true } } : result;
|
|
2296
2561
|
}
|
|
2297
2562
|
/** Read one authoritative versioned member page. */
|
|
2298
|
-
getDisplayProfiles(peers) {
|
|
2299
|
-
if (!Array.isArray(peers) || peers.length > 100 || peers.some(peer => typeof peer !== 'string' || !peer.startsWith('did:'))) {
|
|
2563
|
+
getDisplayProfiles(peers, refresh) {
|
|
2564
|
+
if ((refresh !== undefined && typeof refresh !== 'boolean') || !Array.isArray(peers) || peers.length > 100 || peers.some(peer => typeof peer !== 'string' || !peer.startsWith('did:'))) {
|
|
2300
2565
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
2301
2566
|
}
|
|
2302
|
-
return this.run(client => client.getDisplayProfiles(peers));
|
|
2567
|
+
return this.run(client => client.getDisplayProfiles(peers, refresh));
|
|
2303
2568
|
}
|
|
2304
2569
|
listGroupMembers(request) {
|
|
2305
2570
|
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
@@ -2478,6 +2743,8 @@ let AwikiService = (() => {
|
|
|
2478
2743
|
* @returns The accepted attachment message or a closed failure.
|
|
2479
2744
|
*/
|
|
2480
2745
|
async sendAttachment(request) {
|
|
2746
|
+
if (request?.security !== undefined && request.security !== 'plain' && request.security !== 'e2ee-required')
|
|
2747
|
+
return { ok: false, error: failure('invalid-request') };
|
|
2481
2748
|
if (!isMessageTarget(request?.target))
|
|
2482
2749
|
return { ok: false, error: failure('invalid-request') };
|
|
2483
2750
|
const decoded = decodeAttachment(request.bytesBase64, this.resolved.attachmentMaxBytes);
|
|
@@ -2485,6 +2752,7 @@ let AwikiService = (() => {
|
|
|
2485
2752
|
return decoded;
|
|
2486
2753
|
return this.run(async (client) => {
|
|
2487
2754
|
const message = await client.sendAttachment({
|
|
2755
|
+
...request.security === undefined ? {} : { security: request.security },
|
|
2488
2756
|
target: request.target,
|
|
2489
2757
|
attachment: {
|
|
2490
2758
|
fileName: request.fileName,
|
|
@@ -2823,12 +3091,14 @@ let AwikiService = (() => {
|
|
|
2823
3091
|
return client.getLocalDeviceJoinVerificationProgress(notice.joinSessionId);
|
|
2824
3092
|
}
|
|
2825
3093
|
async requireDeviceManager(client) {
|
|
3094
|
+
await requireBackendFeature(client, 'identity.multi_device');
|
|
2826
3095
|
const current = await client.getCurrentDeviceSummary();
|
|
2827
3096
|
if (!current.canManage || current.role !== 'admin' || current.readiness !== 'admin_ready') {
|
|
2828
3097
|
throw Object.assign(new Error('device manager required'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2829
3098
|
}
|
|
2830
3099
|
}
|
|
2831
3100
|
async requireRootTransferRecipient(client, deviceId) {
|
|
3101
|
+
await requireBackendFeature(client, 'identity.root_transfer');
|
|
2832
3102
|
if (!client.trustedUserPresenceSupported) {
|
|
2833
3103
|
throw Object.assign(new Error('trusted user presence unsupported'), { name: 'AwikiSdkError', code: 'forbidden' });
|
|
2834
3104
|
}
|
|
@@ -2961,6 +3231,7 @@ let AwikiService = (() => {
|
|
|
2961
3231
|
}
|
|
2962
3232
|
/** Invalidate cached session work and cancel every model request still owned by the old session. */
|
|
2963
3233
|
invalidateSummaries() {
|
|
3234
|
+
this.stopBackendSubscriptions();
|
|
2964
3235
|
this.sessionRevision += 1;
|
|
2965
3236
|
this.requestRefs.clear();
|
|
2966
3237
|
this.requestSessions.clear();
|
|
@@ -3042,7 +3313,10 @@ let AwikiService = (() => {
|
|
|
3042
3313
|
catch {
|
|
3043
3314
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
3044
3315
|
}
|
|
3045
|
-
return this.run(
|
|
3316
|
+
return this.run(async (client) => {
|
|
3317
|
+
await requireBackendFeature(client, 'services.mail');
|
|
3318
|
+
return operation(client, request);
|
|
3319
|
+
}, options);
|
|
3046
3320
|
}
|
|
3047
3321
|
/** Read and cache the private Host-owned session marker. */
|
|
3048
3322
|
async isSignedOut() {
|
|
@@ -3103,6 +3377,16 @@ let AwikiService = (() => {
|
|
|
3103
3377
|
this.sessionMutation = pending.then(() => undefined, () => undefined);
|
|
3104
3378
|
return pending;
|
|
3105
3379
|
}
|
|
3380
|
+
/** Discovery is an optional projection, not authority for a committed session transition. */
|
|
3381
|
+
async updateBackendDiscoveryLifecycle(provider, active) {
|
|
3382
|
+
try {
|
|
3383
|
+
await provider.client.setBackendDiscoveryActive?.(active);
|
|
3384
|
+
}
|
|
3385
|
+
catch {
|
|
3386
|
+
// Do not include bridge errors: provider diagnostics may contain private context.
|
|
3387
|
+
this.ctx.logger('awiki-backend').warn('AWiki backend discovery lifecycle unavailable');
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3106
3390
|
/** Start identity realtime and the optional Agent consumer without blocking identity success. */
|
|
3107
3391
|
ensureProviderRuntime(provider) {
|
|
3108
3392
|
if (this.tenantSwitching)
|
|
@@ -3116,6 +3400,7 @@ let AwikiService = (() => {
|
|
|
3116
3400
|
this.replaceProviderRuntime(provider, identityDid);
|
|
3117
3401
|
return;
|
|
3118
3402
|
}
|
|
3403
|
+
this.refreshBackendProjection();
|
|
3119
3404
|
this.ensureRealtimeSupervisor(provider);
|
|
3120
3405
|
this.ensureAgentConsumer(provider);
|
|
3121
3406
|
}
|
|
@@ -3222,6 +3507,9 @@ let AwikiService = (() => {
|
|
|
3222
3507
|
const logger = this.ctx.logger('awiki-listener');
|
|
3223
3508
|
const startup = (async () => {
|
|
3224
3509
|
await this.detachAgentConsumer(provider);
|
|
3510
|
+
const features = await projectBackendFeatures(provider.client, false);
|
|
3511
|
+
if (features['agents.managed_agents'] !== 'supported')
|
|
3512
|
+
return;
|
|
3225
3513
|
if (!this.agentConsumerFenceMatches(provider, workspaceContext, identityDid, generation))
|
|
3226
3514
|
return;
|
|
3227
3515
|
const agents = new DshAwikiListenerAgentRuntime(workspaceContext, this.resolved.listener.workspacePath);
|