@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.
Files changed (142) hide show
  1. package/README.md +131 -22
  2. package/README.zh.md +110 -20
  3. package/THIRD_PARTY_NOTICES.md +8 -1
  4. package/cordis.patch.yml +5 -0
  5. package/lib/client.js +12534 -2647
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +2431 -92
  8. package/lib/model-proxy-contract.js +96 -0
  9. package/lib/provider.js +4 -1
  10. package/lib/sdk-adapter-DLhLrLI6.mjs +886 -0
  11. package/lib/typert.host.js +1269 -534
  12. package/lib/typert.remote-client.d.ts +47 -1
  13. package/lib/typert.remote-client.d.ts.map +1 -1
  14. package/lib/typert.remote-client.js +1254 -141
  15. package/lib/types/attachment-cache.d.ts +28 -0
  16. package/lib/types/attachment-cache.d.ts.map +1 -0
  17. package/lib/types/attachment-cache.js +194 -0
  18. package/lib/types/attachment-cache.js.map +1 -0
  19. package/lib/types/client/AwikiGroupDetails.d.ts +16 -0
  20. package/lib/types/client/AwikiGroupDetails.d.ts.map +1 -0
  21. package/lib/types/client/AwikiGroupDetails.js +108 -0
  22. package/lib/types/client/AwikiGroupDetails.js.map +1 -0
  23. package/lib/types/client/AwikiIdentityAccess.d.ts +21 -0
  24. package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -0
  25. package/lib/types/client/AwikiIdentityAccess.js +148 -0
  26. package/lib/types/client/AwikiIdentityAccess.js.map +1 -0
  27. package/lib/types/client/AwikiIdentityPage.d.ts +11 -0
  28. package/lib/types/client/AwikiIdentityPage.d.ts.map +1 -0
  29. package/lib/types/client/AwikiIdentityPage.js +8 -0
  30. package/lib/types/client/AwikiIdentityPage.js.map +1 -0
  31. package/lib/types/client/AwikiMail.d.ts +15 -0
  32. package/lib/types/client/AwikiMail.d.ts.map +1 -0
  33. package/lib/types/client/AwikiMail.js +380 -0
  34. package/lib/types/client/AwikiMail.js.map +1 -0
  35. package/lib/types/client/AwikiOnboarding.d.ts +22 -0
  36. package/lib/types/client/AwikiOnboarding.d.ts.map +1 -0
  37. package/lib/types/client/AwikiOnboarding.js +104 -0
  38. package/lib/types/client/AwikiOnboarding.js.map +1 -0
  39. package/lib/types/client/AwikiOverlay.d.ts +10 -0
  40. package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
  41. package/lib/types/client/AwikiOverlay.js +370 -127
  42. package/lib/types/client/AwikiOverlay.js.map +1 -1
  43. package/lib/types/client/AwikiProfileCard.d.ts +9 -0
  44. package/lib/types/client/AwikiProfileCard.d.ts.map +1 -0
  45. package/lib/types/client/AwikiProfileCard.js +90 -0
  46. package/lib/types/client/AwikiProfileCard.js.map +1 -0
  47. package/lib/types/client/AwikiRecoveryForm.d.ts +24 -0
  48. package/lib/types/client/AwikiRecoveryForm.d.ts.map +1 -0
  49. package/lib/types/client/AwikiRecoveryForm.js +148 -0
  50. package/lib/types/client/AwikiRecoveryForm.js.map +1 -0
  51. package/lib/types/client/AwikiSettingsSection.d.ts +18 -4
  52. package/lib/types/client/AwikiSettingsSection.d.ts.map +1 -1
  53. package/lib/types/client/AwikiSettingsSection.js +277 -23
  54. package/lib/types/client/AwikiSettingsSection.js.map +1 -1
  55. package/lib/types/client/MentionText.d.ts +7 -0
  56. package/lib/types/client/MentionText.d.ts.map +1 -0
  57. package/lib/types/client/MentionText.js +10 -0
  58. package/lib/types/client/MentionText.js.map +1 -0
  59. package/lib/types/client/RechargeComingSoonDialog.d.ts +11 -0
  60. package/lib/types/client/RechargeComingSoonDialog.d.ts.map +1 -0
  61. package/lib/types/client/RechargeComingSoonDialog.js +8 -0
  62. package/lib/types/client/RechargeComingSoonDialog.js.map +1 -0
  63. package/lib/types/client/controller.d.ts +174 -4
  64. package/lib/types/client/controller.d.ts.map +1 -1
  65. package/lib/types/client/controller.js +975 -79
  66. package/lib/types/client/controller.js.map +1 -1
  67. package/lib/types/client/image-cache.d.ts +20 -0
  68. package/lib/types/client/image-cache.d.ts.map +1 -0
  69. package/lib/types/client/image-cache.js +177 -0
  70. package/lib/types/client/image-cache.js.map +1 -0
  71. package/lib/types/client/index.d.ts +2 -0
  72. package/lib/types/client/index.d.ts.map +1 -1
  73. package/lib/types/client/index.js +102 -42
  74. package/lib/types/client/index.js.map +1 -1
  75. package/lib/types/client/mail-list-cache.d.ts +15 -0
  76. package/lib/types/client/mail-list-cache.d.ts.map +1 -0
  77. package/lib/types/client/mail-list-cache.js +212 -0
  78. package/lib/types/client/mail-list-cache.js.map +1 -0
  79. package/lib/types/client/mentions.d.ts +51 -0
  80. package/lib/types/client/mentions.d.ts.map +1 -0
  81. package/lib/types/client/mentions.js +165 -0
  82. package/lib/types/client/mentions.js.map +1 -0
  83. package/lib/types/client/model-availability-controller.d.ts +27 -0
  84. package/lib/types/client/model-availability-controller.d.ts.map +1 -0
  85. package/lib/types/client/model-availability-controller.js +111 -0
  86. package/lib/types/client/model-availability-controller.js.map +1 -0
  87. package/lib/types/client/model-proxy-controller.d.ts +45 -0
  88. package/lib/types/client/model-proxy-controller.d.ts.map +1 -0
  89. package/lib/types/client/model-proxy-controller.js +306 -0
  90. package/lib/types/client/model-proxy-controller.js.map +1 -0
  91. package/lib/types/client/recharge-availability.d.ts +6 -0
  92. package/lib/types/client/recharge-availability.d.ts.map +1 -0
  93. package/lib/types/client/recharge-availability.js +6 -0
  94. package/lib/types/client/recharge-availability.js.map +1 -0
  95. package/lib/types/client/settings-locales.d.ts +1 -1
  96. package/lib/types/client/settings-locales.d.ts.map +1 -1
  97. package/lib/types/client/settings-locales.js +186 -8
  98. package/lib/types/client/settings-locales.js.map +1 -1
  99. package/lib/types/client/slots.d.ts +34 -2
  100. package/lib/types/client/slots.d.ts.map +1 -1
  101. package/lib/types/index.d.ts +95 -7
  102. package/lib/types/index.d.ts.map +1 -1
  103. package/lib/types/index.js +968 -37
  104. package/lib/types/index.js.map +1 -1
  105. package/lib/types/listener-state.d.ts +23 -0
  106. package/lib/types/listener-state.d.ts.map +1 -0
  107. package/lib/types/listener-state.js +124 -0
  108. package/lib/types/listener-state.js.map +1 -0
  109. package/lib/types/listener.d.ts +96 -0
  110. package/lib/types/listener.d.ts.map +1 -0
  111. package/lib/types/listener.js +575 -0
  112. package/lib/types/listener.js.map +1 -0
  113. package/lib/types/mail.d.ts +8 -0
  114. package/lib/types/mail.d.ts.map +1 -0
  115. package/lib/types/mail.js +75 -0
  116. package/lib/types/mail.js.map +1 -0
  117. package/lib/types/model-proxy-contract.d.ts +67 -0
  118. package/lib/types/model-proxy-contract.d.ts.map +1 -0
  119. package/lib/types/model-proxy-contract.js +135 -0
  120. package/lib/types/model-proxy-contract.js.map +1 -0
  121. package/lib/types/provider-api.d.ts +106 -2
  122. package/lib/types/provider-api.d.ts.map +1 -1
  123. package/lib/types/provider.d.ts.map +1 -1
  124. package/lib/types/provider.js +3 -0
  125. package/lib/types/provider.js.map +1 -1
  126. package/lib/types/sdk-adapter.d.ts +43 -2
  127. package/lib/types/sdk-adapter.d.ts.map +1 -1
  128. package/lib/types/sdk-adapter.js +622 -24
  129. package/lib/types/sdk-adapter.js.map +1 -1
  130. package/lib/types/sent-mail-store.d.ts +22 -0
  131. package/lib/types/sent-mail-store.d.ts.map +1 -0
  132. package/lib/types/sent-mail-store.js +246 -0
  133. package/lib/types/sent-mail-store.js.map +1 -0
  134. package/lib/types/tools.d.ts +10 -0
  135. package/lib/types/tools.d.ts.map +1 -1
  136. package/lib/types/tools.js +98 -0
  137. package/lib/types/tools.js.map +1 -1
  138. package/lib/types/types.d.ts +254 -2
  139. package/lib/types/types.d.ts.map +1 -1
  140. package/lib/types/types.js.map +1 -1
  141. package/package.json +31 -17
  142. package/lib/sdk-adapter-DK6RqV-c.mjs +0 -355
@@ -14,6 +14,8 @@ const RUST_FAILURE_CODES = {
14
14
  handle_unavailable: 'handle-unavailable',
15
15
  not_found: 'not-found',
16
16
  permission_denied: 'forbidden',
17
+ group_not_member: 'group-membership-required',
18
+ group_identity_stale: 'group-identity-stale',
17
19
  auth_revoked: 'forbidden',
18
20
  conflict: 'conflict',
19
21
  join_required: 'handle-unavailable',
@@ -37,7 +39,7 @@ export class AwikiSdkError extends Error {
37
39
  function fail(code = 'remote') {
38
40
  throw new AwikiSdkError(code);
39
41
  }
40
- function mapError(error) {
42
+ function mapError(error, ambiguousSend = false) {
41
43
  if (error instanceof AwikiSdkError)
42
44
  throw error;
43
45
  let code = 'remote';
@@ -45,7 +47,9 @@ function mapError(error) {
45
47
  if (typeof error === 'object' && error !== null) {
46
48
  const value = error;
47
49
  if (value.name === 'ImCoreNodeError' && typeof value.code === 'string') {
48
- code = RUST_FAILURE_CODES[value.code] ?? 'remote';
50
+ code = ambiguousSend && (value.code === 'timeout' || value.code === 'transport_unavailable')
51
+ ? 'delivery-unknown'
52
+ : RUST_FAILURE_CODES[value.code] ?? 'remote';
49
53
  }
50
54
  }
51
55
  }
@@ -71,6 +75,143 @@ function required(value) {
71
75
  fail();
72
76
  return value;
73
77
  }
78
+ /** Recover the browser's exact optimistic message identity without widening the Remote schema. */
79
+ function browserMessageId(idempotencyKey) {
80
+ return /^msg-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u.test(idempotencyKey)
81
+ ? idempotencyKey
82
+ : undefined;
83
+ }
84
+ function remoteString(value, maxBytes) {
85
+ if (typeof value !== 'string' || value.includes('\0') || Buffer.byteLength(value, 'utf8') > maxBytes)
86
+ fail();
87
+ return value;
88
+ }
89
+ function remoteOptionalString(value, maxBytes) {
90
+ return value === undefined ? undefined : remoteString(value, maxBytes);
91
+ }
92
+ function mailToken(value, maxCharacters) {
93
+ if (typeof value !== 'string'
94
+ || value.length === 0
95
+ || value.trim() !== value
96
+ || Array.from(value).length > maxCharacters
97
+ || /[\u0000-\u001f\u007f-\u009f]/u.test(value))
98
+ fail();
99
+ return value;
100
+ }
101
+ function mailAddress(value) {
102
+ if (typeof value !== 'string')
103
+ fail();
104
+ const length = Array.from(value).length;
105
+ if (length < 3
106
+ || length > 320
107
+ || !value.includes('@')
108
+ || /[\s\u0000-\u001f\u007f-\u009f]/u.test(value))
109
+ fail();
110
+ return value;
111
+ }
112
+ function mailAddresses(value) {
113
+ if (!Array.isArray(value) || value.length > 100)
114
+ fail();
115
+ return value.map(mailAddress);
116
+ }
117
+ function mailTimestamp(value) {
118
+ if (value === undefined)
119
+ return undefined;
120
+ const copied = remoteString(value, 64);
121
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/u.test(copied)
122
+ || !Number.isFinite(Date.parse(copied)))
123
+ fail();
124
+ return copied;
125
+ }
126
+ function uint32(value) {
127
+ if (!Number.isSafeInteger(value) || value < 0 || value > 0xffff_ffff)
128
+ fail();
129
+ return value;
130
+ }
131
+ function recoveryE2eeGroupCount(impact) {
132
+ if (typeof impact !== 'object' || impact === null)
133
+ fail();
134
+ const value = impact;
135
+ return uint32(value.unsupportedE2eeGroupCount !== undefined
136
+ ? value.unsupportedE2eeGroupCount
137
+ : value.unsupportedE2EeGroupCount);
138
+ }
139
+ function boolean(value) {
140
+ if (typeof value !== 'boolean')
141
+ fail();
142
+ return value;
143
+ }
144
+ function mailSummary(value) {
145
+ const receivedAt = mailTimestamp(value.receivedAt);
146
+ const sentAt = mailTimestamp(value.sentAt);
147
+ const folder = value.folder === undefined ? undefined : mailToken(value.folder, 64);
148
+ const preview = remoteOptionalString(value.preview, 4_096);
149
+ const attachmentCount = value.attachmentCount === undefined ? undefined : uint32(value.attachmentCount);
150
+ return {
151
+ id: mailToken(value.id, 2_048),
152
+ ...folder === undefined ? {} : { folder },
153
+ from: mailAddresses(value.from),
154
+ to: mailAddresses(value.to),
155
+ cc: mailAddresses(value.cc),
156
+ subject: remoteString(value.subject, 1_024),
157
+ subjectTruncated: boolean(value.subjectTruncated),
158
+ ...preview === undefined ? {} : { preview },
159
+ previewTruncated: boolean(value.previewTruncated),
160
+ ...receivedAt === undefined ? {} : { receivedAt },
161
+ ...sentAt === undefined ? {} : { sentAt },
162
+ unread: boolean(value.unread),
163
+ hasAttachments: boolean(value.hasAttachments),
164
+ ...attachmentCount === undefined ? {} : { attachmentCount },
165
+ };
166
+ }
167
+ function mailAttachment(value) {
168
+ const fileName = remoteOptionalString(value.fileName, 512);
169
+ const contentType = remoteOptionalString(value.contentType, 255);
170
+ const sizeBytes = value.sizeBytes === undefined ? undefined : remoteString(value.sizeBytes, 20);
171
+ if (sizeBytes !== undefined && !/^(?:0|[1-9]\d*)$/u.test(sizeBytes))
172
+ fail();
173
+ return {
174
+ index: uint32(value.index),
175
+ ...fileName === undefined ? {} : { fileName },
176
+ ...contentType === undefined ? {} : { contentType },
177
+ ...sizeBytes === undefined ? {} : { sizeBytes },
178
+ };
179
+ }
180
+ function mailAccount(value) {
181
+ const mailboxAddress = value.mailboxAddress === undefined ? undefined : mailAddress(value.mailboxAddress);
182
+ const displayName = remoteOptionalString(value.displayName, 512);
183
+ const status = remoteOptionalString(value.status, 128);
184
+ return {
185
+ ...mailboxAddress === undefined ? {} : { mailboxAddress },
186
+ ...displayName === undefined ? {} : { displayName },
187
+ ...status === undefined ? {} : { status },
188
+ };
189
+ }
190
+ function mailInbox(value) {
191
+ if (!Array.isArray(value.items) || value.items.length > 100)
192
+ fail();
193
+ const hasMore = boolean(value.hasMore);
194
+ const nextOffset = value.nextOffset === undefined ? undefined : uint32(value.nextOffset);
195
+ if ((hasMore && nextOffset === undefined) || (!hasMore && nextOffset !== undefined))
196
+ fail();
197
+ return {
198
+ items: value.items.map(mailSummary),
199
+ ...nextOffset === undefined ? {} : { nextOffset },
200
+ hasMore,
201
+ };
202
+ }
203
+ function mailMessage(value) {
204
+ if (!Array.isArray(value.attachments) || value.attachments.length > 100)
205
+ fail();
206
+ const bodyText = remoteOptionalString(value.bodyText, 65_536);
207
+ return {
208
+ summary: mailSummary(value.summary),
209
+ ...bodyText === undefined ? {} : { bodyText },
210
+ bodyTruncated: boolean(value.bodyTruncated),
211
+ hasHtmlBody: boolean(value.hasHtmlBody),
212
+ attachments: value.attachments.map(mailAttachment),
213
+ };
214
+ }
74
215
  function sha256(value) {
75
216
  const provided = value.sha256Hex?.toLowerCase();
76
217
  if (provided !== undefined && /^[a-f0-9]{64}$/u.test(provided))
@@ -93,6 +234,67 @@ function identity(value) {
93
234
  registeredAt: safeInteger(value.registeredAtMs),
94
235
  };
95
236
  }
237
+ function profile(value) {
238
+ return {
239
+ did: required(value.did),
240
+ ...value.handle === undefined ? {} : { handle: value.handle },
241
+ displayName: value.displayName?.trim() ?? '',
242
+ bio: value.bio ?? '',
243
+ tags: [...value.tags],
244
+ ...value.updatedAt === undefined ? {} : { updatedAt: value.updatedAt },
245
+ };
246
+ }
247
+ function mentionPayload(value) {
248
+ if (value.content.kind !== 'payload' || value.content.payloadJson === undefined)
249
+ return undefined;
250
+ let parsed;
251
+ try {
252
+ parsed = JSON.parse(value.content.payloadJson);
253
+ }
254
+ catch {
255
+ return undefined;
256
+ }
257
+ if (typeof parsed !== 'object' || parsed === null || !('text' in parsed) || typeof parsed.text !== 'string')
258
+ return undefined;
259
+ const text = parsed.text;
260
+ if (!('mentions' in parsed) || !Array.isArray(parsed.mentions))
261
+ return { text };
262
+ const length = Array.from(text).length;
263
+ const mentions = [];
264
+ for (const raw of parsed.mentions) {
265
+ if (typeof raw !== 'object' || raw === null
266
+ || !('id' in raw) || typeof raw.id !== 'string' || raw.id.trim() === ''
267
+ || !('range' in raw) || typeof raw.range !== 'object' || raw.range === null
268
+ || !('start' in raw.range) || !Number.isSafeInteger(raw.range.start)
269
+ || !('end' in raw.range) || !Number.isSafeInteger(raw.range.end)
270
+ || !('unit' in raw.range) || raw.range.unit !== 'unicode_code_point'
271
+ || raw.range.start < 0
272
+ || raw.range.end <= raw.range.start
273
+ || raw.range.end > length
274
+ || !('target' in raw) || typeof raw.target !== 'object' || raw.target === null
275
+ || !('kind' in raw.target) || raw.target.kind !== 'human'
276
+ || !('did' in raw.target) || typeof raw.target.did !== 'string' || !raw.target.did.startsWith('did:')) {
277
+ return { text };
278
+ }
279
+ const displayName = 'display_name' in raw.target && typeof raw.target.display_name === 'string'
280
+ ? raw.target.display_name
281
+ : undefined;
282
+ mentions.push({
283
+ id: raw.id,
284
+ start: raw.range.start,
285
+ end: raw.range.end,
286
+ did: raw.target.did,
287
+ ...displayName === undefined ? {} : { displayName },
288
+ });
289
+ }
290
+ const ordered = [...mentions].sort((left, right) => left.start - right.start || left.end - right.end);
291
+ if (ordered.some((item, index) => index > 0 && item.start < ordered[index - 1].end))
292
+ return { text };
293
+ const characters = Array.from(text);
294
+ if (ordered.some(item => !characters.slice(item.start, item.end).join('').startsWith('@')))
295
+ return { text };
296
+ return ordered.length === 0 ? { text } : { text, mentions: ordered };
297
+ }
96
298
  /** Copy one native attachment and normalize its decimal/digest encodings. */
97
299
  function attachment(value) {
98
300
  return {
@@ -112,9 +314,16 @@ function preview(value) {
112
314
  const fileName = value.content.attachment?.fileName;
113
315
  return fileName === undefined ? undefined : `[附件] ${fileName}`;
114
316
  }
317
+ case 'payload': return mentionPayload(value)?.text;
115
318
  default: return undefined;
116
319
  }
117
320
  }
321
+ /** Provider-only protocol events are not part of the browser's text/attachment history contract. */
322
+ function displayableMessage(value) {
323
+ return value.content.kind === 'text'
324
+ || value.content.kind === 'attachment'
325
+ || mentionPayload(value) !== undefined;
326
+ }
118
327
  /** Copy one native page and brand its opaque cursor for the Host API. */
119
328
  function page(value, copy) {
120
329
  return {
@@ -151,17 +360,77 @@ export class RustSdkAdapter {
151
360
  client;
152
361
  attachmentConversations = new Map();
153
362
  disposal;
363
+ listener;
154
364
  constructor(client) {
155
365
  this.client = Promise.resolve(client);
366
+ this.listener = {
367
+ syncNow: reason => this.listenerSyncNow(reason),
368
+ startRealtime: () => this.listenerStartRealtime(),
369
+ listConversations: request => this.listenerConversations(request),
370
+ getHistory: request => this.listenerHistory(request),
371
+ markConversationRead: conversationId => this.markConversationRead(conversationId),
372
+ sendText: request => this.sendText(request),
373
+ };
156
374
  }
157
- async run(operation) {
375
+ async run(operation, ambiguousSend = false) {
158
376
  try {
159
377
  return await operation(await this.client);
160
378
  }
161
379
  catch (error) {
162
- mapError(error);
380
+ mapError(error, ambiguousSend);
163
381
  }
164
382
  }
383
+ async displayableMessages(client, values) {
384
+ const messages = values.filter(displayableMessage);
385
+ const peers = [...new Set(messages
386
+ .filter(message => (message.conversationKind === 'group'
387
+ && !message.outgoing
388
+ && message.senderHandle === undefined
389
+ && message.senderDisplayName === undefined))
390
+ .map(message => message.senderDid))];
391
+ if (peers.length === 0)
392
+ return [...messages];
393
+ const profiles = await client.hydrateDisplayProfiles({ peers });
394
+ const byDid = new Map();
395
+ for (const profile of profiles) {
396
+ if (profile.did !== undefined)
397
+ byDid.set(profile.did, profile);
398
+ }
399
+ return messages.map((message) => {
400
+ const profile = byDid.get(message.senderDid);
401
+ if (profile === undefined)
402
+ return message;
403
+ return {
404
+ ...message,
405
+ ...profile.handle === undefined ? {} : { senderHandle: profile.handle },
406
+ ...profile.displayName === undefined ? {} : { senderDisplayName: profile.displayName },
407
+ };
408
+ });
409
+ }
410
+ /**
411
+ * Join the persisted Core peer-profile projection onto direct roster rows.
412
+ * The conversation registry intentionally keeps routing identifiers separate
413
+ * from display metadata, so a bare roster row may otherwise regress to a Handle.
414
+ */
415
+ async displayableConversations(client, values) {
416
+ const peers = [...new Set(values
417
+ .filter((conversation) => (conversation.kind === 'direct' && conversation.peerDid !== undefined))
418
+ .map(conversation => conversation.peerDid))];
419
+ const profiles = peers.length === 0 ? [] : await client.hydrateDisplayProfiles({ peers });
420
+ const byPeer = new Map();
421
+ for (const [index, profile] of profiles.entries()) {
422
+ const requested = peers[index];
423
+ if (requested !== undefined)
424
+ byPeer.set(requested, profile);
425
+ if (profile.did !== undefined)
426
+ byPeer.set(profile.did, profile);
427
+ if (profile.handle !== undefined)
428
+ byPeer.set(profile.handle, profile);
429
+ }
430
+ return values.map(value => this.conversation(value, value.kind === 'direct'
431
+ ? byPeer.get(value.peerDid ?? '') ?? byPeer.get(value.peerHandle ?? '')
432
+ : undefined));
433
+ }
165
434
  message(value) {
166
435
  const sentAt = value.sentAt === undefined ? fail() : timestamp(value.sentAt);
167
436
  const common = {
@@ -193,12 +462,29 @@ export class RustSdkAdapter {
193
462
  },
194
463
  };
195
464
  }
465
+ case 'payload': {
466
+ const parsed = mentionPayload(value);
467
+ if (parsed === undefined)
468
+ fail();
469
+ return {
470
+ ...common,
471
+ content: {
472
+ kind: 'text',
473
+ text: parsed.text,
474
+ ...parsed.mentions === undefined ? {} : { mentions: parsed.mentions },
475
+ },
476
+ };
477
+ }
196
478
  default: fail();
197
479
  }
198
480
  }
199
- conversation(value) {
481
+ conversation(value, profile) {
200
482
  const id = required(value.id);
201
- const title = value.title?.trim();
483
+ const displayName = profile?.displayName?.trim();
484
+ const profileHandle = profile?.handle?.trim();
485
+ const title = displayName === undefined || displayName.length === 0
486
+ ? value.title?.trim()
487
+ : displayName;
202
488
  const lastMessagePreview = preview(value.lastMessage);
203
489
  const common = {
204
490
  id,
@@ -209,14 +495,17 @@ export class RustSdkAdapter {
209
495
  ...value.lastMessageAt === undefined ? {} : { lastMessageAt: timestamp(value.lastMessageAt) },
210
496
  ...lastMessagePreview === undefined ? {} : { lastMessagePreview },
211
497
  };
212
- if (value.lastMessage !== undefined)
498
+ if (value.lastMessage !== undefined && displayableMessage(value.lastMessage))
213
499
  this.message(value.lastMessage);
214
500
  switch (value.kind) {
215
501
  case 'direct': return {
216
502
  kind: 'direct',
217
503
  ...common,
218
- peerDid: required(value.peerDid),
219
- ...value.peerHandle === undefined ? {} : { peerHandle: value.peerHandle },
504
+ peerDid: required(profile?.did ?? value.peerDid),
505
+ ...profileHandle === undefined && value.peerHandle === undefined
506
+ ? {}
507
+ : { peerHandle: required(profileHandle ?? value.peerHandle) },
508
+ ...displayName === undefined || displayName.length === 0 ? {} : { displayName },
220
509
  };
221
510
  case 'group': return {
222
511
  kind: 'group',
@@ -226,6 +515,108 @@ export class RustSdkAdapter {
226
515
  default: fail();
227
516
  }
228
517
  }
518
+ createdGroup(value) {
519
+ return {
520
+ kind: 'group',
521
+ id: required(value.conversationId),
522
+ groupDid: required(value.did),
523
+ title: required(value.title),
524
+ unreadCount: 0,
525
+ };
526
+ }
527
+ groupSnapshot(value) {
528
+ return {
529
+ groupDid: required(value.did),
530
+ conversationId: required(value.conversationId),
531
+ title: required(value.title),
532
+ ...value.description === undefined ? {} : { description: value.description },
533
+ ...value.myRole === undefined ? {} : { myRole: value.myRole },
534
+ ...value.membershipStatus === undefined ? {} : { membershipStatus: value.membershipStatus },
535
+ ...value.memberCount === undefined ? {} : { memberCount: value.memberCount },
536
+ };
537
+ }
538
+ groupMember(value) {
539
+ return {
540
+ did: required(value.did),
541
+ ...value.handle === undefined ? {} : { handle: value.handle },
542
+ };
543
+ }
544
+ groupMemberRecord(value, profile) {
545
+ return {
546
+ ...value.membershipId === undefined ? {} : { membershipId: value.membershipId },
547
+ ...value.peerPersonaId === undefined ? {} : { peerPersonaId: value.peerPersonaId },
548
+ ...value.did === undefined ? {} : { did: value.did },
549
+ ...value.credentialDid === undefined ? {} : { credentialDid: value.credentialDid },
550
+ ...value.handle === undefined ? {} : { handle: value.handle },
551
+ ...profile?.displayName === undefined ? {} : { displayName: profile.displayName },
552
+ ...value.role === undefined ? {} : { role: value.role },
553
+ ...value.status === undefined ? {} : { status: value.status },
554
+ ...value.joinedAt === undefined ? {} : { joinedAt: value.joinedAt },
555
+ ...value.subjectType === undefined ? {} : { subjectType: value.subjectType },
556
+ };
557
+ }
558
+ listenerConversation(value) {
559
+ const common = {
560
+ id: required(value.id),
561
+ unreadCount: value.unreadCount,
562
+ ...value.lastMessageAt === undefined ? {} : { lastMessageAt: timestamp(value.lastMessageAt) },
563
+ };
564
+ if (value.kind === 'direct') {
565
+ return {
566
+ kind: 'direct',
567
+ ...common,
568
+ peerDid: required(value.peerDid),
569
+ ...value.peerHandle === undefined ? {} : { peerHandle: value.peerHandle },
570
+ };
571
+ }
572
+ return { kind: 'group', ...common };
573
+ }
574
+ listenerMessage(value) {
575
+ const sentAt = value.sentAt === undefined ? fail() : timestamp(value.sentAt);
576
+ const common = {
577
+ id: required(value.id),
578
+ conversationId: required(value.conversationId),
579
+ conversationKind: value.conversationKind,
580
+ senderDid: required(value.senderDid),
581
+ sentAt,
582
+ outgoing: value.outgoing,
583
+ };
584
+ return value.content.kind === 'text' && value.content.text !== undefined
585
+ ? { ...common, content: { kind: 'text', text: required(value.content.text) } }
586
+ : { ...common, content: { kind: 'ignored' } };
587
+ }
588
+ listenerSyncNow(reason) {
589
+ return this.run(async (client) => {
590
+ const result = await client.syncNow({ reason });
591
+ if (result.status === 'idle' || result.status === 'changed')
592
+ return;
593
+ if (result.status === 'auth_revoked')
594
+ fail('forbidden');
595
+ fail('network');
596
+ });
597
+ }
598
+ listenerStartRealtime() {
599
+ return this.run(async (client) => {
600
+ const session = await client.startRealtime();
601
+ return {
602
+ nextEvent: () => this.run(() => session.nextEvent()),
603
+ stop: () => this.run(() => session.stop()),
604
+ };
605
+ });
606
+ }
607
+ listenerConversations(request) {
608
+ return this.run(async (client) => page(await client.listConversations(request), value => this.listenerConversation(value)));
609
+ }
610
+ listenerHistory(request) {
611
+ return this.run(async (client) => {
612
+ const history = await client.getHistory({
613
+ conversationId: String(request.conversationId),
614
+ ...request.cursor === undefined ? {} : { cursor: String(request.cursor) },
615
+ ...request.limit === undefined ? {} : { limit: request.limit },
616
+ });
617
+ return page({ ...history, items: [...history.items].reverse() }, value => this.listenerMessage(value));
618
+ });
619
+ }
229
620
  async conversationId(client, target) {
230
621
  if (target.kind === 'direct')
231
622
  return required((await client.resolvePeer(target.peer)).conversationId);
@@ -270,6 +661,59 @@ export class RustSdkAdapter {
270
661
  updateDisplayName(request) {
271
662
  return this.run(async (client) => identity(await client.updateDisplayName(request.displayName)));
272
663
  }
664
+ getProfile() {
665
+ return this.run(async (client) => profile(await client.getProfile()));
666
+ }
667
+ updateProfile(request) {
668
+ return this.run(async (client) => profile(await client.updateProfile({
669
+ displayName: request.displayName,
670
+ bio: request.bio,
671
+ tags: [...request.tags],
672
+ })));
673
+ }
674
+ recoveryProgress(value) {
675
+ return {
676
+ operationId: required(value.operationId),
677
+ fullHandle: required(value.fullHandle),
678
+ ...value.previousDid === undefined ? {} : { previousDid: value.previousDid },
679
+ currentDid: required(value.currentDid),
680
+ phase: value.phase,
681
+ ...value.failureCode === undefined ? {} : { failureCode: value.failureCode },
682
+ retryable: boolean(value.retryable),
683
+ localOrdinaryDataWillMigrate: boolean(value.impact.localOrdinaryDataWillMigrate),
684
+ otherDevicesMustRejoin: boolean(value.impact.otherDevicesMustRejoin),
685
+ unsupportedE2eeGroupCount: recoveryE2eeGroupCount(value.impact),
686
+ unsupportedDidOnlyGroupCount: uint32(value.impact.unsupportedDidOnlyGroupCount),
687
+ };
688
+ }
689
+ sendRecoveryOtp(request) {
690
+ return this.run(async (client) => {
691
+ const value = await client.requestHandleRecoveryOtp(request);
692
+ return {
693
+ operationId: required(value.operationId),
694
+ fullHandle: required(value.fullHandle),
695
+ retryAfterSeconds: value.retryAfterSeconds,
696
+ retryAt: value.retryAt,
697
+ };
698
+ });
699
+ }
700
+ prepareRecovery(request) {
701
+ return this.run(async (client) => this.recoveryProgress(await client.prepareHandleRecovery(request)));
702
+ }
703
+ activateRecovery(request) {
704
+ return this.run(async (client) => this.recoveryProgress(await client.activateHandleRecovery(request)));
705
+ }
706
+ getRecoveryStatus(request) {
707
+ return this.run(async (client) => this.recoveryProgress(await client.getHandleRecoveryStatus(request)));
708
+ }
709
+ resumeRecovery(request) {
710
+ return this.run(async (client) => this.recoveryProgress(await client.resumeHandleRecovery(request)));
711
+ }
712
+ discardRecovery(request) {
713
+ return this.run(async (client) => {
714
+ await client.discardHandleRecovery(request);
715
+ });
716
+ }
273
717
  resolvePeer(peer) {
274
718
  return this.run(async (client) => {
275
719
  const value = await client.resolvePeer(peer);
@@ -281,8 +725,79 @@ export class RustSdkAdapter {
281
725
  };
282
726
  });
283
727
  }
728
+ createGroup(name) {
729
+ return this.run(async (client) => this.createdGroup(await client.createGroup({ name })));
730
+ }
731
+ addGroupMember(groupDid, member) {
732
+ return this.run(async (client) => this.groupMember(await client.addGroupMember({
733
+ groupDid: String(groupDid),
734
+ member,
735
+ })));
736
+ }
737
+ getGroup(groupDid) {
738
+ return this.run(async (client) => this.groupSnapshot(await client.getGroup({ groupDid: String(groupDid) })));
739
+ }
740
+ joinGroup(groupDid) {
741
+ return this.run(async (client) => this.groupSnapshot(await client.joinGroup({ groupDid: String(groupDid) })));
742
+ }
743
+ leaveGroup(groupDid) {
744
+ return this.run(client => client.leaveGroup({ groupDid: String(groupDid) }));
745
+ }
746
+ listGroupMembers(request) {
747
+ return this.run(async (client) => {
748
+ const value = await client.listGroupMembers({
749
+ groupDid: String(request.groupDid),
750
+ ...request.cursor === undefined ? {} : { cursor: String(request.cursor) },
751
+ ...request.limit === undefined ? {} : { limit: request.limit },
752
+ });
753
+ const peers = [...new Set(value.items.flatMap(member => member.did ?? member.handle ?? []))];
754
+ const profiles = peers.length === 0 ? [] : await client.hydrateDisplayProfiles({ peers });
755
+ const byPeer = new Map();
756
+ for (const [index, profile] of profiles.entries()) {
757
+ const requested = peers[index];
758
+ if (requested !== undefined)
759
+ byPeer.set(requested, profile);
760
+ if (profile.did !== undefined)
761
+ byPeer.set(profile.did, profile);
762
+ if (profile.handle !== undefined)
763
+ byPeer.set(profile.handle, profile);
764
+ }
765
+ return {
766
+ items: value.items.map(item => this.groupMemberRecord(item, byPeer.get(item.did ?? '') ?? byPeer.get(item.handle ?? ''))),
767
+ ...value.total === undefined ? {} : { total: value.total },
768
+ ...value.nextCursor === undefined ? {} : { nextCursor: value.nextCursor },
769
+ hasMore: value.hasMore,
770
+ ...value.pageGroup === undefined ? {} : { pageGroup: value.pageGroup },
771
+ ...value.groupStateVersion === undefined ? {} : { groupStateVersion: value.groupStateVersion },
772
+ warnings: [...value.warnings],
773
+ };
774
+ });
775
+ }
776
+ removeGroupMember(groupDid, member) {
777
+ return this.run(async (client) => this.groupMember(await client.removeGroupMember({
778
+ groupDid: String(groupDid),
779
+ member,
780
+ })));
781
+ }
782
+ resumeGroupRebindRecovery() {
783
+ return this.run(async (client) => {
784
+ const value = await client.resumeGroupRebindRecovery(100);
785
+ return {
786
+ processed: uint32(value.processed),
787
+ completed: uint32(value.completed),
788
+ pending: uint32(value.pending),
789
+ blocked: uint32(value.blocked),
790
+ };
791
+ });
792
+ }
284
793
  listConversations(request) {
285
- return this.run(async (client) => page(await client.listConversations(request), value => this.conversation(value)));
794
+ return this.run(async (client) => {
795
+ const conversations = await client.listConversations(request);
796
+ return {
797
+ ...page(conversations, value => value),
798
+ items: await this.displayableConversations(client, conversations.items),
799
+ };
800
+ });
286
801
  }
287
802
  getHistory(request) {
288
803
  return this.run(async (client) => {
@@ -293,28 +808,69 @@ export class RustSdkAdapter {
293
808
  });
294
809
  return page(
295
810
  // Rust Core pages newest-first; the Host/UI contract is chronological.
296
- { ...history, items: [...history.items].reverse() }, value => this.message(value));
811
+ { ...history, items: await this.displayableMessages(client, [...history.items].reverse()) }, value => this.message(value));
812
+ });
813
+ }
814
+ getLocalHistory(request) {
815
+ return this.run(async (client) => {
816
+ const history = await client.getLocalConversationTimeline({
817
+ conversationId: String(request.conversationId),
818
+ ...request.cursor === undefined ? {} : { cursor: String(request.cursor) },
819
+ ...request.limit === undefined ? {} : { limit: request.limit },
820
+ });
821
+ return page(
822
+ // Rust Core local pages are newest-first; the Host/UI contract is chronological.
823
+ { ...history, items: await this.displayableMessages(client, [...history.items].reverse()) }, value => this.message(value));
297
824
  });
298
825
  }
299
826
  markConversationRead(conversationId) {
300
827
  return this.run(async (client) => (await client.markConversationRead(String(conversationId))).updatedCount);
301
828
  }
302
829
  sendText(request) {
303
- return this.run(async (client) => this.message(await client.sendText({
304
- conversationId: await this.conversationId(client, request.target),
305
- text: request.text,
306
- idempotencyKey: request.idempotencyKey,
307
- })));
830
+ return this.run(async (client) => {
831
+ const clientMessageId = browserMessageId(request.idempotencyKey);
832
+ const conversationId = await this.conversationId(client, request.target);
833
+ if (request.mentions !== undefined && request.mentions.length > 0) {
834
+ return this.message(await client.sendPayload({
835
+ conversationId,
836
+ payloadJson: JSON.stringify({
837
+ text: request.text,
838
+ mentions: request.mentions.map(mention => ({
839
+ id: mention.id,
840
+ range: { start: mention.start, end: mention.end, unit: 'unicode_code_point' },
841
+ target: {
842
+ kind: 'human',
843
+ did: mention.did,
844
+ ...mention.displayName === undefined ? {} : { display_name: mention.displayName },
845
+ },
846
+ mention_role: 'addressee',
847
+ })),
848
+ }),
849
+ ...clientMessageId === undefined ? {} : { clientMessageId },
850
+ idempotencyKey: request.idempotencyKey,
851
+ }));
852
+ }
853
+ return this.message(await client.sendText({
854
+ conversationId,
855
+ text: request.text,
856
+ ...clientMessageId === undefined ? {} : { clientMessageId },
857
+ idempotencyKey: request.idempotencyKey,
858
+ }));
859
+ });
308
860
  }
309
861
  sendAttachment(request) {
310
- return this.run(async (client) => this.message(await client.sendAttachment({
311
- conversationId: await this.conversationId(client, request.target),
312
- fileName: request.attachment.fileName,
313
- mimeType: request.attachment.mimeType,
314
- bytes: request.attachment.bytes,
315
- ...request.caption === undefined ? {} : { caption: request.caption },
316
- idempotencyKey: request.idempotencyKey,
317
- })));
862
+ return this.run(async (client) => {
863
+ const clientMessageId = browserMessageId(request.idempotencyKey);
864
+ return this.message(await client.sendAttachment({
865
+ conversationId: await this.conversationId(client, request.target),
866
+ fileName: request.attachment.fileName,
867
+ mimeType: request.attachment.mimeType,
868
+ bytes: request.attachment.bytes,
869
+ ...request.caption === undefined ? {} : { caption: request.caption },
870
+ ...clientMessageId === undefined ? {} : { clientMessageId },
871
+ idempotencyKey: request.idempotencyKey,
872
+ }));
873
+ });
318
874
  }
319
875
  downloadAttachment(request) {
320
876
  return this.run(async (client) => {
@@ -329,6 +885,48 @@ export class RustSdkAdapter {
329
885
  return { attachment: attachment(value.attachment), bytes: Uint8Array.from(value.bytes) };
330
886
  });
331
887
  }
888
+ getMailAccount() {
889
+ return this.run(async (client) => mailAccount(await client.getMailAccount()));
890
+ }
891
+ listMailInbox(request = {}) {
892
+ return this.run(async (client) => mailInbox(await client.listMailInbox({
893
+ ...request.folder === undefined ? {} : { folder: request.folder },
894
+ ...request.unreadOnly === undefined ? {} : { unreadOnly: request.unreadOnly },
895
+ ...request.limit === undefined ? {} : { limit: request.limit },
896
+ ...request.offset === undefined ? {} : { offset: request.offset },
897
+ })));
898
+ }
899
+ readMail(request) {
900
+ return this.run(async (client) => mailMessage(await client.readMail(request.messageId)));
901
+ }
902
+ markMailRead(request) {
903
+ return this.run(async (client) => {
904
+ const value = await client.markMailRead({
905
+ messageIds: [...request.messageIds],
906
+ });
907
+ return { updated: uint32(value.updated) };
908
+ });
909
+ }
910
+ sendMail(request) {
911
+ return this.run(async (client) => {
912
+ const value = await client.sendMail({
913
+ to: [...request.to],
914
+ ...request.cc === undefined ? {} : { cc: [...request.cc] },
915
+ subject: request.subject,
916
+ bodyText: request.bodyText,
917
+ });
918
+ if (!Array.isArray(value.warnings) || value.warnings.length > 100)
919
+ fail();
920
+ const messageId = value.messageId === undefined
921
+ ? undefined
922
+ : mailToken(value.messageId, 2_048);
923
+ return {
924
+ accepted: boolean(value.accepted),
925
+ ...messageId === undefined ? {} : { messageId },
926
+ warnings: value.warnings.map(warning => remoteString(warning, 1_024)),
927
+ };
928
+ }, true);
929
+ }
332
930
  clearLocalData() {
333
931
  return this.run(client => client.clearLocalData());
334
932
  }