@awiki/dsh-plugin 0.3.10 → 0.3.11-rc.2
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 +14 -5
- package/README.zh.md +11 -4
- package/lib/client.js +2067 -272
- package/lib/client.js.map +1 -1
- package/lib/index.js +536 -167
- package/lib/model-proxy-contract.js +8 -8
- package/lib/provider.js +32 -2
- package/lib/{sdk-adapter-CYcJH7sM.mjs → sdk-adapter-DpqT2en9.mjs} +17 -7
- package/lib/typert.host.js +181 -117
- package/lib/typert.remote-client.d.ts +3 -1
- package/lib/typert.remote-client.d.ts.map +1 -1
- package/lib/typert.remote-client.js +181 -117
- package/lib/types/base64.d.ts +4 -0
- package/lib/types/base64.d.ts.map +1 -0
- package/lib/types/base64.js +28 -0
- package/lib/types/base64.js.map +1 -0
- package/lib/types/client/AwikiIdentityAccess.d.ts +6 -4
- package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
- package/lib/types/client/AwikiIdentityAccess.js +147 -58
- package/lib/types/client/AwikiIdentityAccess.js.map +1 -1
- package/lib/types/client/AwikiMail.d.ts +1 -1
- package/lib/types/client/AwikiMail.d.ts.map +1 -1
- package/lib/types/client/AwikiMail.js +139 -6
- package/lib/types/client/AwikiMail.js.map +1 -1
- package/lib/types/client/AwikiOverlay.js +2 -2
- package/lib/types/client/AwikiOverlay.js.map +1 -1
- package/lib/types/client/AwikiRecoveryForm.d.ts +1 -0
- package/lib/types/client/AwikiRecoveryForm.d.ts.map +1 -1
- package/lib/types/client/AwikiRecoveryForm.js +3 -2
- package/lib/types/client/AwikiRecoveryForm.js.map +1 -1
- package/lib/types/client/AwikiSettingsSection.d.ts +1 -1
- package/lib/types/client/AwikiSettingsSection.d.ts.map +1 -1
- package/lib/types/client/awiki-client-bridge.d.ts +1 -1
- package/lib/types/client/awiki-client-bridge.d.ts.map +1 -1
- package/lib/types/client/awiki-client-bridge.js +1 -1
- package/lib/types/client/awiki-client-bridge.js.map +1 -1
- package/lib/types/client/controller.d.ts +15 -1
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +303 -54
- package/lib/types/client/controller.js.map +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +4 -1
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/mail-attachment.d.ts +40 -0
- package/lib/types/client/mail-attachment.d.ts.map +1 -0
- package/lib/types/client/mail-attachment.js +187 -0
- package/lib/types/client/mail-attachment.js.map +1 -0
- package/lib/types/client/otp-cooldown.d.ts +1 -0
- package/lib/types/client/otp-cooldown.d.ts.map +1 -1
- package/lib/types/client/otp-cooldown.js +12 -1
- package/lib/types/client/otp-cooldown.js.map +1 -1
- package/lib/types/client/settings-controller.d.ts +4 -2
- package/lib/types/client/settings-controller.d.ts.map +1 -1
- package/lib/types/client/settings-controller.js +24 -8
- package/lib/types/client/settings-controller.js.map +1 -1
- package/lib/types/client/slots.d.ts +6 -1
- package/lib/types/client/slots.d.ts.map +1 -1
- package/lib/types/client/store.d.ts +1 -1
- package/lib/types/client/store.d.ts.map +1 -1
- package/lib/types/client/store.js +1 -1
- package/lib/types/client/store.js.map +1 -1
- package/lib/types/external-http-auth.d.ts +3 -1
- package/lib/types/external-http-auth.d.ts.map +1 -1
- package/lib/types/external-http-auth.js +2 -2
- package/lib/types/external-http-auth.js.map +1 -1
- package/lib/types/identity-handle.d.ts +5 -0
- package/lib/types/identity-handle.d.ts.map +1 -0
- package/lib/types/identity-handle.js +33 -0
- package/lib/types/identity-handle.js.map +1 -0
- package/lib/types/index.d.ts +11 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +101 -20
- package/lib/types/index.js.map +1 -1
- package/lib/types/listener.d.ts.map +1 -1
- package/lib/types/listener.js +2 -1
- package/lib/types/listener.js.map +1 -1
- package/lib/types/mail-attachment-client.d.ts +14 -0
- package/lib/types/mail-attachment-client.d.ts.map +1 -0
- package/lib/types/mail-attachment-client.js +173 -0
- package/lib/types/mail-attachment-client.js.map +1 -0
- package/lib/types/mail.d.ts +28 -2
- package/lib/types/mail.d.ts.map +1 -1
- package/lib/types/mail.js +79 -2
- package/lib/types/mail.js.map +1 -1
- package/lib/types/model-proxy-contract.d.ts +8 -8
- package/lib/types/model-proxy-contract.d.ts.map +1 -1
- package/lib/types/model-proxy-contract.js +8 -8
- package/lib/types/model-proxy-contract.js.map +1 -1
- package/lib/types/package-version.generated.d.ts +2 -2
- package/lib/types/package-version.generated.d.ts.map +1 -1
- package/lib/types/package-version.generated.js +2 -2
- package/lib/types/package-version.generated.js.map +1 -1
- package/lib/types/provider.d.ts +1 -1
- package/lib/types/provider.d.ts.map +1 -1
- package/lib/types/provider.js +12 -1
- package/lib/types/provider.js.map +1 -1
- package/lib/types/registration-policy.d.ts +5 -0
- package/lib/types/registration-policy.d.ts.map +1 -0
- package/lib/types/registration-policy.js +13 -0
- package/lib/types/registration-policy.js.map +1 -0
- package/lib/types/sdk-adapter.d.ts +2 -1
- package/lib/types/sdk-adapter.d.ts.map +1 -1
- package/lib/types/sdk-adapter.js +13 -4
- package/lib/types/sdk-adapter.js.map +1 -1
- package/lib/types/settings-rpc-contract.d.ts +14 -14
- package/lib/types/settings-rpc-contract.d.ts.map +1 -1
- package/lib/types/settings-rpc-contract.js +14 -14
- package/lib/types/settings-rpc-contract.js.map +1 -1
- package/lib/types/settings-rpc.d.ts.map +1 -1
- package/lib/types/settings-rpc.js +2 -2
- package/lib/types/settings-rpc.js.map +1 -1
- package/lib/types/settings-transport.d.ts +7 -0
- package/lib/types/settings-transport.d.ts.map +1 -0
- package/lib/types/settings-transport.js +52 -0
- package/lib/types/settings-transport.js.map +1 -0
- package/lib/types/tools.d.ts +1 -1
- package/lib/types/tools.d.ts.map +1 -1
- package/lib/types/tools.js +20 -4
- package/lib/types/tools.js.map +1 -1
- package/lib/types/types.d.ts +39 -2
- 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/package.json +84 -75
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import { AwikiDraftStore } from "./drafts.js";
|
|
2
|
+
import { SHORT_HANDLE_INVITE_MESSAGE } from "../registration-policy.js";
|
|
2
3
|
import { IndexedDbAwikiBrowserImageCache, } from "./image-cache.js";
|
|
3
4
|
import { clearMailBrowserCache } from "./mail-list-cache.js";
|
|
5
|
+
const GENERIC_BUSINESS_FAILURE_MESSAGE = 'AWiki 暂时无法完成此操作,请稍后重试。';
|
|
6
|
+
const GENERIC_CARRIER_FAILURE_MESSAGE = '暂时无法连接 AWiki Host,请稍后重试。';
|
|
7
|
+
const GENERIC_THROW_FAILURE_MESSAGE = 'AWiki 调用暂时失败,请稍后重试。';
|
|
8
|
+
/** Project only a stable business code into Browser copy; provider messages remain Host-only. */
|
|
9
|
+
function genericFailureMessage(failure) {
|
|
10
|
+
switch (failure.code) {
|
|
11
|
+
case 'network':
|
|
12
|
+
return '无法连接 AWiki 服务,请检查网络后重试。';
|
|
13
|
+
case 'rate-limited':
|
|
14
|
+
return 'AWiki 请求过于频繁,请稍后重试。';
|
|
15
|
+
case 'not-registered':
|
|
16
|
+
return '请先注册 AWiki 身份后重试。';
|
|
17
|
+
case 'signed-out':
|
|
18
|
+
return '当前 AWiki 身份已退出,请重新进入后重试。';
|
|
19
|
+
case 'identity-recovery-required':
|
|
20
|
+
return '当前设备的 AWiki 身份凭证已失效,请重新恢复身份。';
|
|
21
|
+
case 'device-rejoin-required':
|
|
22
|
+
return '当前设备已被撤销,请重新申请加入。';
|
|
23
|
+
default:
|
|
24
|
+
return GENERIC_BUSINESS_FAILURE_MESSAGE;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Carrier diagnostics are not a Browser contract, including for unknown carrier codes. */
|
|
28
|
+
function genericCarrierFailureMessage(_failure) {
|
|
29
|
+
return GENERIC_CARRIER_FAILURE_MESSAGE;
|
|
30
|
+
}
|
|
4
31
|
/** Turn a registration rejection into an actionable message without exposing remote response text. */
|
|
5
32
|
function registrationFailureMessage(failure) {
|
|
6
33
|
switch (failure.code) {
|
|
@@ -14,6 +41,8 @@ function registrationFailureMessage(failure) {
|
|
|
14
41
|
return '验证码状态已失效,请重新获取验证码后再注册。';
|
|
15
42
|
case 'handle-unavailable':
|
|
16
43
|
return '该 Handle 刚刚已被注册。请返回身份入口,再按恢复流程重新获取验证码。';
|
|
44
|
+
case 'short-handle-invite-required':
|
|
45
|
+
return SHORT_HANDLE_INVITE_MESSAGE;
|
|
17
46
|
case 'conflict':
|
|
18
47
|
return '注册冲突:服务端可能已收到上次注册请求,或该手机号 / Handle 已绑定其他身份。请保留当前页面并再次提交;若仍失败,请勿清除本机身份数据,联系管理员并提供失败时间。';
|
|
19
48
|
case 'rate-limited':
|
|
@@ -27,12 +56,14 @@ function registrationFailureMessage(failure) {
|
|
|
27
56
|
case 'remote':
|
|
28
57
|
return 'AWiki 服务暂时无法完成注册,请稍后重试;若持续失败,请联系管理员并提供失败时间。';
|
|
29
58
|
default:
|
|
30
|
-
return
|
|
59
|
+
return 'AWiki 服务暂时无法完成注册,请稍后重试;若持续失败,请联系管理员并提供失败时间。';
|
|
31
60
|
}
|
|
32
61
|
}
|
|
33
62
|
/** Turn a verification-code request failure into a safe next action. */
|
|
34
63
|
function registrationOtpFailureMessage(failure) {
|
|
35
64
|
switch (failure.code) {
|
|
65
|
+
case 'short-handle-invite-required':
|
|
66
|
+
return SHORT_HANDLE_INVITE_MESSAGE;
|
|
36
67
|
case 'rate-limited':
|
|
37
68
|
return '验证码发送过于频繁,请等待限流解除后再重新获取。';
|
|
38
69
|
case 'invalid-request':
|
|
@@ -62,7 +93,7 @@ function recoveryPreparationFailureMessage(failure) {
|
|
|
62
93
|
case 'remote':
|
|
63
94
|
return 'AWiki 服务暂时无法验证恢复信息,请稍后重试。';
|
|
64
95
|
default:
|
|
65
|
-
return
|
|
96
|
+
return 'AWiki 服务暂时无法验证恢复信息,请稍后重试。';
|
|
66
97
|
}
|
|
67
98
|
}
|
|
68
99
|
function recoveryContinuationFailureMessage(failure, phase) {
|
|
@@ -84,7 +115,7 @@ function recoveryContinuationFailureMessage(failure, phase) {
|
|
|
84
115
|
case 'forbidden':
|
|
85
116
|
return 'AWiki 服务暂时未能完成恢复。可以重新检查结果,或返回入口稍后继续。';
|
|
86
117
|
default:
|
|
87
|
-
return
|
|
118
|
+
return '身份恢复暂未完成。请保留当前恢复操作并重新检查状态,不要重新获取验证码或创建新身份。';
|
|
88
119
|
}
|
|
89
120
|
}
|
|
90
121
|
function identityAccessInspectionFailureMessage(failure) {
|
|
@@ -96,9 +127,10 @@ function identityAccessInspectionFailureMessage(failure) {
|
|
|
96
127
|
case 'remote':
|
|
97
128
|
return 'AWiki 服务暂时无法确认该 Handle 的状态,请稍后重试。';
|
|
98
129
|
default:
|
|
99
|
-
return
|
|
130
|
+
return 'AWiki 服务暂时无法确认该 Handle 的状态,请稍后重试。';
|
|
100
131
|
}
|
|
101
132
|
}
|
|
133
|
+
const IDENTITY_ACCESS_FAILURE_MESSAGE = '暂时无法读取本机身份状态,请稍后重新检查。';
|
|
102
134
|
const INITIAL_VIEW = Object.freeze({
|
|
103
135
|
status: 'cold',
|
|
104
136
|
sessionStatus: 'unregistered',
|
|
@@ -126,6 +158,7 @@ const INITIAL_VIEW = Object.freeze({
|
|
|
126
158
|
accessError: null,
|
|
127
159
|
recoveryOperationId: null,
|
|
128
160
|
recoveryProgress: null,
|
|
161
|
+
recoveryOtpRetryAt: null,
|
|
129
162
|
});
|
|
130
163
|
/** Turn a closed Host summary failure into one actionable Chinese message. */
|
|
131
164
|
function summaryFailureMessage(failure) {
|
|
@@ -135,7 +168,7 @@ function summaryFailureMessage(failure) {
|
|
|
135
168
|
case 'summary-cancelled': return 'AI 总结已取消,请重新生成。';
|
|
136
169
|
case 'summary-invalid-output': return '模型没有返回有效的结构化摘要,请重新生成。';
|
|
137
170
|
case 'summary-failed': return '暂时无法生成 AI 总结,请检查模型连接后重试。';
|
|
138
|
-
default: return
|
|
171
|
+
default: return '暂时无法生成 AI 总结,请检查模型连接后重试。';
|
|
139
172
|
}
|
|
140
173
|
}
|
|
141
174
|
/** Turn closed mail failures into safe, actionable browser messages. */
|
|
@@ -163,7 +196,7 @@ function groupCreateFailureMessage(failure) {
|
|
|
163
196
|
case 'network': return '无法连接 AWiki 服务,请检查网络后重试。';
|
|
164
197
|
case 'conflict': return '群聊创建状态发生冲突,请刷新群聊列表后确认是否已经创建。';
|
|
165
198
|
case 'remote': return 'AWiki 服务暂时无法创建群聊,请稍后重试。';
|
|
166
|
-
default: return
|
|
199
|
+
default: return 'AWiki 服务暂时无法创建群聊,请稍后重试。';
|
|
167
200
|
}
|
|
168
201
|
}
|
|
169
202
|
/** Keep group authorization failures actionable without exposing raw service text. */
|
|
@@ -182,7 +215,7 @@ function groupReadFailureMessage(failure) {
|
|
|
182
215
|
case 'remote':
|
|
183
216
|
return 'AWiki 暂时无法读取这个群聊,请稍后重试。';
|
|
184
217
|
default:
|
|
185
|
-
return
|
|
218
|
+
return 'AWiki 暂时无法读取这个群聊,请稍后重试。';
|
|
186
219
|
}
|
|
187
220
|
}
|
|
188
221
|
function conversationPreferenceFailureMessage(_failure) {
|
|
@@ -217,21 +250,18 @@ async function callGroupRead(operation) {
|
|
|
217
250
|
}
|
|
218
251
|
}
|
|
219
252
|
/** Flatten the carrier and business result once for every controller caller. */
|
|
220
|
-
async function call(operation, failureMessage =
|
|
253
|
+
async function call(operation, failureMessage = genericFailureMessage, carrierFailureMessage = genericCarrierFailureMessage) {
|
|
221
254
|
try {
|
|
222
255
|
const carried = await operation();
|
|
223
256
|
if (!carried.ok)
|
|
224
|
-
return { ok: false, error: carrierFailureMessage(carried.error
|
|
257
|
+
return { ok: false, error: carrierFailureMessage(carried.error) };
|
|
225
258
|
if (!carried.value.ok) {
|
|
226
259
|
return { ok: false, error: failureMessage(carried.value.error) };
|
|
227
260
|
}
|
|
228
261
|
return { ok: true, value: carried.value.value };
|
|
229
262
|
}
|
|
230
|
-
catch
|
|
231
|
-
return {
|
|
232
|
-
ok: false,
|
|
233
|
-
error: error instanceof Error ? `AWiki 调用失败:${error.message}` : 'AWiki 调用失败',
|
|
234
|
-
};
|
|
263
|
+
catch {
|
|
264
|
+
return { ok: false, error: GENERIC_THROW_FAILURE_MESSAGE };
|
|
235
265
|
}
|
|
236
266
|
}
|
|
237
267
|
/** Flatten the carrier and the isolated Guest Integration business result. */
|
|
@@ -239,9 +269,22 @@ async function callIntegration(operation) {
|
|
|
239
269
|
try {
|
|
240
270
|
const carried = await operation();
|
|
241
271
|
if (!carried.ok)
|
|
242
|
-
return { ok: false, error:
|
|
243
|
-
if (!carried.value.ok)
|
|
244
|
-
|
|
272
|
+
return { ok: false, error: genericCarrierFailureMessage(carried.error) };
|
|
273
|
+
if (!carried.value.ok) {
|
|
274
|
+
const failure = carried.value.error;
|
|
275
|
+
const error = failure.code === 'not-found'
|
|
276
|
+
? '尚未创建 Integration。'
|
|
277
|
+
: failure.code === 'conflict'
|
|
278
|
+
? 'Integration 已在其他位置更新,请重新加载后再试。'
|
|
279
|
+
: failure.code === 'invalid-request'
|
|
280
|
+
? 'Integration 信息不完整或格式不正确。'
|
|
281
|
+
: failure.code === 'unauthorized' || failure.code === 'forbidden'
|
|
282
|
+
? '当前 AWiki 身份无权管理此 Integration。'
|
|
283
|
+
: failure.code === 'rate-limited'
|
|
284
|
+
? 'Integration 操作过于频繁,请稍后重试。'
|
|
285
|
+
: '临时消息服务暂时不可用,请稍后重试。';
|
|
286
|
+
return { ok: false, error, failureCode: failure.code };
|
|
287
|
+
}
|
|
245
288
|
return { ok: true, value: carried.value.value };
|
|
246
289
|
}
|
|
247
290
|
catch {
|
|
@@ -249,31 +292,28 @@ async function callIntegration(operation) {
|
|
|
249
292
|
}
|
|
250
293
|
}
|
|
251
294
|
/** Preserve only the stable business code for controller-level state transitions. */
|
|
252
|
-
async function callWithFailureCode(operation) {
|
|
295
|
+
async function callWithFailureCode(operation, failureMessage = genericFailureMessage) {
|
|
253
296
|
try {
|
|
254
297
|
const carried = await operation();
|
|
255
298
|
if (!carried.ok)
|
|
256
|
-
return { ok: false, error:
|
|
299
|
+
return { ok: false, error: genericCarrierFailureMessage(carried.error) };
|
|
257
300
|
if (!carried.value.ok) {
|
|
258
301
|
return {
|
|
259
302
|
ok: false,
|
|
260
|
-
error:
|
|
303
|
+
error: failureMessage(carried.value.error),
|
|
261
304
|
failureCode: carried.value.error.code,
|
|
262
305
|
};
|
|
263
306
|
}
|
|
264
307
|
return { ok: true, value: carried.value.value };
|
|
265
308
|
}
|
|
266
|
-
catch
|
|
267
|
-
return {
|
|
268
|
-
ok: false,
|
|
269
|
-
error: error instanceof Error ? `AWiki 调用失败:${error.message}` : 'AWiki 调用失败',
|
|
270
|
-
};
|
|
309
|
+
catch {
|
|
310
|
+
return { ok: false, error: GENERIC_THROW_FAILURE_MESSAGE };
|
|
271
311
|
}
|
|
272
312
|
}
|
|
273
|
-
function recoveryCarrierFailureMessage(
|
|
274
|
-
return
|
|
313
|
+
function recoveryCarrierFailureMessage(failure) {
|
|
314
|
+
return failure.code === 'invalid-response'
|
|
275
315
|
? '恢复信息已验证,但暂时无法读取恢复状态。请稍后重试。'
|
|
276
|
-
:
|
|
316
|
+
: '暂时无法连接 AWiki Host,无法完成身份恢复。请稍后重试。';
|
|
277
317
|
}
|
|
278
318
|
/** Append unique values while retaining existing references. */
|
|
279
319
|
function appendUnique(current, incoming, id) {
|
|
@@ -373,11 +413,15 @@ const GROUP_HISTORY_RETRY_DELAYS_MS = [250, 750, 1_500, 2_500];
|
|
|
373
413
|
/** Runtime-only decoded-byte budget that prevents repeat Host calls while browsing. */
|
|
374
414
|
const BROWSER_IMAGE_ATTACHMENT_CACHE_MAX_BYTES = 32 * 1024 * 1024;
|
|
375
415
|
const RECOVERY_OPERATION_STORAGE_KEY = 'awiki.handle-recovery.operation.v1';
|
|
416
|
+
const RECOVERY_OTP_RETRY_STORAGE_KEY = 'awiki.handle-recovery.retry-at.v1';
|
|
376
417
|
function recoveryOperationStorageKey(tenantId) {
|
|
377
418
|
return tenantId === undefined || tenantId === ''
|
|
378
419
|
? RECOVERY_OPERATION_STORAGE_KEY
|
|
379
420
|
: `${RECOVERY_OPERATION_STORAGE_KEY}.${encodeURIComponent(tenantId)}`;
|
|
380
421
|
}
|
|
422
|
+
function recoveryTenantKey(tenantId) {
|
|
423
|
+
return tenantId === undefined || tenantId === '' ? 'default' : tenantId;
|
|
424
|
+
}
|
|
381
425
|
function storeRecoveryOperation(operationId, tenantId) {
|
|
382
426
|
try {
|
|
383
427
|
const key = recoveryOperationStorageKey(tenantId);
|
|
@@ -390,6 +434,49 @@ function storeRecoveryOperation(operationId, tenantId) {
|
|
|
390
434
|
// Recovery remains durable in Core even when browser storage is unavailable.
|
|
391
435
|
}
|
|
392
436
|
}
|
|
437
|
+
function recoveryOtpRetryStorageKey(operationId, tenantId) {
|
|
438
|
+
return `${RECOVERY_OTP_RETRY_STORAGE_KEY}.${encodeURIComponent(recoveryTenantKey(tenantId))}.${encodeURIComponent(operationId)}`;
|
|
439
|
+
}
|
|
440
|
+
function recoveryOtpRetryAt(result) {
|
|
441
|
+
const advertised = Date.parse(result.retryAt);
|
|
442
|
+
const deadline = Number.isFinite(advertised)
|
|
443
|
+
? advertised
|
|
444
|
+
: Date.now() + Math.max(0, result.retryAfterSeconds) * 1_000;
|
|
445
|
+
return new Date(deadline).toISOString();
|
|
446
|
+
}
|
|
447
|
+
function storedRecoveryOtpRetryAt(operationId, tenantId) {
|
|
448
|
+
if (operationId === null)
|
|
449
|
+
return null;
|
|
450
|
+
try {
|
|
451
|
+
const key = recoveryOtpRetryStorageKey(operationId, tenantId);
|
|
452
|
+
const value = globalThis.localStorage?.getItem(key);
|
|
453
|
+
if (value === null || value === undefined)
|
|
454
|
+
return null;
|
|
455
|
+
const deadline = Date.parse(value);
|
|
456
|
+
if (!Number.isFinite(deadline) || deadline <= Date.now()) {
|
|
457
|
+
globalThis.localStorage?.removeItem(key);
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
return new Date(deadline).toISOString();
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function storeRecoveryOtpRetryAt(operationId, retryAt, tenantId) {
|
|
467
|
+
try {
|
|
468
|
+
globalThis.localStorage?.setItem(recoveryOtpRetryStorageKey(operationId, tenantId), retryAt);
|
|
469
|
+
}
|
|
470
|
+
catch { }
|
|
471
|
+
}
|
|
472
|
+
function clearRecoveryOtpRetryAt(operationId, tenantId) {
|
|
473
|
+
if (operationId === null)
|
|
474
|
+
return;
|
|
475
|
+
try {
|
|
476
|
+
globalThis.localStorage?.removeItem(recoveryOtpRetryStorageKey(operationId, tenantId));
|
|
477
|
+
}
|
|
478
|
+
catch { }
|
|
479
|
+
}
|
|
393
480
|
/** Wait between group-history readiness probes without retaining controller state. */
|
|
394
481
|
function delay(milliseconds) {
|
|
395
482
|
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
|
@@ -439,6 +526,8 @@ export class AwikiController {
|
|
|
439
526
|
drafts = new AwikiDraftStore();
|
|
440
527
|
view = INITIAL_VIEW;
|
|
441
528
|
listeners = new Set();
|
|
529
|
+
/** Runtime-only selected operations survive tenant round trips without reopening stale browser hints after reload. */
|
|
530
|
+
recoveryOperationsByTenant = new Map();
|
|
442
531
|
config = null;
|
|
443
532
|
conversationsCursor;
|
|
444
533
|
historyCursor;
|
|
@@ -521,6 +610,9 @@ export class AwikiController {
|
|
|
521
610
|
&& previous.sessionStatus === session.value.status && previous.identity?.did === identity?.did;
|
|
522
611
|
this.drafts.setScope(config.value.tenantId ?? 'default', identity?.did ?? session.value.status);
|
|
523
612
|
this.activatePresentationCache(identity);
|
|
613
|
+
const selectedRecovery = previousTenant === config.value.tenantId
|
|
614
|
+
? previous.recoveryOperationId
|
|
615
|
+
: this.recoveryOperationsByTenant.get(recoveryTenantKey(config.value.tenantId)) ?? null;
|
|
524
616
|
this.publish({
|
|
525
617
|
...(sameOwner ? this.view : INITIAL_VIEW),
|
|
526
618
|
status: 'ready',
|
|
@@ -529,7 +621,8 @@ export class AwikiController {
|
|
|
529
621
|
error: null,
|
|
530
622
|
attachmentMaxBytes: config.value.attachmentMaxBytes,
|
|
531
623
|
handleRecoveryPhoneEnabled: config.value.handleRecoveryPhoneEnabled,
|
|
532
|
-
recoveryOperationId:
|
|
624
|
+
recoveryOperationId: selectedRecovery,
|
|
625
|
+
recoveryOtpRetryAt: storedRecoveryOtpRetryAt(selectedRecovery, config.value.tenantId),
|
|
533
626
|
});
|
|
534
627
|
await this.refreshIdentityAccess();
|
|
535
628
|
if (!this.current(generation))
|
|
@@ -684,7 +777,10 @@ export class AwikiController {
|
|
|
684
777
|
* @returns challenge retry metadata or one display-safe failure.
|
|
685
778
|
*/
|
|
686
779
|
async sendRegistrationOtp(request) {
|
|
687
|
-
|
|
780
|
+
const result = await this.withPending('发送验证码', () => callWithFailureCode(() => this.remote.sendRegistrationOtp(request), registrationOtpFailureMessage));
|
|
781
|
+
return !result.ok && result.failureCode !== 'short-handle-invite-required'
|
|
782
|
+
? { ok: false, error: result.error }
|
|
783
|
+
: result;
|
|
688
784
|
}
|
|
689
785
|
/** Discover Core-owned work without selecting an account or activating a session. */
|
|
690
786
|
refreshIdentityAccess() {
|
|
@@ -705,28 +801,36 @@ export class AwikiController {
|
|
|
705
801
|
if (!this.recoveryCurrent(generation, revision))
|
|
706
802
|
return this.recoveryLeft();
|
|
707
803
|
if (!result.ok) {
|
|
708
|
-
this.publish({ ...this.view, accessLoading: false, accessError:
|
|
709
|
-
return
|
|
804
|
+
this.publish({ ...this.view, accessLoading: false, accessError: IDENTITY_ACCESS_FAILURE_MESSAGE });
|
|
805
|
+
return { ok: false, error: IDENTITY_ACCESS_FAILURE_MESSAGE };
|
|
710
806
|
}
|
|
711
807
|
const candidates = result.value.recoveries;
|
|
712
808
|
const selected = this.view.recoveryOperationId;
|
|
713
|
-
this.publish({ ...this.view, identityAccess: result.value
|
|
809
|
+
this.publish({ ...this.view, identityAccess: result.value, recoveryOperationId: selected,
|
|
810
|
+
recoveryProgress: selected === this.view.recoveryOperationId ? this.view.recoveryProgress : null,
|
|
811
|
+
recoveryOtpRetryAt: storedRecoveryOtpRetryAt(selected, this.config?.tenantId) });
|
|
714
812
|
if (selected !== null) {
|
|
715
813
|
const status = await callWithFailureCode(() => this.remote.getRecoveryStatus({ operationId: selected }));
|
|
716
814
|
if (!this.recoveryCurrent(generation, revision))
|
|
717
815
|
return this.recoveryLeft();
|
|
718
816
|
if (!status.ok) {
|
|
719
817
|
if (status.failureCode === 'not-found' && candidates.length === 0) {
|
|
720
|
-
|
|
721
|
-
this.
|
|
818
|
+
clearRecoveryOtpRetryAt(selected, this.config?.tenantId);
|
|
819
|
+
this.rememberRecoveryOperation(null, this.config?.tenantId);
|
|
820
|
+
this.publish({ ...this.view, recoveryOperationId: null, recoveryProgress: null, recoveryOtpRetryAt: null });
|
|
722
821
|
}
|
|
723
822
|
else {
|
|
724
|
-
this.publish({ ...this.view, accessLoading: false, accessError:
|
|
725
|
-
return { ok: false, error:
|
|
823
|
+
this.publish({ ...this.view, accessLoading: false, accessError: IDENTITY_ACCESS_FAILURE_MESSAGE });
|
|
824
|
+
return { ok: false, error: IDENTITY_ACCESS_FAILURE_MESSAGE };
|
|
726
825
|
}
|
|
727
826
|
}
|
|
728
827
|
else {
|
|
729
|
-
|
|
828
|
+
const complete = status.value.phase === 'applied';
|
|
829
|
+
if (complete)
|
|
830
|
+
clearRecoveryOtpRetryAt(selected, this.config?.tenantId);
|
|
831
|
+
this.publish({ ...this.view, recoveryProgress: status.value,
|
|
832
|
+
recoveryOtpRetryAt: complete ? null : storedRecoveryOtpRetryAt(selected, this.config?.tenantId),
|
|
833
|
+
identityAccess: result.value });
|
|
730
834
|
}
|
|
731
835
|
}
|
|
732
836
|
this.publish({ ...this.view, accessLoading: false, accessError: null });
|
|
@@ -740,7 +844,9 @@ export class AwikiController {
|
|
|
740
844
|
this.leaveRecovery();
|
|
741
845
|
else
|
|
742
846
|
this.recoveryRevision++;
|
|
743
|
-
this.
|
|
847
|
+
this.rememberRecoveryOperation(operationId, this.config?.tenantId);
|
|
848
|
+
this.publish({ ...this.view, recoveryOperationId: operationId, recoveryProgress: null,
|
|
849
|
+
recoveryOtpRetryAt: storedRecoveryOtpRetryAt(operationId, this.config?.tenantId) });
|
|
744
850
|
const result = await this.refreshRecoveryStatus();
|
|
745
851
|
return result.ok ? { ok: true, value: undefined } : result;
|
|
746
852
|
}
|
|
@@ -752,6 +858,16 @@ export class AwikiController {
|
|
|
752
858
|
recoveryLeft() {
|
|
753
859
|
return { ok: false, error: '已离开此恢复流程;进度保留在本机。' };
|
|
754
860
|
}
|
|
861
|
+
rememberRecoveryOperation(operationId, tenantId) {
|
|
862
|
+
const key = recoveryTenantKey(tenantId);
|
|
863
|
+
if (operationId === null) {
|
|
864
|
+
this.recoveryOperationsByTenant.delete(key);
|
|
865
|
+
storeRecoveryOperation(null, tenantId);
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
this.recoveryOperationsByTenant.set(key, operationId);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
755
871
|
/** Navigation only. Never cancel or delete the Core-owned operation. */
|
|
756
872
|
leaveRecovery() {
|
|
757
873
|
this.recoveryRevision++;
|
|
@@ -761,7 +877,7 @@ export class AwikiController {
|
|
|
761
877
|
this.pendingRequest = undefined;
|
|
762
878
|
this.pendingRecovery = false;
|
|
763
879
|
this.drafts.clearFlow();
|
|
764
|
-
|
|
880
|
+
this.rememberRecoveryOperation(null, this.config?.tenantId);
|
|
765
881
|
this.publish({ ...this.view, recoveryOperationId: null, recoveryProgress: null, accessError: null,
|
|
766
882
|
accessLoading: false, pending: wasPending ? null : this.view.pending, error: null });
|
|
767
883
|
}
|
|
@@ -792,10 +908,16 @@ export class AwikiController {
|
|
|
792
908
|
*/
|
|
793
909
|
async registerIdentity(request) {
|
|
794
910
|
const generation = this.generation;
|
|
795
|
-
const
|
|
911
|
+
const classified = await this.withPending('注册身份', () => callWithFailureCode(() => this.remote.registerIdentity(request), registrationFailureMessage));
|
|
912
|
+
const result = !classified.ok && classified.failureCode !== 'short-handle-invite-required'
|
|
913
|
+
? { ok: false, error: classified.error }
|
|
914
|
+
: classified;
|
|
796
915
|
if (!result.ok) {
|
|
797
916
|
if (this.current(generation))
|
|
798
917
|
await this.refreshIdentityAccess();
|
|
918
|
+
if (this.current(generation) && result.failureCode === 'short-handle-invite-required') {
|
|
919
|
+
this.publish({ ...this.view, error: null });
|
|
920
|
+
}
|
|
799
921
|
return result;
|
|
800
922
|
}
|
|
801
923
|
if (!this.current(generation))
|
|
@@ -839,6 +961,68 @@ export class AwikiController {
|
|
|
839
961
|
return result.ok ? { ok: true, value: undefined } : result;
|
|
840
962
|
});
|
|
841
963
|
}
|
|
964
|
+
/** Cancel one exact Join choice and send Recovery OTP only while its original browser scope remains current. */
|
|
965
|
+
async beginRecoveryFromDeviceJoin(request) {
|
|
966
|
+
const generation = this.generation;
|
|
967
|
+
const tenantId = this.config?.tenantId;
|
|
968
|
+
const flowScope = this.drafts.getScope('recovery:retryDeadline');
|
|
969
|
+
const flowEpoch = this.drafts.epoch(flowScope);
|
|
970
|
+
const changed = () => ({
|
|
971
|
+
ok: false,
|
|
972
|
+
error: '恢复能力或身份环境已改变,请重新检查后再选择。',
|
|
973
|
+
});
|
|
974
|
+
const scopeCurrent = () => this.current(generation)
|
|
975
|
+
&& this.config?.tenantId === tenantId
|
|
976
|
+
&& this.drafts.getScope('recovery:retryDeadline') === flowScope
|
|
977
|
+
&& this.drafts.epoch(flowScope) === flowEpoch;
|
|
978
|
+
if (!scopeCurrent())
|
|
979
|
+
return changed();
|
|
980
|
+
const latest = await call(() => this.remote.getConfig());
|
|
981
|
+
if (!scopeCurrent())
|
|
982
|
+
return changed();
|
|
983
|
+
if (!latest.ok)
|
|
984
|
+
return { ok: false, error: '暂时无法确认服务器的恢复能力,请稍后重试。' };
|
|
985
|
+
if (latest.value.tenantId !== tenantId)
|
|
986
|
+
return changed();
|
|
987
|
+
this.config = latest.value;
|
|
988
|
+
this.publish({ ...this.view, handleRecoveryPhoneEnabled: latest.value.handleRecoveryPhoneEnabled });
|
|
989
|
+
if (latest.value.tenantOnline === false) {
|
|
990
|
+
return { ok: false, error: '暂时无法确认服务器的恢复能力,请稍后重试。' };
|
|
991
|
+
}
|
|
992
|
+
if (!latest.value.handleRecoveryPhoneEnabled) {
|
|
993
|
+
return { ok: false, error: '当前服务器未启用手机号恢复,请加入新设备或联系管理员。' };
|
|
994
|
+
}
|
|
995
|
+
const current = () => scopeCurrent() && this.config?.handleRecoveryPhoneEnabled === true;
|
|
996
|
+
const result = await this.withPending('开始恢复身份', async () => {
|
|
997
|
+
const discarded = await call(() => this.remote.cancelDeviceJoin());
|
|
998
|
+
if (!discarded.ok)
|
|
999
|
+
return discarded;
|
|
1000
|
+
if (!current())
|
|
1001
|
+
return changed();
|
|
1002
|
+
this.publish({ ...this.view, identityAccess: { choice: null, joining: false, recoveries: [] } });
|
|
1003
|
+
if (request.phone === '')
|
|
1004
|
+
return { ok: true, value: null };
|
|
1005
|
+
return call(() => this.remote.sendRecoveryOtp(request), registrationOtpFailureMessage);
|
|
1006
|
+
}, { publishFailure: false, recovery: true });
|
|
1007
|
+
if (!result.ok) {
|
|
1008
|
+
if (current())
|
|
1009
|
+
await this.continueRecoveryForHandle(request.fullHandle);
|
|
1010
|
+
return result;
|
|
1011
|
+
}
|
|
1012
|
+
if (result.value === null)
|
|
1013
|
+
return result;
|
|
1014
|
+
this.recordRecoveryOtp(result.value, tenantId);
|
|
1015
|
+
const sameFlow = this.config?.tenantId === tenantId
|
|
1016
|
+
&& this.drafts.getScope('recovery:retryDeadline') === flowScope
|
|
1017
|
+
&& this.drafts.epoch(flowScope) === flowEpoch;
|
|
1018
|
+
if (!sameFlow)
|
|
1019
|
+
return result;
|
|
1020
|
+
const retryAt = recoveryOtpRetryAt(result.value);
|
|
1021
|
+
this.publish({ ...this.view, recoveryOperationId: result.value.operationId, recoveryProgress: null,
|
|
1022
|
+
recoveryOtpRetryAt: retryAt });
|
|
1023
|
+
await this.refreshRecoveryStatus();
|
|
1024
|
+
return result;
|
|
1025
|
+
}
|
|
842
1026
|
retireDeviceIdentityForRejoin() {
|
|
843
1027
|
return this.withPending('准备重新加入设备', async () => {
|
|
844
1028
|
const result = await call(() => this.remote.retireDeviceIdentityForRejoin());
|
|
@@ -912,17 +1096,29 @@ export class AwikiController {
|
|
|
912
1096
|
async sendRecoveryOtp(request) {
|
|
913
1097
|
const generation = this.generation;
|
|
914
1098
|
const revision = this.recoveryRevision;
|
|
1099
|
+
const tenantId = this.config?.tenantId;
|
|
1100
|
+
const flowScope = this.drafts.getScope('recovery:retryDeadline');
|
|
915
1101
|
const result = await this.withPending('发送恢复验证码', () => call(() => this.remote.sendRecoveryOtp(request), registrationOtpFailureMessage), { publishFailure: false, recovery: true });
|
|
916
|
-
if (!this.recoveryCurrent(generation, revision))
|
|
917
|
-
return this.recoveryLeft();
|
|
918
1102
|
if (!result.ok) {
|
|
1103
|
+
if (!this.recoveryCurrent(generation, revision))
|
|
1104
|
+
return this.recoveryLeft();
|
|
919
1105
|
await this.continueRecoveryForHandle(request.fullHandle);
|
|
920
1106
|
return result;
|
|
921
1107
|
}
|
|
922
|
-
this.
|
|
1108
|
+
this.recordRecoveryOtp(result.value, tenantId);
|
|
1109
|
+
if (!this.recoveryCurrent(generation, revision)
|
|
1110
|
+
|| this.config?.tenantId !== tenantId
|
|
1111
|
+
|| this.drafts.getScope('recovery:retryDeadline') !== flowScope)
|
|
1112
|
+
return result;
|
|
1113
|
+
this.publish({ ...this.view, recoveryOperationId: result.value.operationId, recoveryProgress: null,
|
|
1114
|
+
recoveryOtpRetryAt: recoveryOtpRetryAt(result.value) });
|
|
923
1115
|
await this.refreshRecoveryStatus();
|
|
924
1116
|
return this.recoveryCurrent(generation, revision) ? result : this.recoveryLeft();
|
|
925
1117
|
}
|
|
1118
|
+
recordRecoveryOtp(result, tenantId) {
|
|
1119
|
+
this.rememberRecoveryOperation(result.operationId, tenantId);
|
|
1120
|
+
storeRecoveryOtpRetryAt(result.operationId, recoveryOtpRetryAt(result), tenantId);
|
|
1121
|
+
}
|
|
926
1122
|
/** Publish results only into the exact selected flow; Core work survives navigation. */
|
|
927
1123
|
async runRecovery(label, operation, enterWhenApplied = false) {
|
|
928
1124
|
const generation = this.generation;
|
|
@@ -935,14 +1131,16 @@ export class AwikiController {
|
|
|
935
1131
|
return this.recoveryLeft();
|
|
936
1132
|
if (!result.ok) {
|
|
937
1133
|
// Reconcile authority after an ambiguous failure, never infer whether Commit occurred.
|
|
938
|
-
const status = await call(() => this.remote.getRecoveryStatus({ operationId }));
|
|
1134
|
+
const status = await call(() => this.remote.getRecoveryStatus({ operationId }), undefined, recoveryCarrierFailureMessage);
|
|
939
1135
|
if (!this.recoveryCurrent(generation, revision))
|
|
940
1136
|
return this.recoveryLeft();
|
|
941
1137
|
this.publish({ ...this.view, recoveryProgress: status.ok ? status.value : this.view.recoveryProgress,
|
|
942
|
-
accessError: status.ok ? null :
|
|
1138
|
+
accessError: status.ok ? null : IDENTITY_ACCESS_FAILURE_MESSAGE });
|
|
943
1139
|
return result;
|
|
944
1140
|
}
|
|
945
1141
|
this.publish({ ...this.view, recoveryProgress: result.value, accessError: null });
|
|
1142
|
+
if (result.value.phase === 'applied')
|
|
1143
|
+
clearRecoveryOtpRetryAt(operationId, this.config?.tenantId);
|
|
946
1144
|
if (enterWhenApplied && result.value.phase === 'applied')
|
|
947
1145
|
return this.enterRecoveredSession();
|
|
948
1146
|
return result;
|
|
@@ -960,7 +1158,25 @@ export class AwikiController {
|
|
|
960
1158
|
}
|
|
961
1159
|
/** Read-only even for applied operations. Entering the identity is a separate action. */
|
|
962
1160
|
async refreshRecoveryStatus() {
|
|
963
|
-
|
|
1161
|
+
const generation = this.generation;
|
|
1162
|
+
const revision = this.recoveryRevision;
|
|
1163
|
+
const operationId = this.view.recoveryOperationId;
|
|
1164
|
+
if (operationId === null)
|
|
1165
|
+
return { ok: false, error: '没有可查询的身份恢复操作' };
|
|
1166
|
+
const result = await this.withPending('刷新恢复状态', async () => {
|
|
1167
|
+
const status = await call(() => this.remote.getRecoveryStatus({ operationId }), undefined, recoveryCarrierFailureMessage);
|
|
1168
|
+
return status.ok ? status : { ok: false, error: IDENTITY_ACCESS_FAILURE_MESSAGE };
|
|
1169
|
+
}, { publishFailure: false, recovery: true });
|
|
1170
|
+
if (!this.recoveryCurrent(generation, revision))
|
|
1171
|
+
return this.recoveryLeft();
|
|
1172
|
+
if (!result.ok) {
|
|
1173
|
+
this.publish({ ...this.view, accessError: IDENTITY_ACCESS_FAILURE_MESSAGE });
|
|
1174
|
+
return result;
|
|
1175
|
+
}
|
|
1176
|
+
this.publish({ ...this.view, recoveryProgress: result.value, accessError: null });
|
|
1177
|
+
if (result.value.phase === 'applied')
|
|
1178
|
+
clearRecoveryOtpRetryAt(operationId, this.config?.tenantId);
|
|
1179
|
+
return result;
|
|
964
1180
|
}
|
|
965
1181
|
async resumeRecovery() {
|
|
966
1182
|
const progress = this.view.recoveryProgress;
|
|
@@ -980,6 +1196,7 @@ export class AwikiController {
|
|
|
980
1196
|
return this.recoveryLeft();
|
|
981
1197
|
if (!result.ok)
|
|
982
1198
|
return result;
|
|
1199
|
+
clearRecoveryOtpRetryAt(progress.operationId, this.config?.tenantId);
|
|
983
1200
|
this.leaveRecovery();
|
|
984
1201
|
const opened = await this.open();
|
|
985
1202
|
return opened.ok ? result : opened;
|
|
@@ -999,10 +1216,23 @@ export class AwikiController {
|
|
|
999
1216
|
return this.recoveryLeft();
|
|
1000
1217
|
if (!result.ok)
|
|
1001
1218
|
return result;
|
|
1219
|
+
clearRecoveryOtpRetryAt(operationId, this.config?.tenantId);
|
|
1002
1220
|
this.leaveRecovery();
|
|
1003
1221
|
void this.refreshIdentityAccess();
|
|
1004
1222
|
return { ok: true, value: undefined };
|
|
1005
1223
|
}
|
|
1224
|
+
/** Return cached browser-safe runtime policy, or load it once on demand. */
|
|
1225
|
+
async getConfig() {
|
|
1226
|
+
if (this.config !== null)
|
|
1227
|
+
return { ok: true, value: this.config };
|
|
1228
|
+
const generation = this.generation;
|
|
1229
|
+
const result = await call(() => this.remote.getConfig(), mailFailureMessage);
|
|
1230
|
+
if (!this.current(generation))
|
|
1231
|
+
return { ok: false, error: 'AWiki 会话已切换' };
|
|
1232
|
+
if (result.ok)
|
|
1233
|
+
this.config = result.value;
|
|
1234
|
+
return result;
|
|
1235
|
+
}
|
|
1006
1236
|
/** Read the active deployment identity's public mailbox state. */
|
|
1007
1237
|
getMailAccount() {
|
|
1008
1238
|
return call(() => this.remote.getMailAccount(), mailFailureMessage);
|
|
@@ -1023,6 +1253,16 @@ export class AwikiController {
|
|
|
1023
1253
|
sendMail(request) {
|
|
1024
1254
|
return call(() => this.remote.sendMail(request), mailFailureMessage);
|
|
1025
1255
|
}
|
|
1256
|
+
/** Download one explicitly selected mail attachment without retaining its bytes. */
|
|
1257
|
+
async downloadMailAttachment(request) {
|
|
1258
|
+
if (this.disposed)
|
|
1259
|
+
return { ok: false, error: 'AWiki 插件已卸载' };
|
|
1260
|
+
const generation = this.generation;
|
|
1261
|
+
const result = await call(() => this.remote.downloadMailAttachment(request), mailFailureMessage);
|
|
1262
|
+
if (!this.current(generation))
|
|
1263
|
+
return { ok: false, error: 'AWiki 已关闭' };
|
|
1264
|
+
return result;
|
|
1265
|
+
}
|
|
1026
1266
|
/**
|
|
1027
1267
|
* Load another page of the conversation roster.
|
|
1028
1268
|
* @returns successful pagination or one display-safe failure.
|
|
@@ -1132,10 +1372,15 @@ export class AwikiController {
|
|
|
1132
1372
|
return this.selectConversation(existing.id);
|
|
1133
1373
|
}
|
|
1134
1374
|
const generation = this.generation;
|
|
1135
|
-
const resolved = await this.withPending('查找用户',
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1375
|
+
const resolved = await this.withPending('查找用户', async () => {
|
|
1376
|
+
const result = await callWithFailureCode(() => this.remote.resolvePeer({ peer }));
|
|
1377
|
+
if (!result.ok) {
|
|
1378
|
+
return { ok: false, error: result.failureCode === 'not-found' ? '该 Handle 不存在' : result.error };
|
|
1379
|
+
}
|
|
1380
|
+
return result;
|
|
1381
|
+
});
|
|
1382
|
+
if (!resolved.ok)
|
|
1383
|
+
return resolved;
|
|
1139
1384
|
if (!this.current(generation))
|
|
1140
1385
|
return { ok: true, value: undefined };
|
|
1141
1386
|
if (resolved.value.did === identity.did)
|
|
@@ -1811,18 +2056,22 @@ export class AwikiController {
|
|
|
1811
2056
|
}
|
|
1812
2057
|
catch { }
|
|
1813
2058
|
}
|
|
1814
|
-
|
|
2059
|
+
clearRecoveryOtpRetryAt(this.view.recoveryOperationId, tenantId);
|
|
2060
|
+
this.rememberRecoveryOperation(null, tenantId);
|
|
1815
2061
|
if (!this.current(generation))
|
|
1816
2062
|
return result;
|
|
1817
2063
|
this.drafts.clearScope();
|
|
1818
2064
|
this.close();
|
|
1819
|
-
|
|
2065
|
+
// Preserve tenantId for the confirm-time getConfig scope check without reopening.
|
|
1820
2066
|
this.conversationsCursor = undefined;
|
|
1821
2067
|
this.historyCursor = undefined;
|
|
1822
2068
|
this.unreadAtOpen.clear();
|
|
1823
2069
|
this.summaryBaselines.clear();
|
|
1824
2070
|
this.clearPresentationCache();
|
|
1825
|
-
this.publish({ ...INITIAL_VIEW, status: 'ready', accessLoading: false,
|
|
2071
|
+
this.publish({ ...INITIAL_VIEW, status: 'ready', accessLoading: false,
|
|
2072
|
+
attachmentMaxBytes: this.config?.attachmentMaxBytes ?? INITIAL_VIEW.attachmentMaxBytes,
|
|
2073
|
+
handleRecoveryPhoneEnabled: this.config?.handleRecoveryPhoneEnabled ?? false,
|
|
2074
|
+
identityAccess: { choice: null, joining: false, recoveries: [] } });
|
|
1826
2075
|
return result;
|
|
1827
2076
|
}
|
|
1828
2077
|
/** Read the Integration without coupling Guest Gateway health to the main AWiki view. */
|