@awiki/dsh-plugin 0.2.4 → 0.3.0-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 +131 -22
- package/README.zh.md +110 -20
- package/THIRD_PARTY_NOTICES.md +8 -1
- package/cordis.patch.yml +5 -0
- package/lib/client.js +12534 -2647
- package/lib/client.js.map +1 -1
- package/lib/index.js +2431 -92
- package/lib/model-proxy-contract.js +96 -0
- package/lib/provider.js +4 -1
- package/lib/sdk-adapter-DLhLrLI6.mjs +886 -0
- package/lib/typert.host.js +1269 -534
- package/lib/typert.remote-client.d.ts +47 -1
- package/lib/typert.remote-client.d.ts.map +1 -1
- package/lib/typert.remote-client.js +1254 -141
- package/lib/types/attachment-cache.d.ts +28 -0
- package/lib/types/attachment-cache.d.ts.map +1 -0
- package/lib/types/attachment-cache.js +194 -0
- package/lib/types/attachment-cache.js.map +1 -0
- package/lib/types/client/AwikiGroupDetails.d.ts +16 -0
- package/lib/types/client/AwikiGroupDetails.d.ts.map +1 -0
- package/lib/types/client/AwikiGroupDetails.js +108 -0
- package/lib/types/client/AwikiGroupDetails.js.map +1 -0
- package/lib/types/client/AwikiIdentityAccess.d.ts +21 -0
- package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -0
- package/lib/types/client/AwikiIdentityAccess.js +148 -0
- package/lib/types/client/AwikiIdentityAccess.js.map +1 -0
- package/lib/types/client/AwikiIdentityPage.d.ts +11 -0
- package/lib/types/client/AwikiIdentityPage.d.ts.map +1 -0
- package/lib/types/client/AwikiIdentityPage.js +8 -0
- package/lib/types/client/AwikiIdentityPage.js.map +1 -0
- package/lib/types/client/AwikiMail.d.ts +15 -0
- package/lib/types/client/AwikiMail.d.ts.map +1 -0
- package/lib/types/client/AwikiMail.js +380 -0
- package/lib/types/client/AwikiMail.js.map +1 -0
- package/lib/types/client/AwikiOnboarding.d.ts +22 -0
- package/lib/types/client/AwikiOnboarding.d.ts.map +1 -0
- package/lib/types/client/AwikiOnboarding.js +104 -0
- package/lib/types/client/AwikiOnboarding.js.map +1 -0
- package/lib/types/client/AwikiOverlay.d.ts +10 -0
- package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
- package/lib/types/client/AwikiOverlay.js +370 -127
- package/lib/types/client/AwikiOverlay.js.map +1 -1
- package/lib/types/client/AwikiProfileCard.d.ts +9 -0
- package/lib/types/client/AwikiProfileCard.d.ts.map +1 -0
- package/lib/types/client/AwikiProfileCard.js +90 -0
- package/lib/types/client/AwikiProfileCard.js.map +1 -0
- package/lib/types/client/AwikiRecoveryForm.d.ts +24 -0
- package/lib/types/client/AwikiRecoveryForm.d.ts.map +1 -0
- package/lib/types/client/AwikiRecoveryForm.js +148 -0
- package/lib/types/client/AwikiRecoveryForm.js.map +1 -0
- package/lib/types/client/AwikiSettingsSection.d.ts +18 -4
- package/lib/types/client/AwikiSettingsSection.d.ts.map +1 -1
- package/lib/types/client/AwikiSettingsSection.js +277 -23
- package/lib/types/client/AwikiSettingsSection.js.map +1 -1
- package/lib/types/client/MentionText.d.ts +7 -0
- package/lib/types/client/MentionText.d.ts.map +1 -0
- package/lib/types/client/MentionText.js +10 -0
- package/lib/types/client/MentionText.js.map +1 -0
- package/lib/types/client/RechargeComingSoonDialog.d.ts +11 -0
- package/lib/types/client/RechargeComingSoonDialog.d.ts.map +1 -0
- package/lib/types/client/RechargeComingSoonDialog.js +8 -0
- package/lib/types/client/RechargeComingSoonDialog.js.map +1 -0
- package/lib/types/client/controller.d.ts +174 -4
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +975 -79
- package/lib/types/client/controller.js.map +1 -1
- package/lib/types/client/image-cache.d.ts +20 -0
- package/lib/types/client/image-cache.d.ts.map +1 -0
- package/lib/types/client/image-cache.js +177 -0
- package/lib/types/client/image-cache.js.map +1 -0
- package/lib/types/client/index.d.ts +2 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +102 -42
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/mail-list-cache.d.ts +15 -0
- package/lib/types/client/mail-list-cache.d.ts.map +1 -0
- package/lib/types/client/mail-list-cache.js +212 -0
- package/lib/types/client/mail-list-cache.js.map +1 -0
- package/lib/types/client/mentions.d.ts +51 -0
- package/lib/types/client/mentions.d.ts.map +1 -0
- package/lib/types/client/mentions.js +165 -0
- package/lib/types/client/mentions.js.map +1 -0
- package/lib/types/client/model-availability-controller.d.ts +27 -0
- package/lib/types/client/model-availability-controller.d.ts.map +1 -0
- package/lib/types/client/model-availability-controller.js +111 -0
- package/lib/types/client/model-availability-controller.js.map +1 -0
- package/lib/types/client/model-proxy-controller.d.ts +45 -0
- package/lib/types/client/model-proxy-controller.d.ts.map +1 -0
- package/lib/types/client/model-proxy-controller.js +306 -0
- package/lib/types/client/model-proxy-controller.js.map +1 -0
- package/lib/types/client/recharge-availability.d.ts +6 -0
- package/lib/types/client/recharge-availability.d.ts.map +1 -0
- package/lib/types/client/recharge-availability.js +6 -0
- package/lib/types/client/recharge-availability.js.map +1 -0
- 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 +186 -8
- package/lib/types/client/settings-locales.js.map +1 -1
- package/lib/types/client/slots.d.ts +34 -2
- package/lib/types/client/slots.d.ts.map +1 -1
- package/lib/types/index.d.ts +95 -7
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +968 -37
- package/lib/types/index.js.map +1 -1
- package/lib/types/listener-state.d.ts +23 -0
- package/lib/types/listener-state.d.ts.map +1 -0
- package/lib/types/listener-state.js +124 -0
- package/lib/types/listener-state.js.map +1 -0
- package/lib/types/listener.d.ts +96 -0
- package/lib/types/listener.d.ts.map +1 -0
- package/lib/types/listener.js +575 -0
- package/lib/types/listener.js.map +1 -0
- package/lib/types/mail.d.ts +8 -0
- package/lib/types/mail.d.ts.map +1 -0
- package/lib/types/mail.js +75 -0
- package/lib/types/mail.js.map +1 -0
- package/lib/types/model-proxy-contract.d.ts +67 -0
- package/lib/types/model-proxy-contract.d.ts.map +1 -0
- package/lib/types/model-proxy-contract.js +135 -0
- package/lib/types/model-proxy-contract.js.map +1 -0
- package/lib/types/provider-api.d.ts +106 -2
- package/lib/types/provider-api.d.ts.map +1 -1
- package/lib/types/provider.d.ts.map +1 -1
- package/lib/types/provider.js +3 -0
- package/lib/types/provider.js.map +1 -1
- package/lib/types/sdk-adapter.d.ts +43 -2
- package/lib/types/sdk-adapter.d.ts.map +1 -1
- package/lib/types/sdk-adapter.js +622 -24
- package/lib/types/sdk-adapter.js.map +1 -1
- package/lib/types/sent-mail-store.d.ts +22 -0
- package/lib/types/sent-mail-store.d.ts.map +1 -0
- package/lib/types/sent-mail-store.js +246 -0
- package/lib/types/sent-mail-store.js.map +1 -0
- package/lib/types/tools.d.ts +10 -0
- package/lib/types/tools.d.ts.map +1 -1
- package/lib/types/tools.js +98 -0
- package/lib/types/tools.js.map +1 -1
- package/lib/types/types.d.ts +254 -2
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js.map +1 -1
- package/package.json +31 -17
- package/lib/sdk-adapter-DK6RqV-c.mjs +0 -355
package/lib/types/index.js
CHANGED
|
@@ -35,7 +35,7 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
35
35
|
};
|
|
36
36
|
import { Context } from '@deepseek-ai/cordis';
|
|
37
37
|
import { homedir } from 'node:os';
|
|
38
|
-
import { join } from 'node:path';
|
|
38
|
+
import { isAbsolute, join } from 'node:path';
|
|
39
39
|
import z from '@deepseek-ai/schemastery';
|
|
40
40
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
41
41
|
import { settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
@@ -43,17 +43,23 @@ import { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "
|
|
|
43
43
|
import { AwikiExternalHttpAuthError, createAwikiExternalHttpAuth, externalHttpAuthError, mapProviderError as mapExternalHttpProviderError, } from "./external-http-auth.js";
|
|
44
44
|
import { downloadedAttachment } from "./sdk-adapter.js";
|
|
45
45
|
import { registerAwikiTools } from "./tools.js";
|
|
46
|
+
import { mailInboxRequest, mailMarkReadRequest, mailReadRequest, mailSendRequest, } from "./mail.js";
|
|
46
47
|
import { AwikiSettingsSchema, validateAwikiSettings, } from "./settings.js";
|
|
47
48
|
import { AWIKI_SETTINGS_NAMESPACE, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain } from "./domain.js";
|
|
48
49
|
import { AWIKI_SETTINGS_RPC_CHANNEL } from "./settings-rpc-contract.js";
|
|
49
50
|
import { createAwikiSettingsRpcHandler } from "./settings-rpc.js";
|
|
50
51
|
import { AwikiSessionStore } from "./session.js";
|
|
52
|
+
import { AwikiImageAttachmentCache, minimumImageAttachmentCacheMaxBytes } from "./attachment-cache.js";
|
|
53
|
+
import { AwikiSentMailStore, isLocalSentMailId } from "./sent-mail-store.js";
|
|
54
|
+
import { AwikiAgentListener, DshAwikiListenerAgentRuntime, } from "./listener.js";
|
|
51
55
|
export { AWIKI_CLEAR_LOCAL_DATA_CONFIRMATION, AWIKI_LOGOUT_CONFIRMATION } from "./types.js";
|
|
52
56
|
export { AWIKI_EXTERNAL_HTTP_MAX_BODY_BYTES, AwikiExternalHttpAuthError, } from "./external-http-auth.js";
|
|
53
57
|
export { AWIKI_DOMAIN_FIELD, AWIKI_SETTINGS_NAMESPACE, AwikiSettingsSchema, DEFAULT_AWIKI_DOMAIN, normalizeAwikiDomain, validateAwikiSettings, } from "./settings.js";
|
|
54
|
-
export { AWIKI_HISTORY_TOOL, AWIKI_IDENTITY_STATUS_TOOL, AWIKI_LIST_CONVERSATIONS_TOOL, AWIKI_SEND_ATTACHMENT_TOOL, AWIKI_SEND_MESSAGE_TOOL, } from "./tools.js";
|
|
58
|
+
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";
|
|
55
59
|
/** Default maximum attachment size: 10 MiB. */
|
|
56
60
|
export const DEFAULT_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
61
|
+
/** Default private on-disk budget for verified image previews: 64 MiB. */
|
|
62
|
+
export const DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES = 64 * 1024 * 1024;
|
|
57
63
|
/** Default browser polling interval while the AWiki drawer is open. */
|
|
58
64
|
export const DEFAULT_POLL_INTERVAL_MS = 3_000;
|
|
59
65
|
/** Default AWiki production service origin. */
|
|
@@ -69,13 +75,18 @@ export const Config = z.object({
|
|
|
69
75
|
userServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
70
76
|
userServiceDomain: z.string().default(DEFAULT_AWIKI_DOMAIN),
|
|
71
77
|
messageServiceUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
78
|
+
mailServiceUrl: z.string(),
|
|
72
79
|
messageServicePublicUrl: z.string().default(DEFAULT_AWIKI_SERVICE_URL),
|
|
73
80
|
messageServiceDid: z.string().default(DEFAULT_AWIKI_MESSAGE_SERVICE_DID),
|
|
74
81
|
allowedAttachmentOrigins: z.array(z.string()).default([]),
|
|
75
82
|
allowInsecureLoopbackForTesting: z.boolean().default(false),
|
|
76
83
|
stateRoot: z.string(),
|
|
77
84
|
attachmentMaxBytes: z.number().default(DEFAULT_ATTACHMENT_MAX_BYTES),
|
|
85
|
+
imageAttachmentCacheMaxBytes: z.number().default(DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES),
|
|
78
86
|
pollIntervalMs: z.number().default(DEFAULT_POLL_INTERVAL_MS),
|
|
87
|
+
listenerEnabled: z.boolean().default(false),
|
|
88
|
+
listenerAllowedPeers: z.array(z.string()).default([]),
|
|
89
|
+
listenerWorkspacePath: z.string(),
|
|
79
90
|
summaryMaxInputBytes: z.number().default(DEFAULT_SUMMARY_MAX_INPUT_BYTES),
|
|
80
91
|
});
|
|
81
92
|
const FAILURE_CODES = new Set([
|
|
@@ -90,15 +101,22 @@ const FAILURE_CODES = new Set([
|
|
|
90
101
|
'forbidden',
|
|
91
102
|
'conflict',
|
|
92
103
|
'rate-limited',
|
|
104
|
+
'group-membership-required',
|
|
105
|
+
'group-identity-stale',
|
|
93
106
|
'attachment-too-large',
|
|
94
107
|
'summary-unavailable',
|
|
95
108
|
'summary-timeout',
|
|
96
109
|
'summary-cancelled',
|
|
97
110
|
'summary-invalid-output',
|
|
98
111
|
'summary-failed',
|
|
112
|
+
'delivery-unknown',
|
|
99
113
|
'network',
|
|
100
114
|
'remote',
|
|
101
115
|
]);
|
|
116
|
+
const LISTENER_RESTART_BASE_DELAY_MS = 1_000;
|
|
117
|
+
const LISTENER_RESTART_MAX_DELAY_MS = 30_000;
|
|
118
|
+
const LISTENER_RESTART_MAX_ATTEMPT = 6;
|
|
119
|
+
const LISTENER_STABLE_RESET_MS = 60_000;
|
|
102
120
|
const FAILURE_MESSAGES = {
|
|
103
121
|
'not-registered': 'No AWiki identity is registered for this deployment.',
|
|
104
122
|
'signed-out': 'This installation is signed out of AWiki.',
|
|
@@ -111,12 +129,15 @@ const FAILURE_MESSAGES = {
|
|
|
111
129
|
'forbidden': 'The AWiki operation is not permitted.',
|
|
112
130
|
'conflict': 'The AWiki operation conflicts with current state.',
|
|
113
131
|
'rate-limited': 'The AWiki service rate-limited the request.',
|
|
132
|
+
'group-membership-required': 'The active AWiki identity is not a member of this group.',
|
|
133
|
+
'group-identity-stale': 'The AWiki group identity binding is still recovering.',
|
|
114
134
|
'attachment-too-large': 'The attachment exceeds this deployment\'s size limit.',
|
|
115
135
|
'summary-unavailable': 'AI summary is unavailable. Check the current default model configuration.',
|
|
116
136
|
'summary-timeout': 'AI summary timed out. Try again.',
|
|
117
137
|
'summary-cancelled': 'AI summary was cancelled. Try again.',
|
|
118
138
|
'summary-invalid-output': 'The model returned an invalid summary. Try again.',
|
|
119
139
|
'summary-failed': 'AI summary could not be generated. Try again.',
|
|
140
|
+
'delivery-unknown': 'Mail delivery could not be confirmed. Inspect the mailbox before retrying.',
|
|
120
141
|
'network': 'The AWiki service could not be reached.',
|
|
121
142
|
'remote': 'The AWiki service rejected the operation.',
|
|
122
143
|
};
|
|
@@ -170,42 +191,83 @@ function attachmentOrigins(raw, messageServicePublicUrl, allowInsecureLoopbackFo
|
|
|
170
191
|
}
|
|
171
192
|
return origins;
|
|
172
193
|
}
|
|
194
|
+
/** Resolve the explicit listener allowlist without accepting wildcards or ambiguous whitespace. */
|
|
195
|
+
function listenerAllowedPeers(raw, enabled) {
|
|
196
|
+
const peers = (raw ?? []).map((peer) => {
|
|
197
|
+
if (peer !== peer.trim() || peer.length === 0 || peer.length > 2_048 || /[\u0000-\u001f\u007f]/u.test(peer)) {
|
|
198
|
+
throw new TypeError('awiki: listenerAllowedPeers entries must be non-empty exact Handles or DIDs');
|
|
199
|
+
}
|
|
200
|
+
if (peer === '*')
|
|
201
|
+
throw new TypeError('awiki: listenerAllowedPeers does not accept wildcards');
|
|
202
|
+
return peer.startsWith('did:') ? peer : peer.toLowerCase();
|
|
203
|
+
});
|
|
204
|
+
if (peers.length > 100 || new Set(peers).size !== peers.length) {
|
|
205
|
+
throw new TypeError('awiki: listenerAllowedPeers must contain at most 100 unique entries');
|
|
206
|
+
}
|
|
207
|
+
if (enabled && peers.length === 0) {
|
|
208
|
+
throw new TypeError('awiki: listenerAllowedPeers must contain at least one Handle or DID when listenerEnabled is true');
|
|
209
|
+
}
|
|
210
|
+
return peers;
|
|
211
|
+
}
|
|
173
212
|
/** Resolve and validate every deployment choice before publishing the service. */
|
|
174
213
|
function resolveConfig(config) {
|
|
175
214
|
const allowInsecureLoopbackForTesting = config.allowInsecureLoopbackForTesting ?? false;
|
|
176
215
|
const configuredStateRoot = config.stateRoot?.trim();
|
|
177
216
|
const configuredDshHome = process.env.DSH_HOME?.trim();
|
|
217
|
+
const dshHome = configuredDshHome === undefined || configuredDshHome.length === 0
|
|
218
|
+
? join(homedir(), '.dsh')
|
|
219
|
+
: configuredDshHome;
|
|
178
220
|
const stateRoot = configuredStateRoot === undefined || configuredStateRoot.length === 0
|
|
179
|
-
? join(
|
|
221
|
+
? join(dshHome, 'awiki', 'im-core')
|
|
180
222
|
: configuredStateRoot;
|
|
181
|
-
if (stateRoot
|
|
182
|
-
throw new TypeError('awiki: stateRoot must be
|
|
223
|
+
if (!isAbsolute(stateRoot))
|
|
224
|
+
throw new TypeError('awiki: stateRoot must be an absolute path');
|
|
183
225
|
const attachmentMaxBytes = config.attachmentMaxBytes ?? DEFAULT_ATTACHMENT_MAX_BYTES;
|
|
184
226
|
if (!Number.isSafeInteger(attachmentMaxBytes) || attachmentMaxBytes < 1) {
|
|
185
227
|
throw new TypeError('awiki: attachmentMaxBytes must be a positive safe integer');
|
|
186
228
|
}
|
|
229
|
+
const imageAttachmentCacheMaxBytes = config.imageAttachmentCacheMaxBytes ?? DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES;
|
|
230
|
+
if (!Number.isSafeInteger(imageAttachmentCacheMaxBytes)
|
|
231
|
+
|| imageAttachmentCacheMaxBytes < minimumImageAttachmentCacheMaxBytes(attachmentMaxBytes)) {
|
|
232
|
+
throw new TypeError('awiki: imageAttachmentCacheMaxBytes cannot retain one maximum-sized attachment');
|
|
233
|
+
}
|
|
187
234
|
const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
188
235
|
if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs < 1_000 || pollIntervalMs > 60_000) {
|
|
189
236
|
throw new TypeError('awiki: pollIntervalMs must be a safe integer from 1000 through 60000');
|
|
190
237
|
}
|
|
238
|
+
const listenerEnabled = config.listenerEnabled ?? false;
|
|
239
|
+
const listenerWorkspacePath = config.listenerWorkspacePath?.trim() || join(dshHome, 'workspaces', 'awiki');
|
|
240
|
+
if (!isAbsolute(listenerWorkspacePath)) {
|
|
241
|
+
throw new TypeError('awiki: listenerWorkspacePath must be an absolute path');
|
|
242
|
+
}
|
|
243
|
+
const allowedPeers = listenerAllowedPeers(config.listenerAllowedPeers, listenerEnabled);
|
|
191
244
|
const summaryMaxInputBytes = config.summaryMaxInputBytes ?? DEFAULT_SUMMARY_MAX_INPUT_BYTES;
|
|
192
245
|
if (!Number.isSafeInteger(summaryMaxInputBytes) || summaryMaxInputBytes < 1_024) {
|
|
193
246
|
throw new TypeError('awiki: summaryMaxInputBytes must be a safe integer of at least 1024');
|
|
194
247
|
}
|
|
195
248
|
const userServiceUrl = serviceUrl('userServiceUrl', config.userServiceUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
196
249
|
const messageServiceUrl = serviceUrl('messageServiceUrl', config.messageServiceUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
250
|
+
const mailServiceUrl = serviceUrl('mailServiceUrl', config.mailServiceUrl ?? userServiceUrl, allowInsecureLoopbackForTesting);
|
|
197
251
|
const messageServicePublicUrl = serviceUrl('messageServicePublicUrl', config.messageServicePublicUrl ?? DEFAULT_AWIKI_SERVICE_URL, allowInsecureLoopbackForTesting);
|
|
198
252
|
return {
|
|
199
253
|
userServiceUrl,
|
|
200
254
|
userServiceDomain: serviceDomain(config.userServiceDomain ?? DEFAULT_AWIKI_DOMAIN),
|
|
201
255
|
messageServiceUrl,
|
|
256
|
+
mailServiceUrl,
|
|
202
257
|
messageServicePublicUrl,
|
|
203
258
|
messageServiceDid: serviceDid(config.messageServiceDid ?? DEFAULT_AWIKI_MESSAGE_SERVICE_DID),
|
|
204
259
|
allowedAttachmentOrigins: attachmentOrigins(config.allowedAttachmentOrigins, messageServicePublicUrl, allowInsecureLoopbackForTesting),
|
|
205
260
|
allowInsecureLoopbackForTesting,
|
|
206
261
|
stateRoot,
|
|
207
262
|
attachmentMaxBytes,
|
|
263
|
+
imageAttachmentCacheMaxBytes,
|
|
208
264
|
pollIntervalMs,
|
|
265
|
+
listenerEnabled,
|
|
266
|
+
listener: {
|
|
267
|
+
allowedPeers,
|
|
268
|
+
workspacePath: listenerWorkspacePath,
|
|
269
|
+
stateRoot,
|
|
270
|
+
},
|
|
209
271
|
summaryMaxInputBytes,
|
|
210
272
|
};
|
|
211
273
|
}
|
|
@@ -234,6 +296,208 @@ function normalizeFailure(error) {
|
|
|
234
296
|
}
|
|
235
297
|
return failure('remote');
|
|
236
298
|
}
|
|
299
|
+
const MAX_GROUP_NAME_CHARACTERS = 100;
|
|
300
|
+
const MAX_GROUP_INITIAL_MEMBERS = 50;
|
|
301
|
+
const MAX_GROUP_MEMBER_CHARACTERS = 512;
|
|
302
|
+
const MAX_PROFILE_DISPLAY_NAME_CHARACTERS = 50;
|
|
303
|
+
const MAX_PROFILE_BIO_CHARACTERS = 100;
|
|
304
|
+
const MAX_PROFILE_TAGS = 5;
|
|
305
|
+
const MAX_PROFILE_TAG_CHARACTERS = 30;
|
|
306
|
+
const MAX_MESSAGE_CHARACTERS = 20_000;
|
|
307
|
+
function normalizeCreateGroupRequest(request) {
|
|
308
|
+
if (typeof request?.name !== 'string' || !Array.isArray(request.members))
|
|
309
|
+
return undefined;
|
|
310
|
+
const name = request.name.trim();
|
|
311
|
+
if (name.length === 0 || Array.from(name).length > MAX_GROUP_NAME_CHARACTERS)
|
|
312
|
+
return undefined;
|
|
313
|
+
if (request.members.length > MAX_GROUP_INITIAL_MEMBERS)
|
|
314
|
+
return undefined;
|
|
315
|
+
const members = [];
|
|
316
|
+
const seen = new Set();
|
|
317
|
+
for (const raw of request.members) {
|
|
318
|
+
if (typeof raw !== 'string')
|
|
319
|
+
return undefined;
|
|
320
|
+
const member = raw.trim().replace(/^@+/u, '');
|
|
321
|
+
if (member.length === 0 || Array.from(member).length > MAX_GROUP_MEMBER_CHARACTERS)
|
|
322
|
+
return undefined;
|
|
323
|
+
if (seen.has(member))
|
|
324
|
+
continue;
|
|
325
|
+
seen.add(member);
|
|
326
|
+
members.push(member);
|
|
327
|
+
}
|
|
328
|
+
return { name, members };
|
|
329
|
+
}
|
|
330
|
+
function normalizeMember(value) {
|
|
331
|
+
if (typeof value !== 'string')
|
|
332
|
+
return undefined;
|
|
333
|
+
const member = value.trim().replace(/^@+/u, '');
|
|
334
|
+
return member.length > 0 && Array.from(member).length <= MAX_GROUP_MEMBER_CHARACTERS
|
|
335
|
+
? member
|
|
336
|
+
: undefined;
|
|
337
|
+
}
|
|
338
|
+
function normalizeGroupDid(value) {
|
|
339
|
+
if (typeof value !== 'string' || !value.startsWith('did:') || value.length > 2_048)
|
|
340
|
+
return undefined;
|
|
341
|
+
return value;
|
|
342
|
+
}
|
|
343
|
+
/** Rebuild the browser-safe recovery DTO instead of trusting provider object shape. */
|
|
344
|
+
function publicGroupRebindRecoverySummary(value) {
|
|
345
|
+
if (typeof value !== 'object' || value === null)
|
|
346
|
+
return undefined;
|
|
347
|
+
const candidate = value;
|
|
348
|
+
const counts = [candidate.processed, candidate.completed, candidate.pending, candidate.blocked];
|
|
349
|
+
if (counts.some(count => !Number.isSafeInteger(count) || count < 0 || count > 0xffff_ffff)) {
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
processed: candidate.processed,
|
|
354
|
+
completed: candidate.completed,
|
|
355
|
+
pending: candidate.pending,
|
|
356
|
+
blocked: candidate.blocked,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function normalizeProfileRequest(request) {
|
|
360
|
+
if (typeof request?.displayName !== 'string' || typeof request.bio !== 'string' || !Array.isArray(request.tags))
|
|
361
|
+
return undefined;
|
|
362
|
+
const displayName = request.displayName.trim();
|
|
363
|
+
const bio = request.bio.trim();
|
|
364
|
+
if (displayName.length === 0
|
|
365
|
+
|| Array.from(displayName).length > MAX_PROFILE_DISPLAY_NAME_CHARACTERS
|
|
366
|
+
|| Array.from(bio).length > MAX_PROFILE_BIO_CHARACTERS
|
|
367
|
+
|| request.tags.length > MAX_PROFILE_TAGS)
|
|
368
|
+
return undefined;
|
|
369
|
+
const tags = [];
|
|
370
|
+
const seen = new Set();
|
|
371
|
+
for (const raw of request.tags) {
|
|
372
|
+
if (typeof raw !== 'string')
|
|
373
|
+
return undefined;
|
|
374
|
+
const tag = raw.trim();
|
|
375
|
+
const key = tag.toLocaleLowerCase();
|
|
376
|
+
if (tag.length === 0 || Array.from(tag).length > MAX_PROFILE_TAG_CHARACTERS || seen.has(key))
|
|
377
|
+
return undefined;
|
|
378
|
+
seen.add(key);
|
|
379
|
+
tags.push(tag);
|
|
380
|
+
}
|
|
381
|
+
return { displayName, bio, tags };
|
|
382
|
+
}
|
|
383
|
+
function normalizeRecoveryOperation(request) {
|
|
384
|
+
if (typeof request?.operationId !== 'string')
|
|
385
|
+
return undefined;
|
|
386
|
+
const operationId = request.operationId.trim();
|
|
387
|
+
return operationId.length > 0 && operationId.length <= 512 ? { operationId } : undefined;
|
|
388
|
+
}
|
|
389
|
+
function normalizeRecoveryOtpRequest(request) {
|
|
390
|
+
if (typeof request?.fullHandle !== 'string' || typeof request.phone !== 'string')
|
|
391
|
+
return undefined;
|
|
392
|
+
const fullHandle = request.fullHandle.trim().replace(/^@+/u, '');
|
|
393
|
+
const phone = request.phone.trim();
|
|
394
|
+
if (fullHandle.length === 0 || fullHandle.length > 512 || phone.length < 5 || phone.length > 32)
|
|
395
|
+
return undefined;
|
|
396
|
+
return { fullHandle, phone };
|
|
397
|
+
}
|
|
398
|
+
function normalizeRecoveryPrepareRequest(request) {
|
|
399
|
+
const operation = normalizeRecoveryOperation(request);
|
|
400
|
+
if (operation === undefined || typeof request.phone !== 'string' || typeof request.otp !== 'string')
|
|
401
|
+
return undefined;
|
|
402
|
+
const phone = request.phone.trim();
|
|
403
|
+
const otp = request.otp.trim();
|
|
404
|
+
if (phone.length < 5 || phone.length > 32 || !/^\d{4,12}$/u.test(otp))
|
|
405
|
+
return undefined;
|
|
406
|
+
return { ...operation, phone, otp };
|
|
407
|
+
}
|
|
408
|
+
const IDENTITY_ACCESS_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
409
|
+
/** Read one untrusted discovery response without buffering beyond the fixed Host limit. */
|
|
410
|
+
async function readBoundedResponseText(response, maxBytes) {
|
|
411
|
+
const declaredLength = response.headers.get('content-length');
|
|
412
|
+
if (declaredLength !== null && /^\d+$/u.test(declaredLength) && Number(declaredLength) > maxBytes)
|
|
413
|
+
return undefined;
|
|
414
|
+
if (response.body === null)
|
|
415
|
+
return '';
|
|
416
|
+
const reader = response.body.getReader();
|
|
417
|
+
const chunks = [];
|
|
418
|
+
let length = 0;
|
|
419
|
+
try {
|
|
420
|
+
while (true) {
|
|
421
|
+
const result = await reader.read();
|
|
422
|
+
if (result.done)
|
|
423
|
+
break;
|
|
424
|
+
length += result.value.byteLength;
|
|
425
|
+
if (length > maxBytes) {
|
|
426
|
+
await reader.cancel();
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
chunks.push(Uint8Array.from(result.value));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
finally {
|
|
436
|
+
reader.releaseLock();
|
|
437
|
+
}
|
|
438
|
+
const bytes = new Uint8Array(length);
|
|
439
|
+
let offset = 0;
|
|
440
|
+
for (const chunk of chunks) {
|
|
441
|
+
bytes.set(chunk, offset);
|
|
442
|
+
offset += chunk.byteLength;
|
|
443
|
+
}
|
|
444
|
+
return Buffer.from(bytes).toString('utf8');
|
|
445
|
+
}
|
|
446
|
+
/** Resolve one configured-domain Handle without widening registration authority. */
|
|
447
|
+
function identityAccessTarget(request, configuredDomain) {
|
|
448
|
+
if (typeof request?.handle !== 'string')
|
|
449
|
+
return undefined;
|
|
450
|
+
const raw = request.handle.trim();
|
|
451
|
+
if (raw.length === 0 || raw.length > 255 || /[\u0000-\u001f\u007f]/u.test(raw))
|
|
452
|
+
return undefined;
|
|
453
|
+
const lowered = raw.replace(/[A-Z]/gu, character => character.toLowerCase());
|
|
454
|
+
const handle = lowered.startsWith('wba://') ? lowered.slice('wba://'.length) : lowered;
|
|
455
|
+
const dot = handle.indexOf('.');
|
|
456
|
+
const localPart = (dot < 0 ? handle : handle.slice(0, dot)).trim();
|
|
457
|
+
const domain = (dot < 0 ? configuredDomain : handle.slice(dot + 1)).trim().replace(/\.$/u, '');
|
|
458
|
+
if (localPart.length === 0 || domain !== configuredDomain)
|
|
459
|
+
return undefined;
|
|
460
|
+
return { localPart, fullHandle: `${localPart}.${configuredDomain}` };
|
|
461
|
+
}
|
|
462
|
+
function normalizeSendTextRequest(request) {
|
|
463
|
+
if (typeof request?.text !== 'string' || typeof request.idempotencyKey !== 'string'
|
|
464
|
+
|| typeof request.target !== 'object' || request.target === null)
|
|
465
|
+
return undefined;
|
|
466
|
+
const text = request.text;
|
|
467
|
+
const codePoints = Array.from(text);
|
|
468
|
+
if (text.trim().length === 0 || codePoints.length > MAX_MESSAGE_CHARACTERS
|
|
469
|
+
|| request.idempotencyKey.length === 0 || request.idempotencyKey.length > 512)
|
|
470
|
+
return undefined;
|
|
471
|
+
if (request.target.kind === 'direct') {
|
|
472
|
+
if (typeof request.target.peer !== 'string' || request.mentions !== undefined)
|
|
473
|
+
return undefined;
|
|
474
|
+
}
|
|
475
|
+
else if (request.target.kind === 'group') {
|
|
476
|
+
if (typeof request.target.group !== 'string')
|
|
477
|
+
return undefined;
|
|
478
|
+
}
|
|
479
|
+
else
|
|
480
|
+
return undefined;
|
|
481
|
+
if (request.mentions === undefined)
|
|
482
|
+
return request;
|
|
483
|
+
if (!Array.isArray(request.mentions) || request.mentions.length === 0 || request.mentions.length > 100)
|
|
484
|
+
return undefined;
|
|
485
|
+
const ids = new Set();
|
|
486
|
+
let previousEnd = 0;
|
|
487
|
+
const mentions = [...request.mentions].sort((left, right) => left.start - right.start || left.end - right.end);
|
|
488
|
+
for (const mention of mentions) {
|
|
489
|
+
if (typeof mention !== 'object' || mention === null
|
|
490
|
+
|| typeof mention.id !== 'string' || mention.id.trim() === '' || ids.has(mention.id)
|
|
491
|
+
|| !Number.isSafeInteger(mention.start) || !Number.isSafeInteger(mention.end)
|
|
492
|
+
|| mention.start < previousEnd || mention.start < 0 || mention.end <= mention.start || mention.end > codePoints.length
|
|
493
|
+
|| typeof mention.did !== 'string' || !mention.did.startsWith('did:')
|
|
494
|
+
|| (mention.displayName !== undefined && typeof mention.displayName !== 'string'))
|
|
495
|
+
return undefined;
|
|
496
|
+
ids.add(mention.id);
|
|
497
|
+
previousEnd = mention.end;
|
|
498
|
+
}
|
|
499
|
+
return { ...request, mentions };
|
|
500
|
+
}
|
|
237
501
|
/** Normalize summary-provider failures without returning prompts, model output, routes, or causes. */
|
|
238
502
|
function normalizeSummaryFailure(error) {
|
|
239
503
|
if (error instanceof SummaryProviderUnavailableError)
|
|
@@ -367,17 +631,40 @@ let AwikiService = (() => {
|
|
|
367
631
|
let _getSession_decorators;
|
|
368
632
|
let _logout_decorators;
|
|
369
633
|
let _login_decorators;
|
|
634
|
+
let _inspectIdentityAccess_decorators;
|
|
370
635
|
let _sendRegistrationOtp_decorators;
|
|
371
636
|
let _registerIdentity_decorators;
|
|
372
637
|
let _updateDisplayName_decorators;
|
|
638
|
+
let _getProfile_decorators;
|
|
639
|
+
let _updateProfile_decorators;
|
|
640
|
+
let _sendRecoveryOtp_decorators;
|
|
641
|
+
let _prepareRecovery_decorators;
|
|
642
|
+
let _activateRecovery_decorators;
|
|
643
|
+
let _getRecoveryStatus_decorators;
|
|
644
|
+
let _resumeRecovery_decorators;
|
|
645
|
+
let _discardRecovery_decorators;
|
|
373
646
|
let _resolvePeer_decorators;
|
|
647
|
+
let _createGroup_decorators;
|
|
648
|
+
let _getGroup_decorators;
|
|
649
|
+
let _joinGroup_decorators;
|
|
650
|
+
let _leaveGroup_decorators;
|
|
651
|
+
let _listGroupMembers_decorators;
|
|
652
|
+
let _addGroupMember_decorators;
|
|
653
|
+
let _removeGroupMember_decorators;
|
|
654
|
+
let _resumeGroupRebindRecovery_decorators;
|
|
374
655
|
let _listConversations_decorators;
|
|
375
656
|
let _getHistory_decorators;
|
|
657
|
+
let _getLocalHistory_decorators;
|
|
376
658
|
let _summarizeConversation_decorators;
|
|
377
659
|
let _markConversationRead_decorators;
|
|
378
660
|
let _sendText_decorators;
|
|
379
661
|
let _sendAttachment_decorators;
|
|
380
662
|
let _downloadAttachment_decorators;
|
|
663
|
+
let _getMailAccount_decorators;
|
|
664
|
+
let _listMailInbox_decorators;
|
|
665
|
+
let _readMail_decorators;
|
|
666
|
+
let _markMailRead_decorators;
|
|
667
|
+
let _sendMail_decorators;
|
|
381
668
|
let _clearLocalData_decorators;
|
|
382
669
|
return class AwikiService extends _classSuper {
|
|
383
670
|
static {
|
|
@@ -387,34 +674,80 @@ let AwikiService = (() => {
|
|
|
387
674
|
_getSession_decorators = [Remote];
|
|
388
675
|
_logout_decorators = [Remote];
|
|
389
676
|
_login_decorators = [Remote];
|
|
677
|
+
_inspectIdentityAccess_decorators = [Remote];
|
|
390
678
|
_sendRegistrationOtp_decorators = [Remote];
|
|
391
679
|
_registerIdentity_decorators = [Remote];
|
|
392
680
|
_updateDisplayName_decorators = [Remote];
|
|
681
|
+
_getProfile_decorators = [Remote];
|
|
682
|
+
_updateProfile_decorators = [Remote];
|
|
683
|
+
_sendRecoveryOtp_decorators = [Remote];
|
|
684
|
+
_prepareRecovery_decorators = [Remote];
|
|
685
|
+
_activateRecovery_decorators = [Remote];
|
|
686
|
+
_getRecoveryStatus_decorators = [Remote];
|
|
687
|
+
_resumeRecovery_decorators = [Remote];
|
|
688
|
+
_discardRecovery_decorators = [Remote];
|
|
393
689
|
_resolvePeer_decorators = [Remote];
|
|
690
|
+
_createGroup_decorators = [Remote];
|
|
691
|
+
_getGroup_decorators = [Remote];
|
|
692
|
+
_joinGroup_decorators = [Remote];
|
|
693
|
+
_leaveGroup_decorators = [Remote];
|
|
694
|
+
_listGroupMembers_decorators = [Remote];
|
|
695
|
+
_addGroupMember_decorators = [Remote];
|
|
696
|
+
_removeGroupMember_decorators = [Remote];
|
|
697
|
+
_resumeGroupRebindRecovery_decorators = [Remote];
|
|
394
698
|
_listConversations_decorators = [Remote];
|
|
395
699
|
_getHistory_decorators = [Remote];
|
|
700
|
+
_getLocalHistory_decorators = [Remote];
|
|
396
701
|
_summarizeConversation_decorators = [Remote];
|
|
397
702
|
_markConversationRead_decorators = [Remote];
|
|
398
703
|
_sendText_decorators = [Remote];
|
|
399
704
|
_sendAttachment_decorators = [Remote];
|
|
400
705
|
_downloadAttachment_decorators = [Remote];
|
|
706
|
+
_getMailAccount_decorators = [Remote];
|
|
707
|
+
_listMailInbox_decorators = [Remote];
|
|
708
|
+
_readMail_decorators = [Remote];
|
|
709
|
+
_markMailRead_decorators = [Remote];
|
|
710
|
+
_sendMail_decorators = [Remote];
|
|
401
711
|
_clearLocalData_decorators = [Remote];
|
|
402
712
|
__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);
|
|
403
713
|
__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);
|
|
404
714
|
__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);
|
|
405
715
|
__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);
|
|
406
716
|
__esDecorate(this, null, _login_decorators, { kind: "method", name: "login", static: false, private: false, access: { has: obj => "login" in obj, get: obj => obj.login }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
717
|
+
__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);
|
|
407
718
|
__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);
|
|
408
719
|
__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);
|
|
409
720
|
__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);
|
|
721
|
+
__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);
|
|
722
|
+
__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);
|
|
723
|
+
__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);
|
|
724
|
+
__esDecorate(this, null, _prepareRecovery_decorators, { kind: "method", name: "prepareRecovery", static: false, private: false, access: { has: obj => "prepareRecovery" in obj, get: obj => obj.prepareRecovery }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
725
|
+
__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);
|
|
726
|
+
__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);
|
|
727
|
+
__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);
|
|
728
|
+
__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);
|
|
410
729
|
__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);
|
|
730
|
+
__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);
|
|
731
|
+
__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);
|
|
732
|
+
__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);
|
|
733
|
+
__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);
|
|
734
|
+
__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);
|
|
735
|
+
__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);
|
|
736
|
+
__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);
|
|
737
|
+
__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);
|
|
411
738
|
__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);
|
|
412
739
|
__esDecorate(this, null, _getHistory_decorators, { kind: "method", name: "getHistory", static: false, private: false, access: { has: obj => "getHistory" in obj, get: obj => obj.getHistory }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
740
|
+
__esDecorate(this, null, _getLocalHistory_decorators, { kind: "method", name: "getLocalHistory", static: false, private: false, access: { has: obj => "getLocalHistory" in obj, get: obj => obj.getLocalHistory }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
413
741
|
__esDecorate(this, null, _summarizeConversation_decorators, { kind: "method", name: "summarizeConversation", static: false, private: false, access: { has: obj => "summarizeConversation" in obj, get: obj => obj.summarizeConversation }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
414
742
|
__esDecorate(this, null, _markConversationRead_decorators, { kind: "method", name: "markConversationRead", static: false, private: false, access: { has: obj => "markConversationRead" in obj, get: obj => obj.markConversationRead }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
415
743
|
__esDecorate(this, null, _sendText_decorators, { kind: "method", name: "sendText", static: false, private: false, access: { has: obj => "sendText" in obj, get: obj => obj.sendText }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
416
744
|
__esDecorate(this, null, _sendAttachment_decorators, { kind: "method", name: "sendAttachment", static: false, private: false, access: { has: obj => "sendAttachment" in obj, get: obj => obj.sendAttachment }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
417
745
|
__esDecorate(this, null, _downloadAttachment_decorators, { kind: "method", name: "downloadAttachment", static: false, private: false, access: { has: obj => "downloadAttachment" in obj, get: obj => obj.downloadAttachment }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
746
|
+
__esDecorate(this, null, _getMailAccount_decorators, { kind: "method", name: "getMailAccount", static: false, private: false, access: { has: obj => "getMailAccount" in obj, get: obj => obj.getMailAccount }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
747
|
+
__esDecorate(this, null, _listMailInbox_decorators, { kind: "method", name: "listMailInbox", static: false, private: false, access: { has: obj => "listMailInbox" in obj, get: obj => obj.listMailInbox }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
748
|
+
__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);
|
|
749
|
+
__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);
|
|
750
|
+
__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);
|
|
418
751
|
__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);
|
|
419
752
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
420
753
|
}
|
|
@@ -422,25 +755,33 @@ let AwikiService = (() => {
|
|
|
422
755
|
static Config = Config;
|
|
423
756
|
resolved = __runInitializers(this, _instanceExtraInitializers);
|
|
424
757
|
sessionStore;
|
|
758
|
+
imageAttachmentCache;
|
|
759
|
+
sentMailStore;
|
|
425
760
|
startupUserServiceDomain;
|
|
426
761
|
settingsProvider;
|
|
427
762
|
provider;
|
|
428
763
|
signedOut;
|
|
429
764
|
sessionMutation = Promise.resolve();
|
|
430
765
|
sessionRevision = 0;
|
|
766
|
+
activeIdentityDid;
|
|
431
767
|
activeSummaryRequests = new Set();
|
|
432
768
|
summaryProvider;
|
|
769
|
+
hostContext;
|
|
433
770
|
/** Trusted same-process external HTTP authentication dispatcher. Never Remote. */
|
|
434
771
|
externalHttpAuth;
|
|
772
|
+
workspaceContext;
|
|
435
773
|
/**
|
|
436
774
|
* @param ctx - owning Host context.
|
|
437
775
|
* @param config - service endpoints, SDK state path, and public limits.
|
|
438
776
|
*/
|
|
439
777
|
constructor(ctx, config) {
|
|
440
778
|
super(ctx, 'awiki');
|
|
779
|
+
this.hostContext = ctx;
|
|
441
780
|
this.resolved = resolveConfig(config);
|
|
442
781
|
this.externalHttpAuth = createAwikiExternalHttpAuth(() => this.acquireExternalHttpAuthSession());
|
|
443
782
|
this.sessionStore = new AwikiSessionStore(this.resolved.stateRoot);
|
|
783
|
+
this.imageAttachmentCache = new AwikiImageAttachmentCache(this.resolved.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes);
|
|
784
|
+
this.sentMailStore = new AwikiSentMailStore(this.resolved.stateRoot);
|
|
444
785
|
this.startupUserServiceDomain = this.resolved.userServiceDomain;
|
|
445
786
|
ctx.inject(['settings'], (settingsCtx) => {
|
|
446
787
|
const provider = settingsCtx.settings;
|
|
@@ -461,6 +802,20 @@ let AwikiService = (() => {
|
|
|
461
802
|
ctx.inject(['connection'], (connectionCtx) => {
|
|
462
803
|
connectionCtx.connection.rpc.handle(AWIKI_SETTINGS_RPC_CHANNEL, createAwikiSettingsRpcHandler(() => this.settingsProvider), { authority: 'loopback' });
|
|
463
804
|
});
|
|
805
|
+
ctx.inject(['workspaceRegistry'], (workspaceCtx) => {
|
|
806
|
+
this.workspaceContext = workspaceCtx;
|
|
807
|
+
const provider = this.provider;
|
|
808
|
+
if (provider !== undefined)
|
|
809
|
+
void this.startListener(provider);
|
|
810
|
+
workspaceCtx.effect(() => async () => {
|
|
811
|
+
if (this.workspaceContext !== workspaceCtx)
|
|
812
|
+
return;
|
|
813
|
+
this.workspaceContext = undefined;
|
|
814
|
+
const current = this.provider;
|
|
815
|
+
if (current !== undefined)
|
|
816
|
+
await this.stopListener(current);
|
|
817
|
+
}, 'awiki: release Workspace listener composition');
|
|
818
|
+
});
|
|
464
819
|
registerAwikiTools(ctx, this);
|
|
465
820
|
ctx.effect(() => async () => {
|
|
466
821
|
const provider = this.provider;
|
|
@@ -485,6 +840,7 @@ let AwikiService = (() => {
|
|
|
485
840
|
userServiceUrl: this.resolved.userServiceUrl,
|
|
486
841
|
userServiceDomain: this.startupUserServiceDomain,
|
|
487
842
|
messageServiceUrl: this.resolved.messageServiceUrl,
|
|
843
|
+
mailServiceUrl: this.resolved.mailServiceUrl,
|
|
488
844
|
messageServicePublicUrl: this.resolved.messageServicePublicUrl,
|
|
489
845
|
messageServiceDid: this.resolved.messageServiceDid,
|
|
490
846
|
allowedAttachmentOrigins: this.resolved.allowedAttachmentOrigins,
|
|
@@ -492,8 +848,14 @@ let AwikiService = (() => {
|
|
|
492
848
|
allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
|
|
493
849
|
stateRoot: this.resolved.stateRoot,
|
|
494
850
|
});
|
|
495
|
-
const provider = {
|
|
851
|
+
const provider = {
|
|
852
|
+
client,
|
|
853
|
+
listenerRestartAttempt: 0,
|
|
854
|
+
listenerGeneration: 0,
|
|
855
|
+
listenerRecoveryBlocked: false,
|
|
856
|
+
};
|
|
496
857
|
this.provider = provider;
|
|
858
|
+
void this.startListener(provider);
|
|
497
859
|
return () => this.disposeProvider(provider);
|
|
498
860
|
}
|
|
499
861
|
/** Register one replaceable conversation-summary provider for this deployment. */
|
|
@@ -527,16 +889,22 @@ let AwikiService = (() => {
|
|
|
527
889
|
* Read the deployment's identity status.
|
|
528
890
|
* @returns The public deployment identity or `null`.
|
|
529
891
|
*/
|
|
530
|
-
getIdentity() {
|
|
531
|
-
|
|
892
|
+
async getIdentity() {
|
|
893
|
+
const result = await this.run(client => client.getIdentity());
|
|
894
|
+
if (result.ok)
|
|
895
|
+
this.activeIdentityDid = result.value?.did;
|
|
896
|
+
return result;
|
|
532
897
|
}
|
|
533
898
|
/** Return the local registration and sign-in state without exposing secrets. */
|
|
534
899
|
async getSession() {
|
|
535
|
-
if (await this.isSignedOut())
|
|
900
|
+
if (await this.isSignedOut()) {
|
|
901
|
+
this.activeIdentityDid = undefined;
|
|
536
902
|
return { ok: true, value: { status: 'signed-out' } };
|
|
903
|
+
}
|
|
537
904
|
const identity = await this.run(client => client.getIdentity(), { allowSignedOut: true });
|
|
538
905
|
if (!identity.ok)
|
|
539
906
|
return identity;
|
|
907
|
+
this.activeIdentityDid = identity.value?.did;
|
|
540
908
|
return identity.value === null
|
|
541
909
|
? { ok: true, value: { status: 'unregistered' } }
|
|
542
910
|
: { ok: true, value: { status: 'active', identity: identity.value } };
|
|
@@ -557,8 +925,14 @@ let AwikiService = (() => {
|
|
|
557
925
|
try {
|
|
558
926
|
await this.sessionStore.signOut();
|
|
559
927
|
this.signedOut = true;
|
|
928
|
+
this.activeIdentityDid = undefined;
|
|
560
929
|
this.invalidateSummaries();
|
|
561
|
-
|
|
930
|
+
const session = { status: 'signed-out' };
|
|
931
|
+
this.publishSession(session);
|
|
932
|
+
const provider = this.provider;
|
|
933
|
+
if (provider !== undefined)
|
|
934
|
+
await this.stopListener(provider);
|
|
935
|
+
return { ok: true, value: session };
|
|
562
936
|
}
|
|
563
937
|
catch {
|
|
564
938
|
return { ok: false, error: failure('remote') };
|
|
@@ -576,19 +950,70 @@ let AwikiService = (() => {
|
|
|
576
950
|
try {
|
|
577
951
|
await this.sessionStore.signIn();
|
|
578
952
|
this.signedOut = false;
|
|
953
|
+
this.activeIdentityDid = identity.value.did;
|
|
579
954
|
this.invalidateSummaries();
|
|
580
|
-
|
|
955
|
+
const session = { status: 'active', identity: identity.value };
|
|
956
|
+
this.publishSession(session);
|
|
957
|
+
const provider = this.provider;
|
|
958
|
+
if (provider !== undefined)
|
|
959
|
+
void this.startListener(provider);
|
|
960
|
+
return { ok: true, value: session };
|
|
581
961
|
}
|
|
582
962
|
catch {
|
|
583
963
|
return { ok: false, error: failure('remote') };
|
|
584
964
|
}
|
|
585
965
|
});
|
|
586
966
|
}
|
|
587
|
-
/**
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
967
|
+
/** Classify one configured-domain Handle before selecting the registration or recovery OTP purpose. */
|
|
968
|
+
async inspectIdentityAccess(request) {
|
|
969
|
+
const target = identityAccessTarget(request, this.startupUserServiceDomain);
|
|
970
|
+
if (target === undefined)
|
|
971
|
+
return { ok: false, error: failure('invalid-request') };
|
|
972
|
+
const endpoint = new URL(`/.well-known/handle/${encodeURIComponent(target.localPart)}`, this.resolved.userServiceUrl);
|
|
973
|
+
const abort = new AbortController();
|
|
974
|
+
const timeout = setTimeout(() => { abort.abort(); }, 10_000);
|
|
975
|
+
let response;
|
|
976
|
+
try {
|
|
977
|
+
response = await fetch(endpoint, {
|
|
978
|
+
method: 'GET',
|
|
979
|
+
headers: { accept: 'application/json', 'cache-control': 'no-store' },
|
|
980
|
+
cache: 'no-store',
|
|
981
|
+
redirect: 'error',
|
|
982
|
+
signal: abort.signal,
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
catch {
|
|
986
|
+
return { ok: false, error: failure('network') };
|
|
987
|
+
}
|
|
988
|
+
finally {
|
|
989
|
+
clearTimeout(timeout);
|
|
990
|
+
}
|
|
991
|
+
if (response.status === 404) {
|
|
992
|
+
return { ok: true, value: { status: 'available', fullHandle: target.fullHandle } };
|
|
993
|
+
}
|
|
994
|
+
if (!response.ok)
|
|
995
|
+
return { ok: false, error: failure('remote') };
|
|
996
|
+
try {
|
|
997
|
+
const text = await readBoundedResponseText(response, IDENTITY_ACCESS_RESPONSE_MAX_BYTES);
|
|
998
|
+
if (text === undefined)
|
|
999
|
+
return { ok: false, error: failure('remote') };
|
|
1000
|
+
const value = JSON.parse(text);
|
|
1001
|
+
if (typeof value !== 'object' || value === null)
|
|
1002
|
+
return { ok: false, error: failure('remote') };
|
|
1003
|
+
const binding = value;
|
|
1004
|
+
if (binding.handle !== target.fullHandle
|
|
1005
|
+
|| typeof binding.did !== 'string'
|
|
1006
|
+
|| !binding.did.startsWith('did:')
|
|
1007
|
+
|| typeof binding.status !== 'string'
|
|
1008
|
+
|| binding.status.length === 0) {
|
|
1009
|
+
return { ok: false, error: failure('remote') };
|
|
1010
|
+
}
|
|
1011
|
+
return { ok: true, value: { status: 'existing', fullHandle: target.fullHandle } };
|
|
1012
|
+
}
|
|
1013
|
+
catch {
|
|
1014
|
+
return { ok: false, error: failure('remote') };
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
592
1017
|
sendRegistrationOtp(request) {
|
|
593
1018
|
return this.run(client => client.sendRegistrationOtp(request));
|
|
594
1019
|
}
|
|
@@ -597,16 +1022,88 @@ let AwikiService = (() => {
|
|
|
597
1022
|
* @param request - Handle, phone, and verification code for registration.
|
|
598
1023
|
* @returns The new public identity or a closed failure.
|
|
599
1024
|
*/
|
|
600
|
-
registerIdentity(request) {
|
|
601
|
-
|
|
1025
|
+
async registerIdentity(request) {
|
|
1026
|
+
const result = await this.run(client => client.registerIdentity(request));
|
|
1027
|
+
if (result.ok)
|
|
1028
|
+
await this.activateRegisteredIdentity(result.value);
|
|
1029
|
+
return result;
|
|
602
1030
|
}
|
|
603
1031
|
/**
|
|
604
1032
|
* Update the deployment identity's public WNS display name.
|
|
605
1033
|
* @param request - replacement display name selected by the user.
|
|
606
1034
|
* @returns The updated public identity or a closed failure.
|
|
607
1035
|
*/
|
|
608
|
-
updateDisplayName(request) {
|
|
609
|
-
|
|
1036
|
+
async updateDisplayName(request) {
|
|
1037
|
+
const result = await this.run(client => client.updateDisplayName(request));
|
|
1038
|
+
if (result.ok)
|
|
1039
|
+
this.activeIdentityDid = result.value.did;
|
|
1040
|
+
return result;
|
|
1041
|
+
}
|
|
1042
|
+
/** Return the public editable profile for the active identity. */
|
|
1043
|
+
getProfile() {
|
|
1044
|
+
return this.run(client => client.getProfile());
|
|
1045
|
+
}
|
|
1046
|
+
/** Update Display Name, bio, and tags after applying product limits in the Host. */
|
|
1047
|
+
updateProfile(request) {
|
|
1048
|
+
const normalized = normalizeProfileRequest(request);
|
|
1049
|
+
if (normalized === undefined)
|
|
1050
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1051
|
+
return this.run(client => client.updateProfile(normalized));
|
|
1052
|
+
}
|
|
1053
|
+
/** Start durable phone recovery for one existing full Handle. */
|
|
1054
|
+
sendRecoveryOtp(request) {
|
|
1055
|
+
const normalized = normalizeRecoveryOtpRequest(request);
|
|
1056
|
+
if (normalized === undefined)
|
|
1057
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1058
|
+
return this.run(client => client.sendRecoveryOtp(normalized), { allowSignedOut: true });
|
|
1059
|
+
}
|
|
1060
|
+
/** Verify a recovery OTP and freeze its exact intent before the remote commit. */
|
|
1061
|
+
prepareRecovery(request) {
|
|
1062
|
+
const normalized = normalizeRecoveryPrepareRequest(request);
|
|
1063
|
+
if (normalized === undefined)
|
|
1064
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1065
|
+
return this.run(client => client.prepareRecovery(normalized), { allowSignedOut: true });
|
|
1066
|
+
}
|
|
1067
|
+
/** Attempt one prepared recovery commit; uncertain outcomes remain durable in Core. */
|
|
1068
|
+
async activateRecovery(request) {
|
|
1069
|
+
const normalized = normalizeRecoveryOperation(request);
|
|
1070
|
+
if (normalized === undefined)
|
|
1071
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1072
|
+
const result = await this.run(client => client.activateRecovery(normalized), { allowSignedOut: true });
|
|
1073
|
+
if (result.ok && !await this.applyRecoveredSession(result.value)) {
|
|
1074
|
+
return { ok: false, error: failure('remote') };
|
|
1075
|
+
}
|
|
1076
|
+
return result;
|
|
1077
|
+
}
|
|
1078
|
+
/** Read durable recovery state before deciding whether a retry is valid. */
|
|
1079
|
+
async getRecoveryStatus(request) {
|
|
1080
|
+
const normalized = normalizeRecoveryOperation(request);
|
|
1081
|
+
if (normalized === undefined)
|
|
1082
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1083
|
+
const result = await this.run(client => client.getRecoveryStatus(normalized), { allowSignedOut: true });
|
|
1084
|
+
if (result.ok && !await this.applyRecoveredSession(result.value)) {
|
|
1085
|
+
return { ok: false, error: failure('remote') };
|
|
1086
|
+
}
|
|
1087
|
+
return result;
|
|
1088
|
+
}
|
|
1089
|
+
/** Resume only the exact Core-owned operation selected by the browser. */
|
|
1090
|
+
async resumeRecovery(request) {
|
|
1091
|
+
const normalized = normalizeRecoveryOperation(request);
|
|
1092
|
+
if (normalized === undefined)
|
|
1093
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1094
|
+
const result = await this.run(client => client.resumeRecovery(normalized), { allowSignedOut: true });
|
|
1095
|
+
if (result.ok && !await this.applyRecoveredSession(result.value)) {
|
|
1096
|
+
return { ok: false, error: failure('remote') };
|
|
1097
|
+
}
|
|
1098
|
+
return result;
|
|
1099
|
+
}
|
|
1100
|
+
/** Discard only a pre-attempt operation; Core rejects post-attempt deletion. */
|
|
1101
|
+
async discardRecovery(request) {
|
|
1102
|
+
const normalized = normalizeRecoveryOperation(request);
|
|
1103
|
+
if (normalized === undefined)
|
|
1104
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1105
|
+
const result = await this.run(client => client.discardRecovery(normalized), { allowSignedOut: true });
|
|
1106
|
+
return result.ok ? { ok: true, value: { completed: true } } : result;
|
|
610
1107
|
}
|
|
611
1108
|
/**
|
|
612
1109
|
* Resolve one Handle or DID before the browser opens a direct chat.
|
|
@@ -616,6 +1113,96 @@ let AwikiService = (() => {
|
|
|
616
1113
|
resolvePeer(request) {
|
|
617
1114
|
return this.run(client => client.resolvePeer(request.peer));
|
|
618
1115
|
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Create one group, then settle every initial-member invitation without hiding a created group.
|
|
1118
|
+
* @param request - bounded group name and initial Handle/DID values.
|
|
1119
|
+
* @returns The created conversation and per-member outcomes.
|
|
1120
|
+
*/
|
|
1121
|
+
async createGroup(request) {
|
|
1122
|
+
const normalized = normalizeCreateGroupRequest(request);
|
|
1123
|
+
if (normalized === undefined)
|
|
1124
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1125
|
+
const created = await this.run(client => client.createGroup(normalized.name));
|
|
1126
|
+
if (!created.ok)
|
|
1127
|
+
return created;
|
|
1128
|
+
const addedMembers = [];
|
|
1129
|
+
const failedMembers = [];
|
|
1130
|
+
for (const member of normalized.members) {
|
|
1131
|
+
const result = await this.run(client => client.addGroupMember(created.value.groupDid, member));
|
|
1132
|
+
if (result.ok)
|
|
1133
|
+
addedMembers.push(result.value);
|
|
1134
|
+
else
|
|
1135
|
+
failedMembers.push({ member, error: result.error });
|
|
1136
|
+
}
|
|
1137
|
+
return {
|
|
1138
|
+
ok: true,
|
|
1139
|
+
value: { conversation: created.value, addedMembers, failedMembers },
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
/** Return one authoritative group snapshot for permission-aware UI. */
|
|
1143
|
+
getGroup(request) {
|
|
1144
|
+
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
1145
|
+
if (groupDid === undefined)
|
|
1146
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1147
|
+
return this.run(client => client.getGroup(groupDid));
|
|
1148
|
+
}
|
|
1149
|
+
/** Join one open group and return its authoritative membership state. */
|
|
1150
|
+
joinGroup(request) {
|
|
1151
|
+
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
1152
|
+
if (groupDid === undefined)
|
|
1153
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1154
|
+
return this.run(client => client.joinGroup(groupDid));
|
|
1155
|
+
}
|
|
1156
|
+
/** Leave one group. Core rejects owner leave and unsupported security profiles. */
|
|
1157
|
+
async leaveGroup(request) {
|
|
1158
|
+
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
1159
|
+
if (groupDid === undefined)
|
|
1160
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1161
|
+
const result = await this.run(client => client.leaveGroup(groupDid));
|
|
1162
|
+
return result.ok ? { ok: true, value: { completed: true } } : result;
|
|
1163
|
+
}
|
|
1164
|
+
/** Read one authoritative versioned member page. */
|
|
1165
|
+
listGroupMembers(request) {
|
|
1166
|
+
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
1167
|
+
if (groupDid === undefined
|
|
1168
|
+
|| (request.cursor !== undefined && (typeof request.cursor !== 'string' || request.cursor.length > 4_096))
|
|
1169
|
+
|| (request.limit !== undefined && (!Number.isSafeInteger(request.limit) || request.limit < 1 || request.limit > 100))) {
|
|
1170
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1171
|
+
}
|
|
1172
|
+
return this.run(client => client.listGroupMembers({
|
|
1173
|
+
groupDid,
|
|
1174
|
+
...request.cursor === undefined ? {} : { cursor: request.cursor },
|
|
1175
|
+
...request.limit === undefined ? {} : { limit: request.limit },
|
|
1176
|
+
}));
|
|
1177
|
+
}
|
|
1178
|
+
/** Invite one ordinary member after group creation. */
|
|
1179
|
+
addGroupMember(request) {
|
|
1180
|
+
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
1181
|
+
const member = normalizeMember(request?.member);
|
|
1182
|
+
if (groupDid === undefined || member === undefined || (request.role !== undefined && request.role !== 'member')) {
|
|
1183
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1184
|
+
}
|
|
1185
|
+
return this.run(client => client.addGroupMember(groupDid, member));
|
|
1186
|
+
}
|
|
1187
|
+
/** Remove one member. The authoritative Core role check remains decisive. */
|
|
1188
|
+
removeGroupMember(request) {
|
|
1189
|
+
const groupDid = normalizeGroupDid(request?.groupDid);
|
|
1190
|
+
const member = normalizeMember(request?.member);
|
|
1191
|
+
if (groupDid === undefined || member === undefined) {
|
|
1192
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1193
|
+
}
|
|
1194
|
+
return this.run(client => client.removeGroupMember(groupDid, member));
|
|
1195
|
+
}
|
|
1196
|
+
/** Resume durable group membership convergence after account sync restores old groups. */
|
|
1197
|
+
async resumeGroupRebindRecovery() {
|
|
1198
|
+
const result = await this.run(client => client.resumeGroupRebindRecovery());
|
|
1199
|
+
if (!result.ok)
|
|
1200
|
+
return result;
|
|
1201
|
+
const summary = publicGroupRebindRecoverySummary(result.value);
|
|
1202
|
+
return summary === undefined
|
|
1203
|
+
? { ok: false, error: failure('remote') }
|
|
1204
|
+
: { ok: true, value: summary };
|
|
1205
|
+
}
|
|
619
1206
|
/**
|
|
620
1207
|
* List direct and existing group conversations.
|
|
621
1208
|
* @param request - Optional opaque cursor and page limit.
|
|
@@ -632,6 +1219,10 @@ let AwikiService = (() => {
|
|
|
632
1219
|
getHistory(request) {
|
|
633
1220
|
return this.run(client => client.getHistory(request));
|
|
634
1221
|
}
|
|
1222
|
+
/** Read one committed local conversation page without sync, history, or Directory RPC. */
|
|
1223
|
+
getLocalHistory(request) {
|
|
1224
|
+
return this.run(client => client.getLocalHistory(request));
|
|
1225
|
+
}
|
|
635
1226
|
/**
|
|
636
1227
|
* Read real AWiki history, enforce range and byte caps, then invoke the configured model once.
|
|
637
1228
|
* @param request - selected conversation and its unread snapshot at open time.
|
|
@@ -736,7 +1327,10 @@ let AwikiService = (() => {
|
|
|
736
1327
|
* @returns The accepted public message or a closed failure.
|
|
737
1328
|
*/
|
|
738
1329
|
sendText(request) {
|
|
739
|
-
|
|
1330
|
+
const normalized = normalizeSendTextRequest(request);
|
|
1331
|
+
if (normalized === undefined)
|
|
1332
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1333
|
+
return this.run(client => client.sendText(normalized));
|
|
740
1334
|
}
|
|
741
1335
|
/**
|
|
742
1336
|
* Upload and send one attachment after Host validation.
|
|
@@ -747,16 +1341,29 @@ let AwikiService = (() => {
|
|
|
747
1341
|
const decoded = decodeAttachment(request.bytesBase64, this.resolved.attachmentMaxBytes);
|
|
748
1342
|
if (!decoded.ok)
|
|
749
1343
|
return decoded;
|
|
750
|
-
return this.run(client =>
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
1344
|
+
return this.run(async (client) => {
|
|
1345
|
+
const message = await client.sendAttachment({
|
|
1346
|
+
target: request.target,
|
|
1347
|
+
attachment: {
|
|
1348
|
+
fileName: request.fileName,
|
|
1349
|
+
mimeType: request.mimeType,
|
|
1350
|
+
bytes: decoded.value,
|
|
1351
|
+
},
|
|
1352
|
+
...request.caption === undefined ? {} : { caption: request.caption },
|
|
1353
|
+
idempotencyKey: request.idempotencyKey,
|
|
1354
|
+
});
|
|
1355
|
+
if (message.content.kind === 'attachment') {
|
|
1356
|
+
const ownerDid = this.activeIdentityDid ?? (await client.getIdentity().catch(() => null))?.did;
|
|
1357
|
+
if (ownerDid !== undefined) {
|
|
1358
|
+
this.activeIdentityDid = ownerDid;
|
|
1359
|
+
await this.imageAttachmentCache.write(ownerDid, message.id, {
|
|
1360
|
+
attachment: message.content.attachment,
|
|
1361
|
+
bytes: decoded.value,
|
|
1362
|
+
}).catch(() => undefined);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return message;
|
|
1366
|
+
}, { skipAttachmentByteValidation: true });
|
|
760
1367
|
}
|
|
761
1368
|
/**
|
|
762
1369
|
* Download and encode one provider-verified attachment.
|
|
@@ -764,16 +1371,112 @@ let AwikiService = (() => {
|
|
|
764
1371
|
* @returns Verified public metadata and canonical Base64 bytes, or a closed failure.
|
|
765
1372
|
*/
|
|
766
1373
|
async downloadAttachment(request) {
|
|
767
|
-
|
|
1374
|
+
try {
|
|
1375
|
+
if (await this.isSignedOut())
|
|
1376
|
+
return { ok: false, error: failure('signed-out') };
|
|
1377
|
+
}
|
|
1378
|
+
catch {
|
|
1379
|
+
return { ok: false, error: failure('remote') };
|
|
1380
|
+
}
|
|
1381
|
+
if (this.provider !== undefined && this.activeIdentityDid !== undefined) {
|
|
1382
|
+
const cached = await this.imageAttachmentCache.read(this.activeIdentityDid, request);
|
|
1383
|
+
if (cached !== undefined)
|
|
1384
|
+
return this.publicDownloadedAttachment(cached);
|
|
1385
|
+
}
|
|
1386
|
+
const result = await this.run(async (client) => {
|
|
1387
|
+
const identity = this.activeIdentityDid === undefined ? await client.getIdentity() : undefined;
|
|
1388
|
+
const ownerDid = this.activeIdentityDid ?? identity?.did;
|
|
1389
|
+
if (ownerDid === undefined)
|
|
1390
|
+
throw Object.assign(new Error('not registered'), { name: 'AwikiSdkError', code: 'not-registered' });
|
|
1391
|
+
this.activeIdentityDid = ownerDid;
|
|
1392
|
+
const cached = await this.imageAttachmentCache.read(ownerDid, request);
|
|
1393
|
+
if (cached !== undefined)
|
|
1394
|
+
return cached;
|
|
1395
|
+
const downloaded = await client.downloadAttachment(request);
|
|
1396
|
+
await this.imageAttachmentCache.write(ownerDid, request.messageId, downloaded).catch(() => undefined);
|
|
1397
|
+
return downloaded;
|
|
1398
|
+
}, { skipAttachmentByteValidation: true });
|
|
768
1399
|
if (!result.ok)
|
|
769
1400
|
return result;
|
|
770
|
-
|
|
1401
|
+
return this.publicDownloadedAttachment(result.value);
|
|
1402
|
+
}
|
|
1403
|
+
/** Revalidate cached/provider bytes before crossing the browser Remote boundary. */
|
|
1404
|
+
publicDownloadedAttachment(value) {
|
|
1405
|
+
if (value.bytes.byteLength > this.resolved.attachmentMaxBytes) {
|
|
771
1406
|
return { ok: false, error: failure('attachment-too-large') };
|
|
772
1407
|
}
|
|
773
|
-
if (
|
|
1408
|
+
if (value.bytes.byteLength !== value.attachment.size) {
|
|
774
1409
|
return { ok: false, error: failure('remote') };
|
|
775
1410
|
}
|
|
776
|
-
return { ok: true, value: downloadedAttachment(
|
|
1411
|
+
return { ok: true, value: downloadedAttachment(value) };
|
|
1412
|
+
}
|
|
1413
|
+
/** Return the deployment identity's public mailbox state. */
|
|
1414
|
+
getMailAccount() {
|
|
1415
|
+
return this.run(client => client.getMailAccount());
|
|
1416
|
+
}
|
|
1417
|
+
/** List one bounded mailbox page on explicit browser/tool demand. */
|
|
1418
|
+
async listMailInbox(request) {
|
|
1419
|
+
let normalized;
|
|
1420
|
+
try {
|
|
1421
|
+
normalized = mailInboxRequest(request ?? {});
|
|
1422
|
+
}
|
|
1423
|
+
catch {
|
|
1424
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1425
|
+
}
|
|
1426
|
+
return this.run(async (client) => {
|
|
1427
|
+
if (normalized.folder !== 'sent')
|
|
1428
|
+
return client.listMailInbox(normalized);
|
|
1429
|
+
return this.sentMailStore.list(await this.ownerDid(client), normalized);
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
/** Read one bounded plain-text mail message. */
|
|
1433
|
+
async readMail(request) {
|
|
1434
|
+
let normalized;
|
|
1435
|
+
try {
|
|
1436
|
+
normalized = mailReadRequest(request);
|
|
1437
|
+
}
|
|
1438
|
+
catch {
|
|
1439
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1440
|
+
}
|
|
1441
|
+
return this.run(async (client) => {
|
|
1442
|
+
if (!isLocalSentMailId(normalized.messageId))
|
|
1443
|
+
return client.readMail(normalized);
|
|
1444
|
+
const local = await this.sentMailStore.read(await this.ownerDid(client), normalized.messageId);
|
|
1445
|
+
if (local === undefined) {
|
|
1446
|
+
throw Object.assign(new Error('sent mail not found'), { name: 'AwikiSdkError', code: 'not-found' });
|
|
1447
|
+
}
|
|
1448
|
+
return local;
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
/** Mark explicitly selected mail messages read. Browser callers require an explicit click. */
|
|
1452
|
+
markMailRead(request) {
|
|
1453
|
+
return this.runValidatedMail(() => mailMarkReadRequest(request), (client, normalized) => client.markMailRead(normalized));
|
|
1454
|
+
}
|
|
1455
|
+
/** Send one plain-text mail once. Browser callers require an explicit confirmation. */
|
|
1456
|
+
async sendMail(request) {
|
|
1457
|
+
let normalized;
|
|
1458
|
+
try {
|
|
1459
|
+
normalized = mailSendRequest(request);
|
|
1460
|
+
}
|
|
1461
|
+
catch {
|
|
1462
|
+
return { ok: false, error: failure('invalid-request') };
|
|
1463
|
+
}
|
|
1464
|
+
return this.run(async (client) => {
|
|
1465
|
+
const result = await client.sendMail(normalized);
|
|
1466
|
+
if (!result.accepted)
|
|
1467
|
+
return result;
|
|
1468
|
+
try {
|
|
1469
|
+
const ownerDid = await this.ownerDid(client);
|
|
1470
|
+
const account = await client.getMailAccount().catch(() => undefined);
|
|
1471
|
+
await this.sentMailStore.append(ownerDid, normalized, result, account);
|
|
1472
|
+
return result;
|
|
1473
|
+
}
|
|
1474
|
+
catch {
|
|
1475
|
+
return result.warnings.length >= 100
|
|
1476
|
+
? result
|
|
1477
|
+
: { ...result, warnings: [...result.warnings, 'Sent history could not be saved locally.'] };
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
777
1480
|
}
|
|
778
1481
|
/**
|
|
779
1482
|
* Permanently remove the exact SDK-owned local state after an explicit browser acknowledgement.
|
|
@@ -786,13 +1489,25 @@ let AwikiService = (() => {
|
|
|
786
1489
|
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
787
1490
|
}
|
|
788
1491
|
return this.mutateSession(async () => {
|
|
1492
|
+
const provider = this.provider;
|
|
1493
|
+
if (provider !== undefined)
|
|
1494
|
+
await this.stopListener(provider);
|
|
789
1495
|
const result = await this.run(client => client.clearLocalData(), { allowSignedOut: true });
|
|
790
|
-
if (!result.ok)
|
|
1496
|
+
if (!result.ok) {
|
|
1497
|
+
if (provider !== undefined && this.provider === provider)
|
|
1498
|
+
void this.startListener(provider);
|
|
791
1499
|
return result;
|
|
1500
|
+
}
|
|
792
1501
|
try {
|
|
1502
|
+
await this.imageAttachmentCache.clear();
|
|
1503
|
+
await this.sentMailStore.clear();
|
|
793
1504
|
await this.sessionStore.signIn();
|
|
794
1505
|
this.signedOut = false;
|
|
1506
|
+
this.activeIdentityDid = undefined;
|
|
795
1507
|
this.invalidateSummaries();
|
|
1508
|
+
this.publishSession({ status: 'unregistered' });
|
|
1509
|
+
if (provider !== undefined && this.provider === provider)
|
|
1510
|
+
void this.startListener(provider);
|
|
796
1511
|
return result;
|
|
797
1512
|
}
|
|
798
1513
|
catch {
|
|
@@ -800,6 +1515,45 @@ let AwikiService = (() => {
|
|
|
800
1515
|
}
|
|
801
1516
|
});
|
|
802
1517
|
}
|
|
1518
|
+
/** Re-enter only after Core confirms that the exact recovered identity is applied locally. */
|
|
1519
|
+
async applyRecoveredSession(progress) {
|
|
1520
|
+
if (progress.phase !== 'applied')
|
|
1521
|
+
return true;
|
|
1522
|
+
return this.mutateSession(async () => {
|
|
1523
|
+
const provider = this.provider;
|
|
1524
|
+
if (provider === undefined)
|
|
1525
|
+
return false;
|
|
1526
|
+
try {
|
|
1527
|
+
const identity = await provider.client.getIdentity();
|
|
1528
|
+
if (identity === null || identity.did !== progress.currentDid)
|
|
1529
|
+
return false;
|
|
1530
|
+
if (this.signedOut === false && this.activeIdentityDid === identity.did)
|
|
1531
|
+
return true;
|
|
1532
|
+
await this.sessionStore.signIn();
|
|
1533
|
+
this.signedOut = false;
|
|
1534
|
+
this.activeIdentityDid = identity.did;
|
|
1535
|
+
this.invalidateSummaries();
|
|
1536
|
+
const session = { status: 'active', identity };
|
|
1537
|
+
this.publishSession(session);
|
|
1538
|
+
await provider.listenerStartup;
|
|
1539
|
+
await this.startListener(provider);
|
|
1540
|
+
return true;
|
|
1541
|
+
}
|
|
1542
|
+
catch {
|
|
1543
|
+
return false;
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
/** Publish one newly registered identity and start its listener through the existing session path. */
|
|
1548
|
+
async activateRegisteredIdentity(identity) {
|
|
1549
|
+
this.activeIdentityDid = identity.did;
|
|
1550
|
+
this.publishSession({ status: 'active', identity });
|
|
1551
|
+
const provider = this.provider;
|
|
1552
|
+
if (provider !== undefined) {
|
|
1553
|
+
await provider.listenerStartup;
|
|
1554
|
+
await this.startListener(provider);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
803
1557
|
/** Invalidate cached session work and cancel every model request still owned by the old session. */
|
|
804
1558
|
invalidateSummaries() {
|
|
805
1559
|
this.sessionRevision += 1;
|
|
@@ -807,6 +1561,20 @@ let AwikiService = (() => {
|
|
|
807
1561
|
controller.abort();
|
|
808
1562
|
this.activeSummaryRequests.clear();
|
|
809
1563
|
}
|
|
1564
|
+
/** Publish a committed session transition to same-process Host consumers. */
|
|
1565
|
+
publishSession(session) {
|
|
1566
|
+
this.hostContext.emit('awiki/session', session);
|
|
1567
|
+
}
|
|
1568
|
+
/** Resolve and cache the owner binding required by private Host-side projections. */
|
|
1569
|
+
async ownerDid(client) {
|
|
1570
|
+
const identity = this.activeIdentityDid === undefined ? await client.getIdentity() : undefined;
|
|
1571
|
+
const ownerDid = this.activeIdentityDid ?? identity?.did;
|
|
1572
|
+
if (ownerDid === undefined) {
|
|
1573
|
+
throw Object.assign(new Error('not registered'), { name: 'AwikiSdkError', code: 'not-registered' });
|
|
1574
|
+
}
|
|
1575
|
+
this.activeIdentityDid = ownerDid;
|
|
1576
|
+
return ownerDid;
|
|
1577
|
+
}
|
|
810
1578
|
/** Invoke the current client and normalize every rejection to a public result. */
|
|
811
1579
|
async run(operation, options = {}) {
|
|
812
1580
|
try {
|
|
@@ -826,6 +1594,17 @@ let AwikiService = (() => {
|
|
|
826
1594
|
return { ok: false, error: normalizeFailure(error) };
|
|
827
1595
|
}
|
|
828
1596
|
}
|
|
1597
|
+
/** Validate mail input before entering the provider and preserve fixed public failures. */
|
|
1598
|
+
runValidatedMail(validate, operation) {
|
|
1599
|
+
let request;
|
|
1600
|
+
try {
|
|
1601
|
+
request = validate();
|
|
1602
|
+
}
|
|
1603
|
+
catch {
|
|
1604
|
+
return Promise.resolve({ ok: false, error: failure('invalid-request') });
|
|
1605
|
+
}
|
|
1606
|
+
return this.run(client => operation(client, request));
|
|
1607
|
+
}
|
|
829
1608
|
/** Read and cache the private Host-owned session marker. */
|
|
830
1609
|
async isSignedOut() {
|
|
831
1610
|
this.signedOut ??= await this.sessionStore.isSignedOut();
|
|
@@ -879,11 +1658,163 @@ let AwikiService = (() => {
|
|
|
879
1658
|
this.sessionMutation = pending.then(() => undefined, () => undefined);
|
|
880
1659
|
return pending;
|
|
881
1660
|
}
|
|
1661
|
+
/** Start one exact identity-bound listener, atomically releasing a failed startup. */
|
|
1662
|
+
startListener(provider) {
|
|
1663
|
+
if (!this.resolved.listenerEnabled
|
|
1664
|
+
|| provider.listener !== undefined
|
|
1665
|
+
|| provider.listenerRecoveryBlocked
|
|
1666
|
+
|| this.provider !== provider) {
|
|
1667
|
+
return Promise.resolve();
|
|
1668
|
+
}
|
|
1669
|
+
if (provider.listenerStartup !== undefined)
|
|
1670
|
+
return provider.listenerStartup;
|
|
1671
|
+
if (provider.listenerCleanup !== undefined || provider.listenerRestartTimer !== undefined)
|
|
1672
|
+
return Promise.resolve();
|
|
1673
|
+
const workspaceContext = this.workspaceContext;
|
|
1674
|
+
const source = provider.client.listener;
|
|
1675
|
+
if (workspaceContext === undefined || source === undefined)
|
|
1676
|
+
return Promise.resolve();
|
|
1677
|
+
const generation = provider.listenerGeneration;
|
|
1678
|
+
const logger = this.ctx.logger('awiki-listener');
|
|
1679
|
+
const startup = (async () => {
|
|
1680
|
+
if (await this.isSignedOut())
|
|
1681
|
+
return;
|
|
1682
|
+
if (await provider.client.getIdentity() === null)
|
|
1683
|
+
return;
|
|
1684
|
+
if (!this.listenerFenceMatches(provider, workspaceContext, generation))
|
|
1685
|
+
return;
|
|
1686
|
+
const agents = new DshAwikiListenerAgentRuntime(workspaceContext, this.resolved.listener.workspacePath);
|
|
1687
|
+
const listener = new AwikiAgentListener(source, agents, this.resolved.listener, logger);
|
|
1688
|
+
provider.listener = listener;
|
|
1689
|
+
try {
|
|
1690
|
+
await listener.start();
|
|
1691
|
+
}
|
|
1692
|
+
catch (error) {
|
|
1693
|
+
if (provider.listener === listener)
|
|
1694
|
+
delete provider.listener;
|
|
1695
|
+
try {
|
|
1696
|
+
await listener.dispose();
|
|
1697
|
+
}
|
|
1698
|
+
catch {
|
|
1699
|
+
provider.listenerRecoveryBlocked = true;
|
|
1700
|
+
}
|
|
1701
|
+
if (!provider.listenerRecoveryBlocked && provider.listenerRestartAttempt > 0) {
|
|
1702
|
+
this.scheduleListenerRestart(provider, workspaceContext, generation);
|
|
1703
|
+
}
|
|
1704
|
+
throw error;
|
|
1705
|
+
}
|
|
1706
|
+
if (!this.listenerFenceMatches(provider, workspaceContext, generation)) {
|
|
1707
|
+
if (provider.listener === listener)
|
|
1708
|
+
delete provider.listener;
|
|
1709
|
+
try {
|
|
1710
|
+
await listener.dispose();
|
|
1711
|
+
}
|
|
1712
|
+
catch (error) {
|
|
1713
|
+
provider.listenerRecoveryBlocked = true;
|
|
1714
|
+
throw error;
|
|
1715
|
+
}
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
provider.listenerStartedAt = Date.now();
|
|
1719
|
+
void listener.whenTerminated().then((result) => {
|
|
1720
|
+
if (result.kind === 'failed') {
|
|
1721
|
+
this.releaseFailedListener(provider, listener, workspaceContext, generation, result.error);
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
})();
|
|
1725
|
+
const observed = startup
|
|
1726
|
+
.catch((error) => {
|
|
1727
|
+
logger.warn('AWiki listener startup failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
1728
|
+
})
|
|
1729
|
+
.finally(() => {
|
|
1730
|
+
if (provider.listenerStartup === observed)
|
|
1731
|
+
delete provider.listenerStartup;
|
|
1732
|
+
});
|
|
1733
|
+
provider.listenerStartup = observed;
|
|
1734
|
+
return observed;
|
|
1735
|
+
}
|
|
1736
|
+
async stopListener(provider) {
|
|
1737
|
+
provider.listenerGeneration += 1;
|
|
1738
|
+
provider.listenerRestartAttempt = 0;
|
|
1739
|
+
delete provider.listenerStartedAt;
|
|
1740
|
+
const timer = provider.listenerRestartTimer;
|
|
1741
|
+
if (timer !== undefined) {
|
|
1742
|
+
delete provider.listenerRestartTimer;
|
|
1743
|
+
clearTimeout(timer);
|
|
1744
|
+
}
|
|
1745
|
+
await provider.listenerStartup;
|
|
1746
|
+
await provider.listenerCleanup;
|
|
1747
|
+
const listener = provider.listener;
|
|
1748
|
+
if (listener === undefined)
|
|
1749
|
+
return;
|
|
1750
|
+
delete provider.listener;
|
|
1751
|
+
try {
|
|
1752
|
+
await listener.dispose();
|
|
1753
|
+
}
|
|
1754
|
+
catch (error) {
|
|
1755
|
+
provider.listenerRecoveryBlocked = true;
|
|
1756
|
+
throw error;
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
listenerFenceMatches(provider, workspaceContext, generation) {
|
|
1760
|
+
return this.provider === provider
|
|
1761
|
+
&& this.workspaceContext === workspaceContext
|
|
1762
|
+
&& provider.listenerGeneration === generation;
|
|
1763
|
+
}
|
|
1764
|
+
releaseFailedListener(provider, listener, workspaceContext, generation, error) {
|
|
1765
|
+
if (provider.listener !== listener)
|
|
1766
|
+
return;
|
|
1767
|
+
delete provider.listener;
|
|
1768
|
+
const startedAt = provider.listenerStartedAt;
|
|
1769
|
+
delete provider.listenerStartedAt;
|
|
1770
|
+
if (startedAt !== undefined && Date.now() - startedAt >= LISTENER_STABLE_RESET_MS) {
|
|
1771
|
+
provider.listenerRestartAttempt = 0;
|
|
1772
|
+
}
|
|
1773
|
+
const logger = this.ctx.logger('awiki-listener');
|
|
1774
|
+
logger.warn('AWiki listener lifecycle failed: %s', error instanceof Error ? error.message : 'unknown failure');
|
|
1775
|
+
let disposed = false;
|
|
1776
|
+
const cleanup = listener.dispose()
|
|
1777
|
+
.then(() => { disposed = true; }, (cleanupError) => {
|
|
1778
|
+
provider.listenerRecoveryBlocked = true;
|
|
1779
|
+
logger.warn('AWiki listener cleanup failed: %s', cleanupError instanceof Error ? cleanupError.message : 'unknown failure');
|
|
1780
|
+
});
|
|
1781
|
+
const observed = cleanup.finally(() => {
|
|
1782
|
+
if (provider.listenerCleanup === observed)
|
|
1783
|
+
delete provider.listenerCleanup;
|
|
1784
|
+
if (disposed)
|
|
1785
|
+
this.scheduleListenerRestart(provider, workspaceContext, generation);
|
|
1786
|
+
});
|
|
1787
|
+
provider.listenerCleanup = observed;
|
|
1788
|
+
}
|
|
1789
|
+
scheduleListenerRestart(provider, workspaceContext, generation) {
|
|
1790
|
+
if (provider.listenerRestartTimer !== undefined
|
|
1791
|
+
|| !this.listenerFenceMatches(provider, workspaceContext, generation))
|
|
1792
|
+
return;
|
|
1793
|
+
provider.listenerRestartAttempt = Math.min(provider.listenerRestartAttempt + 1, LISTENER_RESTART_MAX_ATTEMPT);
|
|
1794
|
+
const exponent = provider.listenerRestartAttempt - 1;
|
|
1795
|
+
const delay = Math.min(LISTENER_RESTART_BASE_DELAY_MS * 2 ** exponent, LISTENER_RESTART_MAX_DELAY_MS);
|
|
1796
|
+
const timer = setTimeout(() => {
|
|
1797
|
+
if (provider.listenerRestartTimer !== timer)
|
|
1798
|
+
return;
|
|
1799
|
+
delete provider.listenerRestartTimer;
|
|
1800
|
+
if (!this.listenerFenceMatches(provider, workspaceContext, generation))
|
|
1801
|
+
return;
|
|
1802
|
+
void this.startListener(provider);
|
|
1803
|
+
}, delay);
|
|
1804
|
+
provider.listenerRestartTimer = timer;
|
|
1805
|
+
}
|
|
882
1806
|
/** Clear one exact provider slot before joining its one shared disposal. */
|
|
883
1807
|
disposeProvider(provider) {
|
|
884
1808
|
if (this.provider === provider)
|
|
885
1809
|
this.provider = undefined;
|
|
886
|
-
provider.disposal ??=
|
|
1810
|
+
provider.disposal ??= (async () => {
|
|
1811
|
+
try {
|
|
1812
|
+
await this.stopListener(provider);
|
|
1813
|
+
}
|
|
1814
|
+
finally {
|
|
1815
|
+
await provider.client.dispose();
|
|
1816
|
+
}
|
|
1817
|
+
})();
|
|
887
1818
|
return provider.disposal;
|
|
888
1819
|
}
|
|
889
1820
|
};
|