@quilibrium/quorum-shared 2.1.0-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/dist/index.d.mts +2414 -0
- package/dist/index.d.ts +2414 -0
- package/dist/index.js +2788 -0
- package/dist/index.mjs +2678 -0
- package/package.json +49 -0
- package/src/api/client.ts +86 -0
- package/src/api/endpoints.ts +87 -0
- package/src/api/errors.ts +179 -0
- package/src/api/index.ts +35 -0
- package/src/crypto/encryption-state.ts +249 -0
- package/src/crypto/index.ts +55 -0
- package/src/crypto/types.ts +307 -0
- package/src/crypto/wasm-provider.ts +298 -0
- package/src/hooks/index.ts +31 -0
- package/src/hooks/keys.ts +62 -0
- package/src/hooks/mutations/index.ts +15 -0
- package/src/hooks/mutations/useDeleteMessage.ts +67 -0
- package/src/hooks/mutations/useEditMessage.ts +87 -0
- package/src/hooks/mutations/useReaction.ts +163 -0
- package/src/hooks/mutations/useSendMessage.ts +131 -0
- package/src/hooks/useChannels.ts +49 -0
- package/src/hooks/useMessages.ts +77 -0
- package/src/hooks/useSpaces.ts +60 -0
- package/src/index.ts +32 -0
- package/src/signing/index.ts +10 -0
- package/src/signing/types.ts +83 -0
- package/src/signing/wasm-provider.ts +75 -0
- package/src/storage/adapter.ts +118 -0
- package/src/storage/index.ts +9 -0
- package/src/sync/index.ts +83 -0
- package/src/sync/service.test.ts +822 -0
- package/src/sync/service.ts +947 -0
- package/src/sync/types.ts +267 -0
- package/src/sync/utils.ts +588 -0
- package/src/transport/browser-websocket.ts +299 -0
- package/src/transport/index.ts +34 -0
- package/src/transport/rn-websocket.ts +321 -0
- package/src/transport/types.ts +56 -0
- package/src/transport/websocket.ts +212 -0
- package/src/types/bookmark.ts +29 -0
- package/src/types/conversation.ts +25 -0
- package/src/types/index.ts +57 -0
- package/src/types/message.ts +178 -0
- package/src/types/space.ts +75 -0
- package/src/types/user.ts +72 -0
- package/src/utils/encoding.ts +106 -0
- package/src/utils/formatting.ts +139 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/logger.ts +141 -0
- package/src/utils/mentions.ts +135 -0
- package/src/utils/validation.ts +84 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,2414 @@
|
|
|
1
|
+
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
+
import * as _tanstack_query_core from '@tanstack/query-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Space-related types for Quorum
|
|
6
|
+
*/
|
|
7
|
+
type Permission = 'message:delete' | 'message:pin' | 'mention:everyone' | 'user:mute';
|
|
8
|
+
type Role = {
|
|
9
|
+
roleId: string;
|
|
10
|
+
displayName: string;
|
|
11
|
+
roleTag: string;
|
|
12
|
+
color: string;
|
|
13
|
+
members: string[];
|
|
14
|
+
permissions: Permission[];
|
|
15
|
+
isPublic?: boolean;
|
|
16
|
+
};
|
|
17
|
+
type Emoji = {
|
|
18
|
+
name: string;
|
|
19
|
+
id: string;
|
|
20
|
+
imgUrl: string;
|
|
21
|
+
};
|
|
22
|
+
type Sticker = {
|
|
23
|
+
name: string;
|
|
24
|
+
id: string;
|
|
25
|
+
imgUrl: string;
|
|
26
|
+
};
|
|
27
|
+
type Group = {
|
|
28
|
+
groupName: string;
|
|
29
|
+
channels: Channel[];
|
|
30
|
+
icon?: string;
|
|
31
|
+
iconColor?: string;
|
|
32
|
+
iconVariant?: 'outline' | 'filled';
|
|
33
|
+
};
|
|
34
|
+
type Channel = {
|
|
35
|
+
channelId: string;
|
|
36
|
+
spaceId: string;
|
|
37
|
+
channelName: string;
|
|
38
|
+
channelTopic: string;
|
|
39
|
+
channelKey?: string;
|
|
40
|
+
createdDate: number;
|
|
41
|
+
modifiedDate: number;
|
|
42
|
+
mentionCount?: number;
|
|
43
|
+
mentions?: string;
|
|
44
|
+
isReadOnly?: boolean;
|
|
45
|
+
managerRoleIds?: string[];
|
|
46
|
+
isPinned?: boolean;
|
|
47
|
+
pinnedAt?: number;
|
|
48
|
+
icon?: string;
|
|
49
|
+
iconColor?: string;
|
|
50
|
+
iconVariant?: 'outline' | 'filled';
|
|
51
|
+
};
|
|
52
|
+
type Space = {
|
|
53
|
+
spaceId: string;
|
|
54
|
+
spaceName: string;
|
|
55
|
+
description?: string;
|
|
56
|
+
vanityUrl: string;
|
|
57
|
+
inviteUrl: string;
|
|
58
|
+
iconUrl: string;
|
|
59
|
+
bannerUrl: string;
|
|
60
|
+
defaultChannelId: string;
|
|
61
|
+
hubAddress: string;
|
|
62
|
+
createdDate: number;
|
|
63
|
+
modifiedDate: number;
|
|
64
|
+
isRepudiable: boolean;
|
|
65
|
+
isPublic: boolean;
|
|
66
|
+
saveEditHistory?: boolean;
|
|
67
|
+
groups: Group[];
|
|
68
|
+
roles: Role[];
|
|
69
|
+
emojis: Emoji[];
|
|
70
|
+
stickers: Sticker[];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Message-related types for Quorum
|
|
75
|
+
*/
|
|
76
|
+
/** Client-side ephemeral status - NEVER persist to storage or include in network payload */
|
|
77
|
+
type MessageSendStatus = 'sending' | 'sent' | 'failed';
|
|
78
|
+
type PostMessage = {
|
|
79
|
+
senderId: string;
|
|
80
|
+
type: 'post';
|
|
81
|
+
text: string | string[];
|
|
82
|
+
repliesToMessageId?: string;
|
|
83
|
+
};
|
|
84
|
+
type UpdateProfileMessage = {
|
|
85
|
+
senderId: string;
|
|
86
|
+
type: 'update-profile';
|
|
87
|
+
displayName: string;
|
|
88
|
+
userIcon: string;
|
|
89
|
+
};
|
|
90
|
+
type RemoveMessage = {
|
|
91
|
+
senderId: string;
|
|
92
|
+
type: 'remove-message';
|
|
93
|
+
removeMessageId: string;
|
|
94
|
+
};
|
|
95
|
+
type EventMessage = {
|
|
96
|
+
senderId: string;
|
|
97
|
+
type: 'event';
|
|
98
|
+
text: string;
|
|
99
|
+
repliesToMessageId?: string;
|
|
100
|
+
};
|
|
101
|
+
type EmbedMessage = {
|
|
102
|
+
senderId: string;
|
|
103
|
+
type: 'embed';
|
|
104
|
+
imageUrl?: string;
|
|
105
|
+
thumbnailUrl?: string;
|
|
106
|
+
videoUrl?: string;
|
|
107
|
+
width?: string;
|
|
108
|
+
height?: string;
|
|
109
|
+
isLargeGif?: boolean;
|
|
110
|
+
repliesToMessageId?: string;
|
|
111
|
+
};
|
|
112
|
+
type ReactionMessage = {
|
|
113
|
+
senderId: string;
|
|
114
|
+
type: 'reaction';
|
|
115
|
+
reaction: string;
|
|
116
|
+
messageId: string;
|
|
117
|
+
};
|
|
118
|
+
type RemoveReactionMessage = {
|
|
119
|
+
senderId: string;
|
|
120
|
+
type: 'remove-reaction';
|
|
121
|
+
reaction: string;
|
|
122
|
+
messageId: string;
|
|
123
|
+
};
|
|
124
|
+
type JoinMessage = {
|
|
125
|
+
senderId: string;
|
|
126
|
+
type: 'join';
|
|
127
|
+
};
|
|
128
|
+
type LeaveMessage = {
|
|
129
|
+
senderId: string;
|
|
130
|
+
type: 'leave';
|
|
131
|
+
};
|
|
132
|
+
type KickMessage = {
|
|
133
|
+
senderId: string;
|
|
134
|
+
type: 'kick';
|
|
135
|
+
};
|
|
136
|
+
type MuteMessage = {
|
|
137
|
+
senderId: string;
|
|
138
|
+
type: 'mute';
|
|
139
|
+
targetUserId: string;
|
|
140
|
+
muteId: string;
|
|
141
|
+
timestamp: number;
|
|
142
|
+
action: 'mute' | 'unmute';
|
|
143
|
+
duration?: number;
|
|
144
|
+
};
|
|
145
|
+
type StickerMessage = {
|
|
146
|
+
senderId: string;
|
|
147
|
+
type: 'sticker';
|
|
148
|
+
stickerId: string;
|
|
149
|
+
repliesToMessageId?: string;
|
|
150
|
+
};
|
|
151
|
+
type PinMessage = {
|
|
152
|
+
senderId: string;
|
|
153
|
+
type: 'pin';
|
|
154
|
+
targetMessageId: string;
|
|
155
|
+
action: 'pin' | 'unpin';
|
|
156
|
+
};
|
|
157
|
+
type DeleteConversationMessage = {
|
|
158
|
+
senderId: string;
|
|
159
|
+
type: 'delete-conversation';
|
|
160
|
+
};
|
|
161
|
+
type EditMessage = {
|
|
162
|
+
senderId: string;
|
|
163
|
+
type: 'edit-message';
|
|
164
|
+
originalMessageId: string;
|
|
165
|
+
editedText: string | string[];
|
|
166
|
+
editedAt: number;
|
|
167
|
+
editNonce: string;
|
|
168
|
+
editSignature?: string;
|
|
169
|
+
};
|
|
170
|
+
type MessageContent = PostMessage | EventMessage | EmbedMessage | ReactionMessage | RemoveReactionMessage | RemoveMessage | JoinMessage | LeaveMessage | KickMessage | MuteMessage | UpdateProfileMessage | StickerMessage | PinMessage | DeleteConversationMessage | EditMessage;
|
|
171
|
+
type Reaction = {
|
|
172
|
+
emojiId: string;
|
|
173
|
+
spaceId: string;
|
|
174
|
+
emojiName: string;
|
|
175
|
+
count: number;
|
|
176
|
+
memberIds: string[];
|
|
177
|
+
};
|
|
178
|
+
type Mentions = {
|
|
179
|
+
memberIds: string[];
|
|
180
|
+
roleIds: string[];
|
|
181
|
+
channelIds: string[];
|
|
182
|
+
everyone?: boolean;
|
|
183
|
+
totalMentionCount?: number;
|
|
184
|
+
};
|
|
185
|
+
type Message = {
|
|
186
|
+
channelId: string;
|
|
187
|
+
spaceId: string;
|
|
188
|
+
messageId: string;
|
|
189
|
+
digestAlgorithm: string;
|
|
190
|
+
nonce: string;
|
|
191
|
+
createdDate: number;
|
|
192
|
+
modifiedDate: number;
|
|
193
|
+
lastModifiedHash: string;
|
|
194
|
+
content: MessageContent;
|
|
195
|
+
reactions: Reaction[];
|
|
196
|
+
mentions: Mentions;
|
|
197
|
+
replyMetadata?: {
|
|
198
|
+
parentAuthor: string;
|
|
199
|
+
parentChannelId: string;
|
|
200
|
+
};
|
|
201
|
+
publicKey?: string;
|
|
202
|
+
signature?: string;
|
|
203
|
+
isPinned?: boolean;
|
|
204
|
+
pinnedAt?: number;
|
|
205
|
+
pinnedBy?: string;
|
|
206
|
+
edits?: Array<{
|
|
207
|
+
text: string | string[];
|
|
208
|
+
modifiedDate: number;
|
|
209
|
+
lastModifiedHash: string;
|
|
210
|
+
}>;
|
|
211
|
+
/** Client-side ephemeral - NEVER persist or transmit */
|
|
212
|
+
sendStatus?: MessageSendStatus;
|
|
213
|
+
/** Client-side ephemeral - sanitized error message for display */
|
|
214
|
+
sendError?: string;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Conversation (DM) types for Quorum
|
|
219
|
+
*/
|
|
220
|
+
type ConversationSource = 'quorum' | 'farcaster';
|
|
221
|
+
type Conversation = {
|
|
222
|
+
conversationId: string;
|
|
223
|
+
type: 'direct' | 'group';
|
|
224
|
+
timestamp: number;
|
|
225
|
+
address: string;
|
|
226
|
+
icon: string;
|
|
227
|
+
displayName: string;
|
|
228
|
+
lastReadTimestamp?: number;
|
|
229
|
+
isRepudiable?: boolean;
|
|
230
|
+
saveEditHistory?: boolean;
|
|
231
|
+
lastMessageId?: string;
|
|
232
|
+
source?: ConversationSource;
|
|
233
|
+
farcasterConversationId?: string;
|
|
234
|
+
farcasterFid?: number;
|
|
235
|
+
farcasterUsername?: string;
|
|
236
|
+
farcasterParticipantFids?: number[];
|
|
237
|
+
unreadCount?: number;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Bookmark types for Quorum
|
|
242
|
+
*/
|
|
243
|
+
type Bookmark = {
|
|
244
|
+
bookmarkId: string;
|
|
245
|
+
messageId: string;
|
|
246
|
+
spaceId?: string;
|
|
247
|
+
channelId?: string;
|
|
248
|
+
conversationId?: string;
|
|
249
|
+
sourceType: 'channel' | 'dm';
|
|
250
|
+
createdAt: number;
|
|
251
|
+
cachedPreview: {
|
|
252
|
+
senderAddress: string;
|
|
253
|
+
senderName: string;
|
|
254
|
+
textSnippet: string;
|
|
255
|
+
messageDate: number;
|
|
256
|
+
sourceName: string;
|
|
257
|
+
contentType: 'text' | 'image' | 'sticker';
|
|
258
|
+
imageUrl?: string;
|
|
259
|
+
thumbnailUrl?: string;
|
|
260
|
+
stickerId?: string;
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
declare const BOOKMARKS_CONFIG: {
|
|
264
|
+
readonly MAX_BOOKMARKS: 200;
|
|
265
|
+
readonly PREVIEW_SNIPPET_LENGTH: 150;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* User-related types for Quorum
|
|
270
|
+
*/
|
|
271
|
+
|
|
272
|
+
type FolderColor = string;
|
|
273
|
+
type NavItem = {
|
|
274
|
+
type: 'space';
|
|
275
|
+
id: string;
|
|
276
|
+
} | {
|
|
277
|
+
type: 'folder';
|
|
278
|
+
id: string;
|
|
279
|
+
name: string;
|
|
280
|
+
spaceIds: string[];
|
|
281
|
+
icon?: string;
|
|
282
|
+
color?: FolderColor;
|
|
283
|
+
createdDate: number;
|
|
284
|
+
modifiedDate: number;
|
|
285
|
+
};
|
|
286
|
+
type NotificationSettings = {
|
|
287
|
+
enabled?: boolean;
|
|
288
|
+
mentions?: boolean;
|
|
289
|
+
replies?: boolean;
|
|
290
|
+
all?: boolean;
|
|
291
|
+
};
|
|
292
|
+
type UserConfig = {
|
|
293
|
+
address: string;
|
|
294
|
+
spaceIds: string[];
|
|
295
|
+
items?: NavItem[];
|
|
296
|
+
timestamp?: number;
|
|
297
|
+
nonRepudiable?: boolean;
|
|
298
|
+
allowSync?: boolean;
|
|
299
|
+
name?: string;
|
|
300
|
+
profile_image?: string;
|
|
301
|
+
spaceKeys?: {
|
|
302
|
+
spaceId: string;
|
|
303
|
+
encryptionState: {
|
|
304
|
+
conversationId: string;
|
|
305
|
+
inboxId: string;
|
|
306
|
+
state: string;
|
|
307
|
+
timestamp: number;
|
|
308
|
+
};
|
|
309
|
+
keys: {
|
|
310
|
+
keyId: string;
|
|
311
|
+
address?: string;
|
|
312
|
+
publicKey: string;
|
|
313
|
+
privateKey: string;
|
|
314
|
+
spaceId: string;
|
|
315
|
+
}[];
|
|
316
|
+
}[];
|
|
317
|
+
notificationSettings?: {
|
|
318
|
+
[spaceId: string]: NotificationSettings;
|
|
319
|
+
};
|
|
320
|
+
bookmarks?: Bookmark[];
|
|
321
|
+
deletedBookmarkIds?: string[];
|
|
322
|
+
};
|
|
323
|
+
type UserProfile = {
|
|
324
|
+
address: string;
|
|
325
|
+
name?: string;
|
|
326
|
+
display_name?: string;
|
|
327
|
+
profile_image?: string;
|
|
328
|
+
bio?: string;
|
|
329
|
+
};
|
|
330
|
+
type SpaceMember = UserProfile & {
|
|
331
|
+
inbox_address: string;
|
|
332
|
+
isKicked?: boolean;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Sync Protocol Types
|
|
337
|
+
*
|
|
338
|
+
* Hash-based delta synchronization for efficient data transfer.
|
|
339
|
+
* Reduces bandwidth by only sending data the recipient is missing.
|
|
340
|
+
*/
|
|
341
|
+
|
|
342
|
+
/** Compact message reference for sync comparison */
|
|
343
|
+
interface MessageDigest {
|
|
344
|
+
/** Unique message identifier */
|
|
345
|
+
messageId: string;
|
|
346
|
+
/** Message creation timestamp */
|
|
347
|
+
createdDate: number;
|
|
348
|
+
/** SHA-256 hash of canonical message content */
|
|
349
|
+
contentHash: string;
|
|
350
|
+
/** Last modification timestamp (for detecting edits) */
|
|
351
|
+
modifiedDate?: number;
|
|
352
|
+
}
|
|
353
|
+
/** Compact reaction reference for sync comparison */
|
|
354
|
+
interface ReactionDigest {
|
|
355
|
+
/** Message this reaction is on */
|
|
356
|
+
messageId: string;
|
|
357
|
+
/** Emoji/reaction identifier */
|
|
358
|
+
emojiId: string;
|
|
359
|
+
/** Number of users who reacted */
|
|
360
|
+
count: number;
|
|
361
|
+
/** Hash of sorted member IDs who reacted */
|
|
362
|
+
membersHash: string;
|
|
363
|
+
}
|
|
364
|
+
/** Manifest of all messages in a channel */
|
|
365
|
+
interface SyncManifest {
|
|
366
|
+
spaceId: string;
|
|
367
|
+
channelId: string;
|
|
368
|
+
messageCount: number;
|
|
369
|
+
oldestTimestamp: number;
|
|
370
|
+
newestTimestamp: number;
|
|
371
|
+
/** Message digests sorted by createdDate ascending */
|
|
372
|
+
digests: MessageDigest[];
|
|
373
|
+
/** Reaction digests for quick comparison */
|
|
374
|
+
reactionDigests: ReactionDigest[];
|
|
375
|
+
}
|
|
376
|
+
/** Delta response containing only needed messages */
|
|
377
|
+
interface MessageDelta {
|
|
378
|
+
spaceId: string;
|
|
379
|
+
channelId: string;
|
|
380
|
+
/** Messages the recipient is missing */
|
|
381
|
+
newMessages: Message[];
|
|
382
|
+
/** Messages that have been edited since recipient's version */
|
|
383
|
+
updatedMessages: Message[];
|
|
384
|
+
/** Message IDs that have been deleted (tombstones) */
|
|
385
|
+
deletedMessageIds: string[];
|
|
386
|
+
}
|
|
387
|
+
/** Delta response for reactions only */
|
|
388
|
+
interface ReactionDelta {
|
|
389
|
+
spaceId: string;
|
|
390
|
+
channelId: string;
|
|
391
|
+
/** Reactions to add: { messageId, emojiId, memberIds[] } */
|
|
392
|
+
added: Array<{
|
|
393
|
+
messageId: string;
|
|
394
|
+
emojiId: string;
|
|
395
|
+
memberIds: string[];
|
|
396
|
+
}>;
|
|
397
|
+
/** Reactions to remove: { messageId, emojiId, memberIds[] } */
|
|
398
|
+
removed: Array<{
|
|
399
|
+
messageId: string;
|
|
400
|
+
emojiId: string;
|
|
401
|
+
memberIds: string[];
|
|
402
|
+
}>;
|
|
403
|
+
}
|
|
404
|
+
/** Compact member reference */
|
|
405
|
+
interface MemberDigest {
|
|
406
|
+
/** User's address */
|
|
407
|
+
address: string;
|
|
408
|
+
/** User's inbox address */
|
|
409
|
+
inboxAddress: string;
|
|
410
|
+
/** SHA-256 hash of display_name (or empty string) */
|
|
411
|
+
displayNameHash: string;
|
|
412
|
+
/** SHA-256 hash of icon URL/data (or empty string) */
|
|
413
|
+
iconHash: string;
|
|
414
|
+
}
|
|
415
|
+
/** Member delta containing only changes */
|
|
416
|
+
interface MemberDelta {
|
|
417
|
+
spaceId: string;
|
|
418
|
+
/** New or updated members (full data) */
|
|
419
|
+
members: SpaceMember[];
|
|
420
|
+
/** Addresses of removed/kicked members */
|
|
421
|
+
removedAddresses: string[];
|
|
422
|
+
}
|
|
423
|
+
/** Peer map entry for Triple Ratchet */
|
|
424
|
+
interface PeerEntry {
|
|
425
|
+
/** Peer's ID in the ratchet */
|
|
426
|
+
peerId: number;
|
|
427
|
+
/** Peer's public key (hex or base64) */
|
|
428
|
+
publicKey: string;
|
|
429
|
+
/** Peer's identity public key (hex or base64) - optional for minimal sync */
|
|
430
|
+
identityPublicKey?: string;
|
|
431
|
+
/** Peer's signed pre-key (hex or base64) - optional for minimal sync */
|
|
432
|
+
signedPrePublicKey?: string;
|
|
433
|
+
}
|
|
434
|
+
/** Peer map delta */
|
|
435
|
+
interface PeerMapDelta {
|
|
436
|
+
spaceId: string;
|
|
437
|
+
/** New peer entries */
|
|
438
|
+
added: PeerEntry[];
|
|
439
|
+
/** Updated peer entries (changed keys) */
|
|
440
|
+
updated: PeerEntry[];
|
|
441
|
+
/** Removed peer IDs */
|
|
442
|
+
removed: number[];
|
|
443
|
+
}
|
|
444
|
+
/** Tombstone for a deleted message */
|
|
445
|
+
interface DeletedMessageTombstone {
|
|
446
|
+
messageId: string;
|
|
447
|
+
spaceId: string;
|
|
448
|
+
channelId: string;
|
|
449
|
+
deletedAt: number;
|
|
450
|
+
}
|
|
451
|
+
/** Summary of sync data for comparison */
|
|
452
|
+
interface SyncSummary {
|
|
453
|
+
memberCount: number;
|
|
454
|
+
messageCount: number;
|
|
455
|
+
newestMessageTimestamp: number;
|
|
456
|
+
oldestMessageTimestamp: number;
|
|
457
|
+
/** Hash of all message IDs for quick comparison */
|
|
458
|
+
manifestHash?: string;
|
|
459
|
+
}
|
|
460
|
+
/** sync-request payload (broadcast via hub) */
|
|
461
|
+
interface SyncRequestPayload {
|
|
462
|
+
type: 'sync-request';
|
|
463
|
+
/** Our inbox address for responses */
|
|
464
|
+
inboxAddress: string;
|
|
465
|
+
/** Request expiry timestamp */
|
|
466
|
+
expiry: number;
|
|
467
|
+
/** Summary of our data */
|
|
468
|
+
summary: SyncSummary;
|
|
469
|
+
}
|
|
470
|
+
/** sync-info payload (direct to requester) */
|
|
471
|
+
interface SyncInfoPayload {
|
|
472
|
+
type: 'sync-info';
|
|
473
|
+
/** Our inbox address */
|
|
474
|
+
inboxAddress: string;
|
|
475
|
+
/** Summary of our data */
|
|
476
|
+
summary: SyncSummary;
|
|
477
|
+
}
|
|
478
|
+
/** sync-initiate payload (direct to best peer) */
|
|
479
|
+
interface SyncInitiatePayload {
|
|
480
|
+
type: 'sync-initiate';
|
|
481
|
+
/** Our inbox address (for peer to send data back to) */
|
|
482
|
+
inboxAddress: string;
|
|
483
|
+
/** Our manifest for comparison */
|
|
484
|
+
manifest?: SyncManifest;
|
|
485
|
+
/** Our member digests for comparison */
|
|
486
|
+
memberDigests?: MemberDigest[];
|
|
487
|
+
/** Our peer IDs for comparison */
|
|
488
|
+
peerIds?: number[];
|
|
489
|
+
}
|
|
490
|
+
/** sync-manifest payload (response to sync-initiate with our data summary) */
|
|
491
|
+
interface SyncManifestPayload {
|
|
492
|
+
type: 'sync-manifest';
|
|
493
|
+
/** Our inbox address (for peer to send data back to) */
|
|
494
|
+
inboxAddress: string;
|
|
495
|
+
/** Our full manifest */
|
|
496
|
+
manifest: SyncManifest;
|
|
497
|
+
/** Our member digests */
|
|
498
|
+
memberDigests: MemberDigest[];
|
|
499
|
+
/** Our peer IDs */
|
|
500
|
+
peerIds: number[];
|
|
501
|
+
}
|
|
502
|
+
/** sync-delta payload (actual data transfer) */
|
|
503
|
+
interface SyncDeltaPayload {
|
|
504
|
+
type: 'sync-delta';
|
|
505
|
+
/** Message changes */
|
|
506
|
+
messageDelta?: MessageDelta;
|
|
507
|
+
/** Reaction changes (synced independently) */
|
|
508
|
+
reactionDelta?: ReactionDelta;
|
|
509
|
+
/** Member changes */
|
|
510
|
+
memberDelta?: MemberDelta;
|
|
511
|
+
/** Peer map changes */
|
|
512
|
+
peerMapDelta?: PeerMapDelta;
|
|
513
|
+
/** Whether this is the final delta chunk */
|
|
514
|
+
isFinal?: boolean;
|
|
515
|
+
}
|
|
516
|
+
/** Candidate from sync-info response */
|
|
517
|
+
interface SyncCandidate {
|
|
518
|
+
inboxAddress: string;
|
|
519
|
+
summary: SyncSummary;
|
|
520
|
+
}
|
|
521
|
+
/** Active sync session state */
|
|
522
|
+
interface SyncSession {
|
|
523
|
+
spaceId: string;
|
|
524
|
+
channelId: string;
|
|
525
|
+
/** Request expiry timestamp */
|
|
526
|
+
expiry: number;
|
|
527
|
+
/** Candidates who responded to sync-request */
|
|
528
|
+
candidates: SyncCandidate[];
|
|
529
|
+
/** Timeout handle for initiating sync */
|
|
530
|
+
timeout?: ReturnType<typeof setTimeout>;
|
|
531
|
+
/** Whether sync is in progress */
|
|
532
|
+
inProgress: boolean;
|
|
533
|
+
/** Target inbox address when we've initiated sync */
|
|
534
|
+
syncTarget?: string;
|
|
535
|
+
}
|
|
536
|
+
/** All sync control message payloads */
|
|
537
|
+
type SyncControlPayload = SyncRequestPayload | SyncInfoPayload | SyncInitiatePayload | SyncManifestPayload | SyncDeltaPayload;
|
|
538
|
+
/** Type guard for sync-request */
|
|
539
|
+
declare function isSyncRequest(payload: SyncControlPayload): payload is SyncRequestPayload;
|
|
540
|
+
/** Type guard for sync-info */
|
|
541
|
+
declare function isSyncInfo(payload: SyncControlPayload): payload is SyncInfoPayload;
|
|
542
|
+
/** Type guard for sync-initiate */
|
|
543
|
+
declare function isSyncInitiate(payload: SyncControlPayload): payload is SyncInitiatePayload;
|
|
544
|
+
/** Type guard for sync-manifest */
|
|
545
|
+
declare function isSyncManifest(payload: SyncControlPayload): payload is SyncManifestPayload;
|
|
546
|
+
/** Type guard for sync-delta */
|
|
547
|
+
declare function isSyncDelta(payload: SyncControlPayload): payload is SyncDeltaPayload;
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* StorageAdapter interface
|
|
551
|
+
*
|
|
552
|
+
* Platform-agnostic storage interface that can be implemented by:
|
|
553
|
+
* - IndexedDB (desktop/web)
|
|
554
|
+
* - MMKV (React Native mobile)
|
|
555
|
+
*/
|
|
556
|
+
|
|
557
|
+
interface GetMessagesParams {
|
|
558
|
+
spaceId: string;
|
|
559
|
+
channelId: string;
|
|
560
|
+
cursor?: number;
|
|
561
|
+
direction?: 'forward' | 'backward';
|
|
562
|
+
limit?: number;
|
|
563
|
+
}
|
|
564
|
+
interface GetMessagesResult {
|
|
565
|
+
messages: Message[];
|
|
566
|
+
nextCursor: number | null;
|
|
567
|
+
prevCursor: number | null;
|
|
568
|
+
}
|
|
569
|
+
interface StorageAdapter {
|
|
570
|
+
init(): Promise<void>;
|
|
571
|
+
getSpaces(): Promise<Space[]>;
|
|
572
|
+
getSpace(spaceId: string): Promise<Space | null>;
|
|
573
|
+
saveSpace(space: Space): Promise<void>;
|
|
574
|
+
deleteSpace(spaceId: string): Promise<void>;
|
|
575
|
+
getChannels(spaceId: string): Promise<Channel[]>;
|
|
576
|
+
getMessages(params: GetMessagesParams): Promise<GetMessagesResult>;
|
|
577
|
+
getMessage(params: {
|
|
578
|
+
spaceId: string;
|
|
579
|
+
channelId: string;
|
|
580
|
+
messageId: string;
|
|
581
|
+
}): Promise<Message | undefined>;
|
|
582
|
+
saveMessage(message: Message, lastMessageTimestamp: number, address: string, conversationType: string, icon: string, displayName: string): Promise<void>;
|
|
583
|
+
deleteMessage(messageId: string): Promise<void>;
|
|
584
|
+
getConversations(params: {
|
|
585
|
+
type: 'direct' | 'group';
|
|
586
|
+
cursor?: number;
|
|
587
|
+
limit?: number;
|
|
588
|
+
}): Promise<{
|
|
589
|
+
conversations: Conversation[];
|
|
590
|
+
nextCursor: number | null;
|
|
591
|
+
}>;
|
|
592
|
+
getConversation(conversationId: string): Promise<Conversation | undefined>;
|
|
593
|
+
saveConversation(conversation: Conversation): Promise<void>;
|
|
594
|
+
deleteConversation(conversationId: string): Promise<void>;
|
|
595
|
+
getUserConfig(address: string): Promise<UserConfig | undefined>;
|
|
596
|
+
saveUserConfig(userConfig: UserConfig): Promise<void>;
|
|
597
|
+
getSpaceMembers(spaceId: string): Promise<SpaceMember[]>;
|
|
598
|
+
getSpaceMember(spaceId: string, address: string): Promise<SpaceMember | undefined>;
|
|
599
|
+
saveSpaceMember(spaceId: string, member: SpaceMember): Promise<void>;
|
|
600
|
+
getLastSyncTime(key: string): Promise<number | undefined>;
|
|
601
|
+
setLastSyncTime(key: string, time: number): Promise<void>;
|
|
602
|
+
/**
|
|
603
|
+
* Get message digests for efficient sync comparison.
|
|
604
|
+
* If not implemented, returns undefined and SyncService computes from messages.
|
|
605
|
+
*/
|
|
606
|
+
getMessageDigests?(spaceId: string, channelId: string): Promise<MessageDigest[] | undefined>;
|
|
607
|
+
/**
|
|
608
|
+
* Get messages by IDs for delta sync.
|
|
609
|
+
* If not implemented, returns undefined and caller fetches individually.
|
|
610
|
+
*/
|
|
611
|
+
getMessagesByIds?(spaceId: string, channelId: string, ids: string[]): Promise<Message[] | undefined>;
|
|
612
|
+
/**
|
|
613
|
+
* Get member digests for efficient sync comparison.
|
|
614
|
+
* If not implemented, returns undefined and SyncService computes from members.
|
|
615
|
+
*/
|
|
616
|
+
getMemberDigests?(spaceId: string): Promise<MemberDigest[] | undefined>;
|
|
617
|
+
/**
|
|
618
|
+
* Get deleted message tombstones for sync.
|
|
619
|
+
*/
|
|
620
|
+
getTombstones?(spaceId: string, channelId: string): Promise<DeletedMessageTombstone[]>;
|
|
621
|
+
/**
|
|
622
|
+
* Save deleted message tombstone.
|
|
623
|
+
*/
|
|
624
|
+
saveTombstone?(tombstone: DeletedMessageTombstone): Promise<void>;
|
|
625
|
+
/**
|
|
626
|
+
* Clean up old tombstones.
|
|
627
|
+
*/
|
|
628
|
+
cleanupTombstones?(maxAgeMs: number): Promise<void>;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* QuorumApiClient interface
|
|
633
|
+
*
|
|
634
|
+
* Platform-agnostic API client that can be implemented differently
|
|
635
|
+
* for mobile (fetch) and desktop (Electron IPC or fetch)
|
|
636
|
+
*/
|
|
637
|
+
|
|
638
|
+
interface SendMessageParams {
|
|
639
|
+
spaceId: string;
|
|
640
|
+
channelId: string;
|
|
641
|
+
text: string;
|
|
642
|
+
repliesToMessageId?: string;
|
|
643
|
+
}
|
|
644
|
+
interface AddReactionParams {
|
|
645
|
+
spaceId: string;
|
|
646
|
+
channelId: string;
|
|
647
|
+
messageId: string;
|
|
648
|
+
reaction: string;
|
|
649
|
+
}
|
|
650
|
+
interface RemoveReactionParams {
|
|
651
|
+
spaceId: string;
|
|
652
|
+
channelId: string;
|
|
653
|
+
messageId: string;
|
|
654
|
+
reaction: string;
|
|
655
|
+
}
|
|
656
|
+
interface EditMessageParams {
|
|
657
|
+
spaceId: string;
|
|
658
|
+
channelId: string;
|
|
659
|
+
messageId: string;
|
|
660
|
+
text: string;
|
|
661
|
+
}
|
|
662
|
+
interface DeleteMessageParams {
|
|
663
|
+
spaceId: string;
|
|
664
|
+
channelId: string;
|
|
665
|
+
messageId: string;
|
|
666
|
+
}
|
|
667
|
+
interface SendDirectMessageParams {
|
|
668
|
+
conversationId: string;
|
|
669
|
+
text: string;
|
|
670
|
+
repliesToMessageId?: string;
|
|
671
|
+
}
|
|
672
|
+
interface QuorumApiClient {
|
|
673
|
+
fetchSpaces(): Promise<Space[]>;
|
|
674
|
+
fetchSpace(spaceId: string): Promise<Space>;
|
|
675
|
+
joinSpace(inviteCode: string): Promise<Space>;
|
|
676
|
+
fetchMessages(params: {
|
|
677
|
+
spaceId: string;
|
|
678
|
+
channelId: string;
|
|
679
|
+
cursor?: string;
|
|
680
|
+
limit?: number;
|
|
681
|
+
}): Promise<{
|
|
682
|
+
messages: Message[];
|
|
683
|
+
nextPageToken?: string;
|
|
684
|
+
}>;
|
|
685
|
+
sendMessage(params: SendMessageParams): Promise<Message>;
|
|
686
|
+
editMessage(params: EditMessageParams): Promise<Message>;
|
|
687
|
+
deleteMessage(params: DeleteMessageParams): Promise<void>;
|
|
688
|
+
addReaction(params: AddReactionParams): Promise<void>;
|
|
689
|
+
removeReaction(params: RemoveReactionParams): Promise<void>;
|
|
690
|
+
fetchConversations(): Promise<Conversation[]>;
|
|
691
|
+
createConversation(params: {
|
|
692
|
+
address: string;
|
|
693
|
+
}): Promise<Conversation>;
|
|
694
|
+
sendDirectMessage(params: SendDirectMessageParams): Promise<Message>;
|
|
695
|
+
fetchDirectMessages(params: {
|
|
696
|
+
conversationId: string;
|
|
697
|
+
cursor?: string;
|
|
698
|
+
limit?: number;
|
|
699
|
+
}): Promise<{
|
|
700
|
+
messages: Message[];
|
|
701
|
+
nextPageToken?: string;
|
|
702
|
+
}>;
|
|
703
|
+
pinMessage(params: {
|
|
704
|
+
spaceId: string;
|
|
705
|
+
channelId: string;
|
|
706
|
+
messageId: string;
|
|
707
|
+
}): Promise<void>;
|
|
708
|
+
unpinMessage(params: {
|
|
709
|
+
spaceId: string;
|
|
710
|
+
channelId: string;
|
|
711
|
+
messageId: string;
|
|
712
|
+
}): Promise<void>;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* API error classes
|
|
717
|
+
*/
|
|
718
|
+
/** API error codes */
|
|
719
|
+
declare enum ApiErrorCode {
|
|
720
|
+
NETWORK_ERROR = "NETWORK_ERROR",
|
|
721
|
+
TIMEOUT = "TIMEOUT",
|
|
722
|
+
UNAUTHORIZED = "UNAUTHORIZED",
|
|
723
|
+
FORBIDDEN = "FORBIDDEN",
|
|
724
|
+
TOKEN_EXPIRED = "TOKEN_EXPIRED",
|
|
725
|
+
VALIDATION_ERROR = "VALIDATION_ERROR",
|
|
726
|
+
BAD_REQUEST = "BAD_REQUEST",
|
|
727
|
+
NOT_FOUND = "NOT_FOUND",
|
|
728
|
+
CONFLICT = "CONFLICT",
|
|
729
|
+
RATE_LIMITED = "RATE_LIMITED",
|
|
730
|
+
SERVER_ERROR = "SERVER_ERROR",
|
|
731
|
+
SERVICE_UNAVAILABLE = "SERVICE_UNAVAILABLE",
|
|
732
|
+
UNKNOWN = "UNKNOWN"
|
|
733
|
+
}
|
|
734
|
+
/** API error details */
|
|
735
|
+
interface ApiErrorDetails {
|
|
736
|
+
code: ApiErrorCode;
|
|
737
|
+
message: string;
|
|
738
|
+
status?: number;
|
|
739
|
+
field?: string;
|
|
740
|
+
retryAfter?: number;
|
|
741
|
+
originalError?: Error;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Custom API error class
|
|
745
|
+
*/
|
|
746
|
+
declare class ApiError extends Error {
|
|
747
|
+
readonly code: ApiErrorCode;
|
|
748
|
+
readonly status?: number;
|
|
749
|
+
readonly field?: string;
|
|
750
|
+
readonly retryAfter?: number;
|
|
751
|
+
readonly originalError?: Error;
|
|
752
|
+
constructor(details: ApiErrorDetails);
|
|
753
|
+
/** Check if error is retryable */
|
|
754
|
+
get isRetryable(): boolean;
|
|
755
|
+
/** Check if error requires re-authentication */
|
|
756
|
+
get requiresAuth(): boolean;
|
|
757
|
+
/** Convert to JSON-serializable object */
|
|
758
|
+
toJSON(): ApiErrorDetails;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Create ApiError from HTTP response
|
|
762
|
+
*/
|
|
763
|
+
declare function createApiError(status: number, message?: string, field?: string): ApiError;
|
|
764
|
+
/**
|
|
765
|
+
* Create ApiError from network error
|
|
766
|
+
*/
|
|
767
|
+
declare function createNetworkError(error: Error): ApiError;
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* API endpoint definitions
|
|
771
|
+
*/
|
|
772
|
+
/** Base API configuration */
|
|
773
|
+
interface ApiConfig {
|
|
774
|
+
baseUrl: string;
|
|
775
|
+
timeout?: number;
|
|
776
|
+
}
|
|
777
|
+
/** API endpoint builders */
|
|
778
|
+
declare const endpoints: {
|
|
779
|
+
readonly spaces: {
|
|
780
|
+
readonly list: () => string;
|
|
781
|
+
readonly detail: (spaceId: string) => string;
|
|
782
|
+
readonly members: (spaceId: string) => string;
|
|
783
|
+
readonly join: (spaceId: string) => string;
|
|
784
|
+
readonly leave: (spaceId: string) => string;
|
|
785
|
+
};
|
|
786
|
+
readonly channels: {
|
|
787
|
+
readonly list: (spaceId: string) => string;
|
|
788
|
+
readonly detail: (spaceId: string, channelId: string) => string;
|
|
789
|
+
};
|
|
790
|
+
readonly messages: {
|
|
791
|
+
readonly list: (spaceId: string, channelId: string) => string;
|
|
792
|
+
readonly detail: (spaceId: string, channelId: string, messageId: string) => string;
|
|
793
|
+
readonly send: (spaceId: string, channelId: string) => string;
|
|
794
|
+
readonly react: (spaceId: string, channelId: string, messageId: string) => string;
|
|
795
|
+
readonly pin: (spaceId: string, channelId: string, messageId: string) => string;
|
|
796
|
+
};
|
|
797
|
+
readonly conversations: {
|
|
798
|
+
readonly list: () => string;
|
|
799
|
+
readonly detail: (conversationId: string) => string;
|
|
800
|
+
readonly messages: (conversationId: string) => string;
|
|
801
|
+
};
|
|
802
|
+
readonly user: {
|
|
803
|
+
readonly config: () => string;
|
|
804
|
+
readonly profile: () => string;
|
|
805
|
+
readonly notifications: () => string;
|
|
806
|
+
};
|
|
807
|
+
readonly search: {
|
|
808
|
+
readonly messages: (spaceId: string) => string;
|
|
809
|
+
readonly members: (spaceId: string) => string;
|
|
810
|
+
};
|
|
811
|
+
};
|
|
812
|
+
/** HTTP methods */
|
|
813
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
814
|
+
/** Request options */
|
|
815
|
+
interface RequestOptions {
|
|
816
|
+
method?: HttpMethod;
|
|
817
|
+
headers?: Record<string, string>;
|
|
818
|
+
body?: unknown;
|
|
819
|
+
timeout?: number;
|
|
820
|
+
signal?: AbortSignal;
|
|
821
|
+
}
|
|
822
|
+
/** Pagination parameters */
|
|
823
|
+
interface PaginationParams {
|
|
824
|
+
cursor?: string;
|
|
825
|
+
limit?: number;
|
|
826
|
+
}
|
|
827
|
+
/** Message list parameters */
|
|
828
|
+
interface MessageListParams extends PaginationParams {
|
|
829
|
+
before?: string;
|
|
830
|
+
after?: string;
|
|
831
|
+
around?: string;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Query key factories for React Query
|
|
836
|
+
*
|
|
837
|
+
* Consistent key structure across platforms for cache management
|
|
838
|
+
*/
|
|
839
|
+
declare const queryKeys: {
|
|
840
|
+
readonly spaces: {
|
|
841
|
+
readonly all: readonly ["spaces"];
|
|
842
|
+
readonly detail: (spaceId: string) => readonly ["spaces", string];
|
|
843
|
+
readonly members: (spaceId: string) => readonly ["spaces", string, "members"];
|
|
844
|
+
readonly member: (spaceId: string, address: string) => readonly ["spaces", string, "members", string];
|
|
845
|
+
};
|
|
846
|
+
readonly channels: {
|
|
847
|
+
readonly bySpace: (spaceId: string) => readonly ["channels", string];
|
|
848
|
+
readonly detail: (spaceId: string, channelId: string) => readonly ["channels", string, string];
|
|
849
|
+
};
|
|
850
|
+
readonly messages: {
|
|
851
|
+
readonly infinite: (spaceId: string, channelId: string) => readonly ["messages", "infinite", string, string];
|
|
852
|
+
readonly detail: (spaceId: string, channelId: string, messageId: string) => readonly ["messages", string, string, string];
|
|
853
|
+
readonly pinned: (spaceId: string, channelId: string) => readonly ["messages", "pinned", string, string];
|
|
854
|
+
};
|
|
855
|
+
readonly conversations: {
|
|
856
|
+
readonly all: (type: "direct" | "group") => readonly ["conversations", "direct" | "group"];
|
|
857
|
+
readonly detail: (conversationId: string) => readonly ["conversations", string];
|
|
858
|
+
readonly messages: (conversationId: string) => readonly ["conversations", string, "messages"];
|
|
859
|
+
};
|
|
860
|
+
readonly user: {
|
|
861
|
+
readonly config: (address: string) => readonly ["user", "config", string];
|
|
862
|
+
readonly profile: (address: string) => readonly ["user", "profile", string];
|
|
863
|
+
};
|
|
864
|
+
readonly bookmarks: {
|
|
865
|
+
readonly all: readonly ["bookmarks"];
|
|
866
|
+
readonly bySource: (sourceType: "channel" | "dm") => readonly ["bookmarks", "channel" | "dm"];
|
|
867
|
+
readonly bySpace: (spaceId: string) => readonly ["bookmarks", "space", string];
|
|
868
|
+
readonly check: (messageId: string) => readonly ["bookmarks", "check", string];
|
|
869
|
+
};
|
|
870
|
+
};
|
|
871
|
+
type SpacesKey = typeof queryKeys.spaces.all;
|
|
872
|
+
type SpaceDetailKey = ReturnType<typeof queryKeys.spaces.detail>;
|
|
873
|
+
type ChannelsKey = ReturnType<typeof queryKeys.channels.bySpace>;
|
|
874
|
+
type MessagesInfiniteKey = ReturnType<typeof queryKeys.messages.infinite>;
|
|
875
|
+
|
|
876
|
+
interface UseSpacesOptions {
|
|
877
|
+
storage: StorageAdapter;
|
|
878
|
+
enabled?: boolean;
|
|
879
|
+
}
|
|
880
|
+
declare function useSpaces({ storage, enabled }: UseSpacesOptions): _tanstack_react_query.UseQueryResult<Space[], Error>;
|
|
881
|
+
interface UseSpaceOptions {
|
|
882
|
+
storage: StorageAdapter;
|
|
883
|
+
spaceId: string | undefined;
|
|
884
|
+
enabled?: boolean;
|
|
885
|
+
}
|
|
886
|
+
declare function useSpace({ storage, spaceId, enabled }: UseSpaceOptions): _tanstack_react_query.UseQueryResult<Space | null, Error>;
|
|
887
|
+
declare function useSpaceMembers({ storage, spaceId, enabled, }: UseSpaceOptions): _tanstack_react_query.UseQueryResult<SpaceMember[], Error>;
|
|
888
|
+
|
|
889
|
+
interface UseChannelsOptions {
|
|
890
|
+
storage: StorageAdapter;
|
|
891
|
+
spaceId: string | undefined;
|
|
892
|
+
enabled?: boolean;
|
|
893
|
+
}
|
|
894
|
+
declare function useChannels({ storage, spaceId, enabled }: UseChannelsOptions): _tanstack_react_query.UseQueryResult<Channel[], Error>;
|
|
895
|
+
/**
|
|
896
|
+
* Helper to extract channels from a space's groups
|
|
897
|
+
*/
|
|
898
|
+
declare function flattenChannels(groups: {
|
|
899
|
+
channels: Channel[];
|
|
900
|
+
}[]): Channel[];
|
|
901
|
+
/**
|
|
902
|
+
* Find a channel by ID within groups
|
|
903
|
+
*/
|
|
904
|
+
declare function findChannel(groups: {
|
|
905
|
+
channels: Channel[];
|
|
906
|
+
}[], channelId: string): Channel | undefined;
|
|
907
|
+
|
|
908
|
+
interface UseMessagesOptions {
|
|
909
|
+
storage: StorageAdapter;
|
|
910
|
+
spaceId: string | undefined;
|
|
911
|
+
channelId: string | undefined;
|
|
912
|
+
enabled?: boolean;
|
|
913
|
+
limit?: number;
|
|
914
|
+
}
|
|
915
|
+
declare function useMessages({ storage, spaceId, channelId, enabled, limit, }: UseMessagesOptions): _tanstack_react_query.UseInfiniteQueryResult<_tanstack_query_core.InfiniteData<GetMessagesResult, unknown>, Error>;
|
|
916
|
+
/**
|
|
917
|
+
* Flatten paginated messages into a single array
|
|
918
|
+
*/
|
|
919
|
+
declare function flattenMessages(pages: GetMessagesResult[] | undefined): Message[];
|
|
920
|
+
/**
|
|
921
|
+
* Hook to invalidate message cache
|
|
922
|
+
*/
|
|
923
|
+
declare function useInvalidateMessages(): {
|
|
924
|
+
invalidateChannel: (spaceId: string, channelId: string) => void;
|
|
925
|
+
invalidateSpace: (spaceId: string) => void;
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
interface UseSendMessageOptions {
|
|
929
|
+
storage: StorageAdapter;
|
|
930
|
+
apiClient: QuorumApiClient;
|
|
931
|
+
currentUserId: string;
|
|
932
|
+
}
|
|
933
|
+
declare function useSendMessage({ storage, apiClient, currentUserId, }: UseSendMessageOptions): _tanstack_react_query.UseMutationResult<Message, Error, SendMessageParams, {
|
|
934
|
+
previousData: unknown;
|
|
935
|
+
optimisticMessage: Message;
|
|
936
|
+
}>;
|
|
937
|
+
|
|
938
|
+
interface UseReactionOptions {
|
|
939
|
+
storage: StorageAdapter;
|
|
940
|
+
apiClient: QuorumApiClient;
|
|
941
|
+
currentUserId: string;
|
|
942
|
+
}
|
|
943
|
+
declare function useAddReaction({ storage, apiClient, currentUserId, }: UseReactionOptions): _tanstack_react_query.UseMutationResult<void, Error, AddReactionParams, {
|
|
944
|
+
previousData: unknown;
|
|
945
|
+
}>;
|
|
946
|
+
declare function useRemoveReaction({ storage, apiClient, currentUserId, }: UseReactionOptions): _tanstack_react_query.UseMutationResult<void, Error, RemoveReactionParams, {
|
|
947
|
+
previousData: unknown;
|
|
948
|
+
}>;
|
|
949
|
+
|
|
950
|
+
interface UseEditMessageOptions {
|
|
951
|
+
storage: StorageAdapter;
|
|
952
|
+
apiClient: QuorumApiClient;
|
|
953
|
+
}
|
|
954
|
+
declare function useEditMessage({ storage, apiClient }: UseEditMessageOptions): _tanstack_react_query.UseMutationResult<Message, Error, EditMessageParams, {
|
|
955
|
+
previousData: unknown;
|
|
956
|
+
}>;
|
|
957
|
+
|
|
958
|
+
interface UseDeleteMessageOptions {
|
|
959
|
+
storage: StorageAdapter;
|
|
960
|
+
apiClient: QuorumApiClient;
|
|
961
|
+
}
|
|
962
|
+
declare function useDeleteMessage({ storage, apiClient }: UseDeleteMessageOptions): _tanstack_react_query.UseMutationResult<void, Error, DeleteMessageParams, {
|
|
963
|
+
previousData: unknown;
|
|
964
|
+
}>;
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Message validation utilities
|
|
968
|
+
*/
|
|
969
|
+
|
|
970
|
+
/** Maximum message length */
|
|
971
|
+
declare const MAX_MESSAGE_LENGTH = 4000;
|
|
972
|
+
/** Maximum number of mentions per message */
|
|
973
|
+
declare const MAX_MENTIONS = 50;
|
|
974
|
+
/** Validation result */
|
|
975
|
+
interface ValidationResult {
|
|
976
|
+
valid: boolean;
|
|
977
|
+
errors: string[];
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Validate message content before sending
|
|
981
|
+
*/
|
|
982
|
+
declare function validateMessageContent(content: string): ValidationResult;
|
|
983
|
+
/**
|
|
984
|
+
* Validate a message object
|
|
985
|
+
*/
|
|
986
|
+
declare function validateMessage(message: Partial<Message>): ValidationResult;
|
|
987
|
+
/**
|
|
988
|
+
* Sanitize message content for display
|
|
989
|
+
*/
|
|
990
|
+
declare function sanitizeContent(content: string): string;
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Mention parsing utilities
|
|
994
|
+
*/
|
|
995
|
+
|
|
996
|
+
/** Mention patterns */
|
|
997
|
+
declare const MENTION_PATTERNS: {
|
|
998
|
+
user: RegExp;
|
|
999
|
+
role: RegExp;
|
|
1000
|
+
channel: RegExp;
|
|
1001
|
+
everyone: RegExp;
|
|
1002
|
+
here: RegExp;
|
|
1003
|
+
};
|
|
1004
|
+
/** Parsed mention */
|
|
1005
|
+
interface ParsedMention {
|
|
1006
|
+
type: 'user' | 'role' | 'channel' | 'everyone' | 'here';
|
|
1007
|
+
id?: string;
|
|
1008
|
+
raw: string;
|
|
1009
|
+
start: number;
|
|
1010
|
+
end: number;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Parse mentions from message text
|
|
1014
|
+
*/
|
|
1015
|
+
declare function parseMentions(text: string): ParsedMention[];
|
|
1016
|
+
/**
|
|
1017
|
+
* Extract Mentions object from parsed mentions
|
|
1018
|
+
*/
|
|
1019
|
+
declare function extractMentions(text: string): Mentions;
|
|
1020
|
+
/**
|
|
1021
|
+
* Format mention for display
|
|
1022
|
+
*/
|
|
1023
|
+
declare function formatMention(type: 'user' | 'role' | 'channel', id: string): string;
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Date and text formatting utilities
|
|
1027
|
+
*/
|
|
1028
|
+
/**
|
|
1029
|
+
* Format timestamp to time string (e.g., "2:30 PM")
|
|
1030
|
+
*/
|
|
1031
|
+
declare function formatTime(timestamp: number): string;
|
|
1032
|
+
/**
|
|
1033
|
+
* Format timestamp to date string (e.g., "Dec 24, 2025")
|
|
1034
|
+
*/
|
|
1035
|
+
declare function formatDate(timestamp: number): string;
|
|
1036
|
+
/**
|
|
1037
|
+
* Format timestamp to full datetime string (e.g., "Dec 24, 2025 at 2:30 PM")
|
|
1038
|
+
*/
|
|
1039
|
+
declare function formatDateTime(timestamp: number): string;
|
|
1040
|
+
/**
|
|
1041
|
+
* Format timestamp to relative time (e.g., "5 minutes ago", "Yesterday")
|
|
1042
|
+
*/
|
|
1043
|
+
declare function formatRelativeTime(timestamp: number): string;
|
|
1044
|
+
/**
|
|
1045
|
+
* Format message date header (e.g., "Today", "Yesterday", "December 24, 2025")
|
|
1046
|
+
*/
|
|
1047
|
+
declare function formatMessageDate(timestamp: number): string;
|
|
1048
|
+
/**
|
|
1049
|
+
* Check if two dates are the same day
|
|
1050
|
+
*/
|
|
1051
|
+
declare function isSameDay(d1: Date, d2: Date): boolean;
|
|
1052
|
+
/**
|
|
1053
|
+
* Truncate text to a maximum length with ellipsis
|
|
1054
|
+
*/
|
|
1055
|
+
declare function truncateText(text: string, maxLength: number): string;
|
|
1056
|
+
/**
|
|
1057
|
+
* Format file size (e.g., "1.5 MB")
|
|
1058
|
+
*/
|
|
1059
|
+
declare function formatFileSize(bytes: number): string;
|
|
1060
|
+
/**
|
|
1061
|
+
* Format member count (e.g., "1.2K members")
|
|
1062
|
+
*/
|
|
1063
|
+
declare function formatMemberCount(count: number): string;
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Encoding utilities for hex and base64 conversions
|
|
1067
|
+
*
|
|
1068
|
+
* These are platform-agnostic utilities for converting between
|
|
1069
|
+
* byte arrays and string representations.
|
|
1070
|
+
*/
|
|
1071
|
+
/**
|
|
1072
|
+
* Convert a hex string to a byte array
|
|
1073
|
+
* @param hex - Hexadecimal string (with or without 0x prefix)
|
|
1074
|
+
* @returns Array of bytes
|
|
1075
|
+
*/
|
|
1076
|
+
declare function hexToBytes(hex: string): number[];
|
|
1077
|
+
/**
|
|
1078
|
+
* Convert a byte array to a hex string
|
|
1079
|
+
* @param bytes - Array of bytes (number[] or Uint8Array)
|
|
1080
|
+
* @returns Hexadecimal string (lowercase, no prefix)
|
|
1081
|
+
*/
|
|
1082
|
+
declare function bytesToHex(bytes: number[] | Uint8Array): string;
|
|
1083
|
+
/**
|
|
1084
|
+
* Convert a base64 string to a Uint8Array
|
|
1085
|
+
* @param base64 - Base64-encoded string
|
|
1086
|
+
* @returns Uint8Array of bytes
|
|
1087
|
+
*/
|
|
1088
|
+
declare function base64ToBytes(base64: string): Uint8Array;
|
|
1089
|
+
/**
|
|
1090
|
+
* Convert a byte array to a base64 string
|
|
1091
|
+
* @param bytes - Array of bytes (number[] or Uint8Array)
|
|
1092
|
+
* @returns Base64-encoded string
|
|
1093
|
+
*/
|
|
1094
|
+
declare function bytesToBase64(bytes: number[] | Uint8Array): string;
|
|
1095
|
+
/**
|
|
1096
|
+
* Convert a UTF-8 string to a byte array
|
|
1097
|
+
* @param str - UTF-8 string
|
|
1098
|
+
* @returns Array of bytes
|
|
1099
|
+
*/
|
|
1100
|
+
declare function stringToBytes(str: string): number[];
|
|
1101
|
+
/**
|
|
1102
|
+
* Convert a byte array to a UTF-8 string
|
|
1103
|
+
* @param bytes - Array of bytes (number[] or Uint8Array)
|
|
1104
|
+
* @returns UTF-8 string
|
|
1105
|
+
*/
|
|
1106
|
+
declare function bytesToString(bytes: number[] | Uint8Array): string;
|
|
1107
|
+
/**
|
|
1108
|
+
* Convert a number to an 8-byte big-endian array (int64)
|
|
1109
|
+
* Used for timestamp serialization in signatures
|
|
1110
|
+
*
|
|
1111
|
+
* @param num - The number to convert (must be within safe integer range)
|
|
1112
|
+
* @returns 8-byte Uint8Array in big-endian format
|
|
1113
|
+
*/
|
|
1114
|
+
declare function int64ToBytes(num: number): Uint8Array;
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Logger utility that respects build environment
|
|
1118
|
+
*
|
|
1119
|
+
* In development: logs to console
|
|
1120
|
+
* In production: no-ops for performance
|
|
1121
|
+
*
|
|
1122
|
+
* Usage:
|
|
1123
|
+
* import { logger } from '@quorum/shared';
|
|
1124
|
+
* logger.log('[MyModule]', 'some message', data);
|
|
1125
|
+
* logger.warn('[MyModule]', 'warning message');
|
|
1126
|
+
* logger.error('[MyModule]', 'error message', error);
|
|
1127
|
+
*/
|
|
1128
|
+
type LogLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
|
|
1129
|
+
interface LoggerConfig {
|
|
1130
|
+
enabled: boolean;
|
|
1131
|
+
minLevel: LogLevel;
|
|
1132
|
+
}
|
|
1133
|
+
declare const logger: {
|
|
1134
|
+
/**
|
|
1135
|
+
* Configure the logger
|
|
1136
|
+
*/
|
|
1137
|
+
configure(newConfig: Partial<LoggerConfig>): void;
|
|
1138
|
+
/**
|
|
1139
|
+
* Check if logging is enabled
|
|
1140
|
+
*/
|
|
1141
|
+
isEnabled(): boolean;
|
|
1142
|
+
/**
|
|
1143
|
+
* Enable logging (useful for debugging production issues)
|
|
1144
|
+
*/
|
|
1145
|
+
enable(): void;
|
|
1146
|
+
/**
|
|
1147
|
+
* Disable logging
|
|
1148
|
+
*/
|
|
1149
|
+
disable(): void;
|
|
1150
|
+
/**
|
|
1151
|
+
* Log at debug level
|
|
1152
|
+
*/
|
|
1153
|
+
debug: (...args: unknown[]) => void;
|
|
1154
|
+
/**
|
|
1155
|
+
* Log at default level
|
|
1156
|
+
*/
|
|
1157
|
+
log: (...args: unknown[]) => void;
|
|
1158
|
+
/**
|
|
1159
|
+
* Log at info level
|
|
1160
|
+
*/
|
|
1161
|
+
info: (...args: unknown[]) => void;
|
|
1162
|
+
/**
|
|
1163
|
+
* Log at warn level
|
|
1164
|
+
*/
|
|
1165
|
+
warn: (...args: unknown[]) => void;
|
|
1166
|
+
/**
|
|
1167
|
+
* Log at error level (always logs unless explicitly disabled)
|
|
1168
|
+
*/
|
|
1169
|
+
error: (...args: unknown[]) => void;
|
|
1170
|
+
/**
|
|
1171
|
+
* Create a scoped logger with a prefix
|
|
1172
|
+
*/
|
|
1173
|
+
scope(prefix: string): {
|
|
1174
|
+
debug: (...args: unknown[]) => void;
|
|
1175
|
+
log: (...args: unknown[]) => void;
|
|
1176
|
+
info: (...args: unknown[]) => void;
|
|
1177
|
+
warn: (...args: unknown[]) => void;
|
|
1178
|
+
error: (...args: unknown[]) => void;
|
|
1179
|
+
};
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
/**
|
|
1183
|
+
* Crypto types and CryptoProvider interface
|
|
1184
|
+
*
|
|
1185
|
+
* Platform-agnostic cryptographic operations for E2E encryption.
|
|
1186
|
+
* Implementations:
|
|
1187
|
+
* - WASM (desktop/web): Uses channel-wasm bindings
|
|
1188
|
+
* - Native (iOS/Android): Uses uniffi-generated bindings
|
|
1189
|
+
*/
|
|
1190
|
+
interface Ed448Keypair {
|
|
1191
|
+
type: 'ed448';
|
|
1192
|
+
public_key: number[];
|
|
1193
|
+
private_key: number[];
|
|
1194
|
+
}
|
|
1195
|
+
interface X448Keypair {
|
|
1196
|
+
type: 'x448';
|
|
1197
|
+
public_key: number[];
|
|
1198
|
+
private_key: number[];
|
|
1199
|
+
}
|
|
1200
|
+
type Keypair = Ed448Keypair | X448Keypair;
|
|
1201
|
+
interface MessageCiphertext {
|
|
1202
|
+
ciphertext: string;
|
|
1203
|
+
initialization_vector: string;
|
|
1204
|
+
associated_data?: string;
|
|
1205
|
+
}
|
|
1206
|
+
interface P2PChannelEnvelope {
|
|
1207
|
+
protocol_identifier: number;
|
|
1208
|
+
message_header: MessageCiphertext;
|
|
1209
|
+
message_body: MessageCiphertext;
|
|
1210
|
+
}
|
|
1211
|
+
interface SenderX3DHParams {
|
|
1212
|
+
sending_identity_private_key: number[];
|
|
1213
|
+
sending_ephemeral_private_key: number[];
|
|
1214
|
+
receiving_identity_key: number[];
|
|
1215
|
+
receiving_signed_pre_key: number[];
|
|
1216
|
+
session_key_length: number;
|
|
1217
|
+
}
|
|
1218
|
+
interface ReceiverX3DHParams {
|
|
1219
|
+
sending_identity_private_key: number[];
|
|
1220
|
+
sending_signed_private_key: number[];
|
|
1221
|
+
receiving_identity_key: number[];
|
|
1222
|
+
receiving_ephemeral_key: number[];
|
|
1223
|
+
session_key_length: number;
|
|
1224
|
+
}
|
|
1225
|
+
interface NewDoubleRatchetParams {
|
|
1226
|
+
session_key: number[];
|
|
1227
|
+
sending_header_key: number[];
|
|
1228
|
+
next_receiving_header_key: number[];
|
|
1229
|
+
is_sender: boolean;
|
|
1230
|
+
sending_ephemeral_private_key: number[];
|
|
1231
|
+
receiving_ephemeral_key: number[];
|
|
1232
|
+
}
|
|
1233
|
+
interface DoubleRatchetStateAndMessage {
|
|
1234
|
+
ratchet_state: string;
|
|
1235
|
+
message: number[];
|
|
1236
|
+
}
|
|
1237
|
+
interface DoubleRatchetStateAndEnvelope {
|
|
1238
|
+
ratchet_state: string;
|
|
1239
|
+
envelope: string;
|
|
1240
|
+
}
|
|
1241
|
+
interface PeerInfo {
|
|
1242
|
+
public_key: number[];
|
|
1243
|
+
identity_public_key: number[];
|
|
1244
|
+
signed_pre_public_key: number[];
|
|
1245
|
+
}
|
|
1246
|
+
interface NewTripleRatchetParams {
|
|
1247
|
+
peers: number[][];
|
|
1248
|
+
peer_key: number[];
|
|
1249
|
+
identity_key: number[];
|
|
1250
|
+
signed_pre_key: number[];
|
|
1251
|
+
threshold: number;
|
|
1252
|
+
async_dkg_ratchet: boolean;
|
|
1253
|
+
}
|
|
1254
|
+
interface TripleRatchetStateAndMetadata {
|
|
1255
|
+
ratchet_state: string;
|
|
1256
|
+
metadata: Record<string, string>;
|
|
1257
|
+
}
|
|
1258
|
+
interface TripleRatchetStateAndMessage {
|
|
1259
|
+
ratchet_state: string;
|
|
1260
|
+
message: number[];
|
|
1261
|
+
}
|
|
1262
|
+
interface TripleRatchetStateAndEnvelope {
|
|
1263
|
+
ratchet_state: string;
|
|
1264
|
+
envelope: string;
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* InitializationEnvelope - Contains sender info for first message in a session
|
|
1268
|
+
*
|
|
1269
|
+
* This is the format BEFORE sealing. The ephemeral_public_key is NOT included here -
|
|
1270
|
+
* it goes at the TOP LEVEL of the SealedMessage, and the SAME ephemeral key is used for both:
|
|
1271
|
+
* 1. Sealing the envelope (inbox encryption)
|
|
1272
|
+
* 2. X3DH session establishment
|
|
1273
|
+
*
|
|
1274
|
+
* After unsealing, the ephemeral_public_key is added back to create UnsealedEnvelope
|
|
1275
|
+
* (see transport/websocket.ts)
|
|
1276
|
+
*/
|
|
1277
|
+
interface InitializationEnvelope {
|
|
1278
|
+
/** Sender's user address */
|
|
1279
|
+
user_address: string;
|
|
1280
|
+
/** Sender's display name */
|
|
1281
|
+
display_name?: string;
|
|
1282
|
+
/** Sender's icon URL */
|
|
1283
|
+
user_icon?: string;
|
|
1284
|
+
/** Inbox address for sending replies */
|
|
1285
|
+
return_inbox_address: string;
|
|
1286
|
+
/** X448 encryption key for the return inbox (hex) */
|
|
1287
|
+
return_inbox_encryption_key: string;
|
|
1288
|
+
/** Ed448 public key for the return inbox (hex) */
|
|
1289
|
+
return_inbox_public_key: string;
|
|
1290
|
+
/** Ed448 private key for the return inbox (hex) - shared so recipient can sign replies */
|
|
1291
|
+
return_inbox_private_key: string;
|
|
1292
|
+
/** Sender's identity public key for X3DH (hex) */
|
|
1293
|
+
identity_public_key: string;
|
|
1294
|
+
/** Session/conversation tag (typically the return inbox address) */
|
|
1295
|
+
tag: string;
|
|
1296
|
+
/** The Double Ratchet encrypted message envelope */
|
|
1297
|
+
message: string;
|
|
1298
|
+
/** Message type (e.g., 'direct') */
|
|
1299
|
+
type: string;
|
|
1300
|
+
}
|
|
1301
|
+
interface InboxMessageEncryptRequest {
|
|
1302
|
+
inbox_public_key: number[];
|
|
1303
|
+
ephemeral_private_key: number[];
|
|
1304
|
+
plaintext: number[];
|
|
1305
|
+
}
|
|
1306
|
+
interface InboxMessageDecryptRequest {
|
|
1307
|
+
inbox_private_key: number[];
|
|
1308
|
+
ephemeral_public_key: number[];
|
|
1309
|
+
ciphertext: MessageCiphertext;
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* CryptoProvider - Platform-agnostic interface for cryptographic operations
|
|
1313
|
+
*
|
|
1314
|
+
* This interface abstracts the underlying crypto implementation,
|
|
1315
|
+
* allowing the same code to work with WASM (desktop) or native modules (mobile).
|
|
1316
|
+
*
|
|
1317
|
+
* All methods are async to support both synchronous WASM and async native bridges.
|
|
1318
|
+
* State is serialized as JSON strings for cross-platform compatibility.
|
|
1319
|
+
*/
|
|
1320
|
+
interface CryptoProvider {
|
|
1321
|
+
/**
|
|
1322
|
+
* Generate an X448 keypair for encryption
|
|
1323
|
+
*/
|
|
1324
|
+
generateX448(): Promise<X448Keypair>;
|
|
1325
|
+
/**
|
|
1326
|
+
* Generate an Ed448 keypair for signing
|
|
1327
|
+
*/
|
|
1328
|
+
generateEd448(): Promise<Ed448Keypair>;
|
|
1329
|
+
/**
|
|
1330
|
+
* Derive public key from X448 private key
|
|
1331
|
+
* @param privateKey Base64-encoded private key
|
|
1332
|
+
* @returns Base64-encoded public key
|
|
1333
|
+
*/
|
|
1334
|
+
getPublicKeyX448(privateKey: string): Promise<string>;
|
|
1335
|
+
/**
|
|
1336
|
+
* Derive public key from Ed448 private key
|
|
1337
|
+
* @param privateKey Base64-encoded private key
|
|
1338
|
+
* @returns Base64-encoded public key
|
|
1339
|
+
*/
|
|
1340
|
+
getPublicKeyEd448(privateKey: string): Promise<string>;
|
|
1341
|
+
/**
|
|
1342
|
+
* Perform sender-side X3DH key agreement
|
|
1343
|
+
* @returns Base64-encoded session key (96 bytes: 32 session + 32 sending header + 32 receiving header)
|
|
1344
|
+
*/
|
|
1345
|
+
senderX3DH(params: SenderX3DHParams): Promise<string>;
|
|
1346
|
+
/**
|
|
1347
|
+
* Perform receiver-side X3DH key agreement
|
|
1348
|
+
* @returns Base64-encoded session key (96 bytes)
|
|
1349
|
+
*/
|
|
1350
|
+
receiverX3DH(params: ReceiverX3DHParams): Promise<string>;
|
|
1351
|
+
/**
|
|
1352
|
+
* Initialize a new double ratchet session
|
|
1353
|
+
* @returns JSON-serialized ratchet state
|
|
1354
|
+
*/
|
|
1355
|
+
newDoubleRatchet(params: NewDoubleRatchetParams): Promise<string>;
|
|
1356
|
+
/**
|
|
1357
|
+
* Encrypt a message using double ratchet
|
|
1358
|
+
* @returns Updated state and encrypted envelope
|
|
1359
|
+
*/
|
|
1360
|
+
doubleRatchetEncrypt(stateAndMessage: DoubleRatchetStateAndMessage): Promise<DoubleRatchetStateAndEnvelope>;
|
|
1361
|
+
/**
|
|
1362
|
+
* Decrypt a message using double ratchet
|
|
1363
|
+
* @returns Updated state and decrypted message
|
|
1364
|
+
*/
|
|
1365
|
+
doubleRatchetDecrypt(stateAndEnvelope: DoubleRatchetStateAndEnvelope): Promise<DoubleRatchetStateAndMessage>;
|
|
1366
|
+
/**
|
|
1367
|
+
* Initialize a new triple ratchet session for group messaging
|
|
1368
|
+
* @returns Initial state and DKG metadata
|
|
1369
|
+
*/
|
|
1370
|
+
newTripleRatchet(params: NewTripleRatchetParams): Promise<TripleRatchetStateAndMetadata>;
|
|
1371
|
+
/**
|
|
1372
|
+
* Triple ratchet DKG round 1 - Generate polynomial fragments
|
|
1373
|
+
*/
|
|
1374
|
+
tripleRatchetInitRound1(state: TripleRatchetStateAndMetadata): Promise<TripleRatchetStateAndMetadata>;
|
|
1375
|
+
/**
|
|
1376
|
+
* Triple ratchet DKG round 2 - Receive fragments, compute ZK commitment
|
|
1377
|
+
*/
|
|
1378
|
+
tripleRatchetInitRound2(state: TripleRatchetStateAndMetadata): Promise<TripleRatchetStateAndMetadata>;
|
|
1379
|
+
/**
|
|
1380
|
+
* Triple ratchet DKG round 3 - Receive commitments, compute ZK reveal
|
|
1381
|
+
*/
|
|
1382
|
+
tripleRatchetInitRound3(state: TripleRatchetStateAndMetadata): Promise<TripleRatchetStateAndMetadata>;
|
|
1383
|
+
/**
|
|
1384
|
+
* Triple ratchet DKG round 4 - Verify proofs, reconstruct group key
|
|
1385
|
+
*/
|
|
1386
|
+
tripleRatchetInitRound4(state: TripleRatchetStateAndMetadata): Promise<TripleRatchetStateAndMetadata>;
|
|
1387
|
+
/**
|
|
1388
|
+
* Encrypt a message for the group using triple ratchet
|
|
1389
|
+
*/
|
|
1390
|
+
tripleRatchetEncrypt(stateAndMessage: TripleRatchetStateAndMessage): Promise<TripleRatchetStateAndEnvelope>;
|
|
1391
|
+
/**
|
|
1392
|
+
* Decrypt a group message using triple ratchet
|
|
1393
|
+
*/
|
|
1394
|
+
tripleRatchetDecrypt(stateAndEnvelope: TripleRatchetStateAndEnvelope): Promise<TripleRatchetStateAndMessage>;
|
|
1395
|
+
/**
|
|
1396
|
+
* Handle group membership changes
|
|
1397
|
+
*/
|
|
1398
|
+
tripleRatchetResize(state: TripleRatchetStateAndMetadata): Promise<TripleRatchetStateAndMetadata>;
|
|
1399
|
+
/**
|
|
1400
|
+
* Encrypt a message for an inbox (sealed sender / ECIES-style)
|
|
1401
|
+
* @returns Base64-encoded sealed message
|
|
1402
|
+
*/
|
|
1403
|
+
encryptInboxMessage(request: InboxMessageEncryptRequest): Promise<string>;
|
|
1404
|
+
/**
|
|
1405
|
+
* Decrypt a sealed inbox message
|
|
1406
|
+
* @returns Decrypted plaintext as byte array
|
|
1407
|
+
*/
|
|
1408
|
+
decryptInboxMessage(request: InboxMessageDecryptRequest): Promise<number[]>;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/**
|
|
1412
|
+
* Encryption State Types and Storage Interface
|
|
1413
|
+
*
|
|
1414
|
+
* Platform-agnostic types for managing Double Ratchet encryption states.
|
|
1415
|
+
* The actual storage implementation is platform-specific (MMKV for mobile, IndexedDB for desktop).
|
|
1416
|
+
*/
|
|
1417
|
+
/**
|
|
1418
|
+
* DeviceKeys - Device's cryptographic keypairs for E2E encryption
|
|
1419
|
+
* Used for X3DH key exchange and inbox message unsealing
|
|
1420
|
+
*/
|
|
1421
|
+
interface DeviceKeys {
|
|
1422
|
+
/** X448 identity key for X3DH - private key */
|
|
1423
|
+
identityPrivateKey: number[];
|
|
1424
|
+
/** X448 identity key for X3DH - public key */
|
|
1425
|
+
identityPublicKey: number[];
|
|
1426
|
+
/** X448 signed pre-key for X3DH - private key */
|
|
1427
|
+
preKeyPrivateKey: number[];
|
|
1428
|
+
/** X448 signed pre-key for X3DH - public key */
|
|
1429
|
+
preKeyPublicKey: number[];
|
|
1430
|
+
/** X448 inbox encryption key for unsealing - private key */
|
|
1431
|
+
inboxEncryptionPrivateKey: number[];
|
|
1432
|
+
/** X448 inbox encryption key for unsealing - public key */
|
|
1433
|
+
inboxEncryptionPublicKey: number[];
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* RecipientInfo - Recipient's public keys needed for encryption
|
|
1437
|
+
* Used to establish X3DH session and seal messages
|
|
1438
|
+
*/
|
|
1439
|
+
interface RecipientInfo {
|
|
1440
|
+
/** Recipient's address */
|
|
1441
|
+
address: string;
|
|
1442
|
+
/** X448 identity public key for X3DH */
|
|
1443
|
+
identityKey: number[];
|
|
1444
|
+
/** X448 signed pre-key for X3DH */
|
|
1445
|
+
signedPreKey: number[];
|
|
1446
|
+
/** Recipient's inbox address */
|
|
1447
|
+
inboxAddress: string;
|
|
1448
|
+
/** X448 public key for sealing envelopes to recipient's inbox */
|
|
1449
|
+
inboxEncryptionKey?: number[];
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* EncryptedEnvelope - Result of encrypting a message
|
|
1453
|
+
*/
|
|
1454
|
+
interface EncryptedEnvelope {
|
|
1455
|
+
/** The encrypted Double Ratchet envelope */
|
|
1456
|
+
envelope: string;
|
|
1457
|
+
/** Recipient's inbox address */
|
|
1458
|
+
inboxAddress: string;
|
|
1459
|
+
/** Ephemeral public key (only set for first message in new session) */
|
|
1460
|
+
ephemeralPublicKey?: number[];
|
|
1461
|
+
/** Ephemeral private key (only set for first message - needed for sealing) */
|
|
1462
|
+
ephemeralPrivateKey?: number[];
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* KeyValueStorageProvider - Platform-agnostic key-value storage interface
|
|
1466
|
+
*
|
|
1467
|
+
* Implementations:
|
|
1468
|
+
* - MMKV (React Native)
|
|
1469
|
+
* - IndexedDB (Desktop/Web)
|
|
1470
|
+
* - AsyncStorage (React Native fallback)
|
|
1471
|
+
*/
|
|
1472
|
+
interface KeyValueStorageProvider {
|
|
1473
|
+
/**
|
|
1474
|
+
* Get a string value by key
|
|
1475
|
+
* @returns The value or null if not found
|
|
1476
|
+
*/
|
|
1477
|
+
getString(key: string): string | null;
|
|
1478
|
+
/**
|
|
1479
|
+
* Set a string value
|
|
1480
|
+
*/
|
|
1481
|
+
set(key: string, value: string): void;
|
|
1482
|
+
/**
|
|
1483
|
+
* Remove a key
|
|
1484
|
+
*/
|
|
1485
|
+
remove(key: string): void;
|
|
1486
|
+
/**
|
|
1487
|
+
* Get all keys in storage
|
|
1488
|
+
*/
|
|
1489
|
+
getAllKeys(): string[];
|
|
1490
|
+
/**
|
|
1491
|
+
* Clear all data from storage
|
|
1492
|
+
*/
|
|
1493
|
+
clearAll(): void;
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* SendingInbox - Recipient's inbox info needed for sealing messages
|
|
1497
|
+
* Matches desktop SDK's SendingInbox type
|
|
1498
|
+
*/
|
|
1499
|
+
interface SendingInbox {
|
|
1500
|
+
/** Recipient's inbox address where we send to */
|
|
1501
|
+
inbox_address: string;
|
|
1502
|
+
/** Recipient's X448 public key for sealing (hex) */
|
|
1503
|
+
inbox_encryption_key: string;
|
|
1504
|
+
/** Recipient's Ed448 public key (hex) - empty until confirmed */
|
|
1505
|
+
inbox_public_key: string;
|
|
1506
|
+
/** Always empty - we don't have their private key */
|
|
1507
|
+
inbox_private_key: string;
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* ReceivingInbox - Our inbox info for receiving replies
|
|
1511
|
+
* Simplified version (full keypair stored separately in ConversationInboxKeypair)
|
|
1512
|
+
*/
|
|
1513
|
+
interface ReceivingInbox {
|
|
1514
|
+
/** Our inbox address where we receive replies */
|
|
1515
|
+
inbox_address: string;
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* EncryptionState - Double Ratchet state for a conversation+inbox pair
|
|
1519
|
+
* Matches desktop's DoubleRatchetStateAndInboxKeys structure
|
|
1520
|
+
*/
|
|
1521
|
+
interface EncryptionState {
|
|
1522
|
+
/** JSON-serialized ratchet state */
|
|
1523
|
+
state: string;
|
|
1524
|
+
/** When state was created/updated */
|
|
1525
|
+
timestamp: number;
|
|
1526
|
+
/** Conversation identifier */
|
|
1527
|
+
conversationId: string;
|
|
1528
|
+
/** Associated inbox ID (our receiving inbox) */
|
|
1529
|
+
inboxId: string;
|
|
1530
|
+
/** Whether we've sent an accept message */
|
|
1531
|
+
sentAccept?: boolean;
|
|
1532
|
+
/** Recipient's inbox info for sealing messages */
|
|
1533
|
+
sendingInbox?: SendingInbox;
|
|
1534
|
+
/** Session tag (usually our inbox address) */
|
|
1535
|
+
tag?: string;
|
|
1536
|
+
/**
|
|
1537
|
+
* X3DH ephemeral public key (hex) used for session establishment.
|
|
1538
|
+
* MUST be reused for all init envelopes until session is confirmed.
|
|
1539
|
+
* This ensures the receiver can derive the same session key via X3DH.
|
|
1540
|
+
*/
|
|
1541
|
+
x3dhEphemeralPublicKey?: string;
|
|
1542
|
+
/**
|
|
1543
|
+
* X3DH ephemeral private key (hex) used for session establishment.
|
|
1544
|
+
* MUST be reused for sealing init envelopes until session is confirmed.
|
|
1545
|
+
*/
|
|
1546
|
+
x3dhEphemeralPrivateKey?: string;
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* InboxMapping - Maps inbox address to conversation
|
|
1550
|
+
*/
|
|
1551
|
+
interface InboxMapping {
|
|
1552
|
+
inboxId: string;
|
|
1553
|
+
conversationId: string;
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* LatestState - Tracks the most recent state for a conversation
|
|
1557
|
+
*/
|
|
1558
|
+
interface LatestState {
|
|
1559
|
+
conversationId: string;
|
|
1560
|
+
inboxId: string;
|
|
1561
|
+
timestamp: number;
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* ConversationInboxKeypair - Per-conversation inbox keypair for receiving replies
|
|
1565
|
+
* Mirrors desktop's InboxKeyset structure with both Ed448 and X448 keys
|
|
1566
|
+
*/
|
|
1567
|
+
interface ConversationInboxKeypair {
|
|
1568
|
+
conversationId: string;
|
|
1569
|
+
inboxAddress: string;
|
|
1570
|
+
/** X448 encryption public key (for sealing/unsealing messages) */
|
|
1571
|
+
encryptionPublicKey: number[];
|
|
1572
|
+
/** X448 encryption private key */
|
|
1573
|
+
encryptionPrivateKey: number[];
|
|
1574
|
+
/** Ed448 signing public key (for signing/verifying inbox messages) */
|
|
1575
|
+
signingPublicKey?: number[];
|
|
1576
|
+
/** Ed448 signing private key */
|
|
1577
|
+
signingPrivateKey?: number[];
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Storage key prefixes for encryption state data
|
|
1581
|
+
*/
|
|
1582
|
+
declare const ENCRYPTION_STORAGE_KEYS: {
|
|
1583
|
+
/** enc_state:{conversationId}:{inboxId} */
|
|
1584
|
+
readonly ENCRYPTION_STATE: "enc_state:";
|
|
1585
|
+
/** inbox_map:{inboxId} */
|
|
1586
|
+
readonly INBOX_MAPPING: "inbox_map:";
|
|
1587
|
+
/** latest:{conversationId} */
|
|
1588
|
+
readonly LATEST_STATE: "latest:";
|
|
1589
|
+
/** conv_inboxes:{conversationId} -> inboxId[] */
|
|
1590
|
+
readonly CONVERSATION_INBOXES: "conv_inboxes:";
|
|
1591
|
+
/** conv_inbox_key:{conversationId} -> ConversationInboxKeypair */
|
|
1592
|
+
readonly CONVERSATION_INBOX_KEY: "conv_inbox_key:";
|
|
1593
|
+
};
|
|
1594
|
+
/**
|
|
1595
|
+
* EncryptionStateStorageInterface - Interface for managing encryption states
|
|
1596
|
+
*
|
|
1597
|
+
* This interface defines the contract for encryption state storage.
|
|
1598
|
+
* Implementations use platform-specific storage backends.
|
|
1599
|
+
*/
|
|
1600
|
+
interface EncryptionStateStorageInterface {
|
|
1601
|
+
getEncryptionState(conversationId: string, inboxId: string): EncryptionState | null;
|
|
1602
|
+
getEncryptionStates(conversationId: string): EncryptionState[];
|
|
1603
|
+
saveEncryptionState(state: EncryptionState, updateLatest?: boolean): void;
|
|
1604
|
+
deleteEncryptionState(conversationId: string, inboxId: string): void;
|
|
1605
|
+
deleteAllEncryptionStates(conversationId: string): void;
|
|
1606
|
+
getInboxMapping(inboxId: string): InboxMapping | null;
|
|
1607
|
+
saveInboxMapping(inboxId: string, conversationId: string): void;
|
|
1608
|
+
deleteInboxMapping(inboxId: string): void;
|
|
1609
|
+
getLatestState(conversationId: string): LatestState | null;
|
|
1610
|
+
saveConversationInboxKeypair(keypair: ConversationInboxKeypair): void;
|
|
1611
|
+
getConversationInboxKeypair(conversationId: string): ConversationInboxKeypair | null;
|
|
1612
|
+
deleteConversationInboxKeypair(conversationId: string): void;
|
|
1613
|
+
getConversationInboxKeypairByAddress(inboxAddress: string): ConversationInboxKeypair | null;
|
|
1614
|
+
getAllConversationInboxAddresses(): string[];
|
|
1615
|
+
clearAll(): void;
|
|
1616
|
+
hasEncryptionState(conversationId: string): boolean;
|
|
1617
|
+
getStatesByInboxId(inboxId: string): Array<{
|
|
1618
|
+
conversationId: string;
|
|
1619
|
+
state: EncryptionState;
|
|
1620
|
+
}>;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
/**
|
|
1624
|
+
* WASM CryptoProvider implementation
|
|
1625
|
+
*
|
|
1626
|
+
* Wraps the channel-wasm WASM module to implement the CryptoProvider interface.
|
|
1627
|
+
* Used by desktop/web applications.
|
|
1628
|
+
*
|
|
1629
|
+
* Note: This file imports from the WASM module dynamically to avoid bundling
|
|
1630
|
+
* issues in environments that don't support WASM.
|
|
1631
|
+
*/
|
|
1632
|
+
|
|
1633
|
+
/**
|
|
1634
|
+
* Interface for the WASM module functions
|
|
1635
|
+
* This matches the exports from channel-wasm
|
|
1636
|
+
*/
|
|
1637
|
+
interface ChannelWasmModule {
|
|
1638
|
+
js_generate_x448(): string;
|
|
1639
|
+
js_generate_ed448(): string;
|
|
1640
|
+
js_get_pubkey_x448(key: string): string;
|
|
1641
|
+
js_get_pubkey_ed448(key: string): string;
|
|
1642
|
+
js_sign_ed448(key: string, message: string): string;
|
|
1643
|
+
js_verify_ed448(publicKey: string, message: string, signature: string): string;
|
|
1644
|
+
js_sender_x3dh(input: string): string;
|
|
1645
|
+
js_receiver_x3dh(input: string): string;
|
|
1646
|
+
js_new_double_ratchet(params: string): string;
|
|
1647
|
+
js_double_ratchet_encrypt(params: string): string;
|
|
1648
|
+
js_double_ratchet_decrypt(params: string): string;
|
|
1649
|
+
js_new_triple_ratchet(params: string): string;
|
|
1650
|
+
js_triple_ratchet_init_round_1(params: string): string;
|
|
1651
|
+
js_triple_ratchet_init_round_2(params: string): string;
|
|
1652
|
+
js_triple_ratchet_init_round_3(params: string): string;
|
|
1653
|
+
js_triple_ratchet_init_round_4(params: string): string;
|
|
1654
|
+
js_triple_ratchet_encrypt(params: string): string;
|
|
1655
|
+
js_triple_ratchet_decrypt(params: string): string;
|
|
1656
|
+
js_triple_ratchet_resize(params: string): string;
|
|
1657
|
+
js_encrypt_inbox_message(input: string): string;
|
|
1658
|
+
js_decrypt_inbox_message(input: string): string;
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* WasmCryptoProvider - Implements CryptoProvider using channel-wasm
|
|
1662
|
+
*/
|
|
1663
|
+
declare class WasmCryptoProvider implements CryptoProvider {
|
|
1664
|
+
private wasm;
|
|
1665
|
+
constructor(wasmModule: ChannelWasmModule);
|
|
1666
|
+
generateX448(): Promise<X448Keypair>;
|
|
1667
|
+
generateEd448(): Promise<Ed448Keypair>;
|
|
1668
|
+
getPublicKeyX448(privateKey: string): Promise<string>;
|
|
1669
|
+
getPublicKeyEd448(privateKey: string): Promise<string>;
|
|
1670
|
+
senderX3DH(params: SenderX3DHParams): Promise<string>;
|
|
1671
|
+
receiverX3DH(params: ReceiverX3DHParams): Promise<string>;
|
|
1672
|
+
newDoubleRatchet(params: NewDoubleRatchetParams): Promise<string>;
|
|
1673
|
+
doubleRatchetEncrypt(stateAndMessage: DoubleRatchetStateAndMessage): Promise<DoubleRatchetStateAndEnvelope>;
|
|
1674
|
+
doubleRatchetDecrypt(stateAndEnvelope: DoubleRatchetStateAndEnvelope): Promise<DoubleRatchetStateAndMessage>;
|
|
1675
|
+
newTripleRatchet(params: NewTripleRatchetParams): Promise<TripleRatchetStateAndMetadata>;
|
|
1676
|
+
tripleRatchetInitRound1(state: TripleRatchetStateAndMetadata): Promise<TripleRatchetStateAndMetadata>;
|
|
1677
|
+
tripleRatchetInitRound2(state: TripleRatchetStateAndMetadata): Promise<TripleRatchetStateAndMetadata>;
|
|
1678
|
+
tripleRatchetInitRound3(state: TripleRatchetStateAndMetadata): Promise<TripleRatchetStateAndMetadata>;
|
|
1679
|
+
tripleRatchetInitRound4(state: TripleRatchetStateAndMetadata): Promise<TripleRatchetStateAndMetadata>;
|
|
1680
|
+
tripleRatchetEncrypt(stateAndMessage: TripleRatchetStateAndMessage): Promise<TripleRatchetStateAndEnvelope>;
|
|
1681
|
+
tripleRatchetDecrypt(stateAndEnvelope: TripleRatchetStateAndEnvelope): Promise<TripleRatchetStateAndMessage>;
|
|
1682
|
+
tripleRatchetResize(state: TripleRatchetStateAndMetadata): Promise<TripleRatchetStateAndMetadata>;
|
|
1683
|
+
encryptInboxMessage(request: InboxMessageEncryptRequest): Promise<string>;
|
|
1684
|
+
decryptInboxMessage(request: InboxMessageDecryptRequest): Promise<number[]>;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
/**
|
|
1688
|
+
* Signing types and SigningProvider interface
|
|
1689
|
+
*
|
|
1690
|
+
* Platform-agnostic message signing and verification.
|
|
1691
|
+
* Uses Ed448 for all signing operations.
|
|
1692
|
+
*/
|
|
1693
|
+
/**
|
|
1694
|
+
* SigningProvider - Platform-agnostic interface for cryptographic signing
|
|
1695
|
+
*
|
|
1696
|
+
* Implementations:
|
|
1697
|
+
* - WASM (desktop/web): Uses channel-wasm js_sign_ed448/js_verify_ed448
|
|
1698
|
+
* - Native (iOS/Android): Uses uniffi-generated bindings
|
|
1699
|
+
*/
|
|
1700
|
+
interface SigningProvider {
|
|
1701
|
+
/**
|
|
1702
|
+
* Sign a message using Ed448
|
|
1703
|
+
* @param privateKey Base64-encoded Ed448 private key (56 bytes)
|
|
1704
|
+
* @param message Base64-encoded message to sign
|
|
1705
|
+
* @returns Base64-encoded signature (114 bytes)
|
|
1706
|
+
*/
|
|
1707
|
+
signEd448(privateKey: string, message: string): Promise<string>;
|
|
1708
|
+
/**
|
|
1709
|
+
* Verify an Ed448 signature
|
|
1710
|
+
* @param publicKey Base64-encoded Ed448 public key (57 bytes)
|
|
1711
|
+
* @param message Base64-encoded original message
|
|
1712
|
+
* @param signature Base64-encoded signature to verify
|
|
1713
|
+
* @returns true if signature is valid, false otherwise
|
|
1714
|
+
*/
|
|
1715
|
+
verifyEd448(publicKey: string, message: string, signature: string): Promise<boolean>;
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Signed message structure
|
|
1719
|
+
*/
|
|
1720
|
+
interface SignedMessage {
|
|
1721
|
+
/** The message content (may be encrypted) */
|
|
1722
|
+
content: string;
|
|
1723
|
+
/** Base64-encoded Ed448 signature */
|
|
1724
|
+
signature: string;
|
|
1725
|
+
/** Base64-encoded Ed448 public key of signer */
|
|
1726
|
+
publicKey: string;
|
|
1727
|
+
/** Timestamp when signed */
|
|
1728
|
+
timestamp: number;
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Verify a signed message structure
|
|
1732
|
+
*/
|
|
1733
|
+
declare function verifySignedMessage(provider: SigningProvider, message: SignedMessage): Promise<boolean>;
|
|
1734
|
+
/**
|
|
1735
|
+
* Create a signed message
|
|
1736
|
+
*/
|
|
1737
|
+
declare function createSignedMessage(provider: SigningProvider, privateKey: string, publicKey: string, content: string): Promise<SignedMessage>;
|
|
1738
|
+
|
|
1739
|
+
/**
|
|
1740
|
+
* WASM SigningProvider implementation
|
|
1741
|
+
*
|
|
1742
|
+
* Wraps the channel-wasm WASM module to implement the SigningProvider interface.
|
|
1743
|
+
* Used by desktop/web applications.
|
|
1744
|
+
*/
|
|
1745
|
+
|
|
1746
|
+
/**
|
|
1747
|
+
* WasmSigningProvider - Implements SigningProvider using channel-wasm
|
|
1748
|
+
*/
|
|
1749
|
+
declare class WasmSigningProvider implements SigningProvider {
|
|
1750
|
+
private wasm;
|
|
1751
|
+
constructor(wasmModule: ChannelWasmModule);
|
|
1752
|
+
signEd448(privateKey: string, message: string): Promise<string>;
|
|
1753
|
+
verifyEd448(publicKey: string, message: string, signature: string): Promise<boolean>;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
/**
|
|
1757
|
+
* Transport types and interfaces
|
|
1758
|
+
*
|
|
1759
|
+
* Platform-agnostic transport layer for HTTP and IPC communication.
|
|
1760
|
+
*/
|
|
1761
|
+
interface TransportConfig {
|
|
1762
|
+
baseUrl: string;
|
|
1763
|
+
timeout?: number;
|
|
1764
|
+
defaultHeaders?: Record<string, string>;
|
|
1765
|
+
}
|
|
1766
|
+
interface TransportRequestOptions {
|
|
1767
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
1768
|
+
headers?: Record<string, string>;
|
|
1769
|
+
body?: unknown;
|
|
1770
|
+
timeout?: number;
|
|
1771
|
+
signal?: AbortSignal;
|
|
1772
|
+
}
|
|
1773
|
+
interface TransportResponse<T> {
|
|
1774
|
+
data: T;
|
|
1775
|
+
status: number;
|
|
1776
|
+
headers: Record<string, string>;
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* TransportClient - Platform-agnostic HTTP transport
|
|
1780
|
+
*
|
|
1781
|
+
* Implementations:
|
|
1782
|
+
* - Fetch (mobile/web): Uses native fetch API
|
|
1783
|
+
* - Electron IPC (desktop): Uses Electron IPC for main process requests
|
|
1784
|
+
*/
|
|
1785
|
+
interface TransportClient {
|
|
1786
|
+
/**
|
|
1787
|
+
* Make an HTTP request
|
|
1788
|
+
*/
|
|
1789
|
+
request<T>(endpoint: string, options?: TransportRequestOptions): Promise<TransportResponse<T>>;
|
|
1790
|
+
/**
|
|
1791
|
+
* Configure the transport client
|
|
1792
|
+
*/
|
|
1793
|
+
configure(config: TransportConfig): void;
|
|
1794
|
+
/**
|
|
1795
|
+
* Set authorization header for all requests
|
|
1796
|
+
*/
|
|
1797
|
+
setAuthToken(token: string): void;
|
|
1798
|
+
/**
|
|
1799
|
+
* Clear authorization
|
|
1800
|
+
*/
|
|
1801
|
+
clearAuth(): void;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
/**
|
|
1805
|
+
* WebSocket client interface
|
|
1806
|
+
*
|
|
1807
|
+
* Platform-agnostic WebSocket connection management.
|
|
1808
|
+
* Handles connection, reconnection, and message routing.
|
|
1809
|
+
*
|
|
1810
|
+
* IMPORTANT: This handles transport only. Encryption is handled separately
|
|
1811
|
+
* by the CryptoProvider before/after message transmission.
|
|
1812
|
+
*/
|
|
1813
|
+
type WebSocketConnectionState = 'connecting' | 'connected' | 'disconnected' | 'reconnecting';
|
|
1814
|
+
/**
|
|
1815
|
+
* Encrypted message received from WebSocket
|
|
1816
|
+
* The content is a SealedMessage containing the encrypted envelope.
|
|
1817
|
+
* All cryptographic material (ephemeral key, identity key) comes from the envelope itself.
|
|
1818
|
+
*/
|
|
1819
|
+
interface EncryptedWebSocketMessage {
|
|
1820
|
+
/** The encrypted message content (JSON string of SealedMessage) */
|
|
1821
|
+
encryptedContent: string;
|
|
1822
|
+
/** The inbox address this message was sent to */
|
|
1823
|
+
inboxAddress: string;
|
|
1824
|
+
/** Server timestamp */
|
|
1825
|
+
timestamp: number;
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* SealedMessage format from the network
|
|
1829
|
+
* Contains an encrypted envelope that can be unsealed with device keys.
|
|
1830
|
+
* The ephemeral_public_key is used along with the inbox private key to decrypt.
|
|
1831
|
+
*/
|
|
1832
|
+
interface SealedMessage {
|
|
1833
|
+
/** The inbox address the message was sent to */
|
|
1834
|
+
inbox_address: string;
|
|
1835
|
+
/** Sender's ephemeral public key (used for decryption) */
|
|
1836
|
+
ephemeral_public_key: string;
|
|
1837
|
+
/** The encrypted envelope (contains identity key, message, return inbox info) */
|
|
1838
|
+
envelope: string;
|
|
1839
|
+
/** Optional inbox public key for verification */
|
|
1840
|
+
inbox_public_key?: string;
|
|
1841
|
+
/** Optional inbox signature for verification */
|
|
1842
|
+
inbox_signature?: string;
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* UnsealedEnvelope - the decrypted content of a SealedMessage
|
|
1846
|
+
* Contains all the data needed to establish a session and decrypt the message.
|
|
1847
|
+
*/
|
|
1848
|
+
interface UnsealedEnvelope {
|
|
1849
|
+
/** Sender's user address */
|
|
1850
|
+
user_address: string;
|
|
1851
|
+
/** Sender's display name */
|
|
1852
|
+
display_name?: string;
|
|
1853
|
+
/** Sender's icon URL */
|
|
1854
|
+
user_icon?: string;
|
|
1855
|
+
/** Inbox address for sending replies */
|
|
1856
|
+
return_inbox_address: string;
|
|
1857
|
+
/** Encryption key for the return inbox */
|
|
1858
|
+
return_inbox_encryption_key: string;
|
|
1859
|
+
/** Public key for the return inbox */
|
|
1860
|
+
return_inbox_public_key: string;
|
|
1861
|
+
/** Private key for the return inbox (only for sender's own use) */
|
|
1862
|
+
return_inbox_private_key: string;
|
|
1863
|
+
/** Sender's identity public key (for X3DH) */
|
|
1864
|
+
identity_public_key: string;
|
|
1865
|
+
/** Session/conversation tag */
|
|
1866
|
+
tag: string;
|
|
1867
|
+
/** The encrypted/signed message content */
|
|
1868
|
+
message: string;
|
|
1869
|
+
/** Message type */
|
|
1870
|
+
type: string;
|
|
1871
|
+
/** The ephemeral public key used to seal this message */
|
|
1872
|
+
ephemeral_public_key: string;
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Outbound message to send via WebSocket
|
|
1876
|
+
*/
|
|
1877
|
+
interface OutboundWebSocketMessage {
|
|
1878
|
+
/** Message type (e.g., 'message', 'listen', 'unlisten') */
|
|
1879
|
+
type: string;
|
|
1880
|
+
/** Message payload */
|
|
1881
|
+
payload: unknown;
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Listen/subscribe message for inbox addresses
|
|
1885
|
+
*/
|
|
1886
|
+
interface ListenMessage {
|
|
1887
|
+
type: 'listen';
|
|
1888
|
+
inbox_addresses: string[];
|
|
1889
|
+
}
|
|
1890
|
+
/**
|
|
1891
|
+
* Unlisten/unsubscribe message
|
|
1892
|
+
*/
|
|
1893
|
+
interface UnlistenMessage {
|
|
1894
|
+
type: 'unlisten';
|
|
1895
|
+
inbox_addresses: string[];
|
|
1896
|
+
}
|
|
1897
|
+
interface WebSocketClientOptions {
|
|
1898
|
+
/** WebSocket server URL */
|
|
1899
|
+
url: string;
|
|
1900
|
+
/** Interval between reconnection attempts (ms). Default: 1000 */
|
|
1901
|
+
reconnectInterval?: number;
|
|
1902
|
+
/** Maximum reconnection attempts. Default: Infinity */
|
|
1903
|
+
maxReconnectAttempts?: number;
|
|
1904
|
+
/** Queue processing interval (ms). Default: 1000 */
|
|
1905
|
+
queueProcessInterval?: number;
|
|
1906
|
+
}
|
|
1907
|
+
type MessageHandler = (message: EncryptedWebSocketMessage) => Promise<void>;
|
|
1908
|
+
type StateChangeHandler = (state: WebSocketConnectionState) => void;
|
|
1909
|
+
type ErrorHandler = (error: Error) => void;
|
|
1910
|
+
/**
|
|
1911
|
+
* WebSocketClient - Platform-agnostic WebSocket interface
|
|
1912
|
+
*
|
|
1913
|
+
* Features:
|
|
1914
|
+
* - Automatic reconnection with configurable interval
|
|
1915
|
+
* - Dual queue system (inbound/outbound)
|
|
1916
|
+
* - Inbox address subscription management
|
|
1917
|
+
* - Message handler registration
|
|
1918
|
+
*
|
|
1919
|
+
* Implementations:
|
|
1920
|
+
* - Browser/Electron: Uses native WebSocket API
|
|
1921
|
+
* - React Native: Uses React Native WebSocket
|
|
1922
|
+
*/
|
|
1923
|
+
interface WebSocketClient {
|
|
1924
|
+
/**
|
|
1925
|
+
* Current connection state
|
|
1926
|
+
*/
|
|
1927
|
+
readonly state: WebSocketConnectionState;
|
|
1928
|
+
/**
|
|
1929
|
+
* Whether currently connected
|
|
1930
|
+
*/
|
|
1931
|
+
readonly isConnected: boolean;
|
|
1932
|
+
/**
|
|
1933
|
+
* Connect to the WebSocket server
|
|
1934
|
+
*/
|
|
1935
|
+
connect(): Promise<void>;
|
|
1936
|
+
/**
|
|
1937
|
+
* Disconnect from the WebSocket server
|
|
1938
|
+
*/
|
|
1939
|
+
disconnect(): void;
|
|
1940
|
+
/**
|
|
1941
|
+
* Send a message (must be already serialized)
|
|
1942
|
+
* Messages are queued if not connected and sent when connection is established
|
|
1943
|
+
*/
|
|
1944
|
+
send(message: string): Promise<void>;
|
|
1945
|
+
/**
|
|
1946
|
+
* Enqueue an outbound message with async preparation
|
|
1947
|
+
* Useful when message needs async work before sending
|
|
1948
|
+
*/
|
|
1949
|
+
enqueueOutbound(prepareMessage: () => Promise<string[]>): void;
|
|
1950
|
+
/**
|
|
1951
|
+
* Subscribe to messages for inbox addresses
|
|
1952
|
+
*/
|
|
1953
|
+
subscribe(inboxAddresses: string[]): Promise<void>;
|
|
1954
|
+
/**
|
|
1955
|
+
* Unsubscribe from inbox addresses
|
|
1956
|
+
*/
|
|
1957
|
+
unsubscribe(inboxAddresses: string[]): Promise<void>;
|
|
1958
|
+
/**
|
|
1959
|
+
* Set the message handler for incoming messages
|
|
1960
|
+
* Only one handler can be set at a time
|
|
1961
|
+
*/
|
|
1962
|
+
setMessageHandler(handler: MessageHandler): void;
|
|
1963
|
+
/**
|
|
1964
|
+
* Set the resubscribe callback (called after reconnection)
|
|
1965
|
+
*/
|
|
1966
|
+
setResubscribeHandler(handler: () => Promise<void>): void;
|
|
1967
|
+
/**
|
|
1968
|
+
* Add a connection state change listener
|
|
1969
|
+
* @returns Unsubscribe function
|
|
1970
|
+
*/
|
|
1971
|
+
onStateChange(handler: StateChangeHandler): () => void;
|
|
1972
|
+
/**
|
|
1973
|
+
* Add an error listener
|
|
1974
|
+
* @returns Unsubscribe function
|
|
1975
|
+
*/
|
|
1976
|
+
onError(handler: ErrorHandler): () => void;
|
|
1977
|
+
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Factory function to create a WebSocket client
|
|
1980
|
+
*/
|
|
1981
|
+
type CreateWebSocketClient = (options: WebSocketClientOptions) => WebSocketClient;
|
|
1982
|
+
|
|
1983
|
+
/**
|
|
1984
|
+
* Browser/Electron WebSocket client implementation
|
|
1985
|
+
*
|
|
1986
|
+
* Uses the native WebSocket API available in browsers and Electron.
|
|
1987
|
+
* Implements automatic reconnection, dual queue system, and subscription management.
|
|
1988
|
+
*/
|
|
1989
|
+
|
|
1990
|
+
/**
|
|
1991
|
+
* BrowserWebSocketClient - WebSocket implementation for browser/Electron
|
|
1992
|
+
*
|
|
1993
|
+
* Features:
|
|
1994
|
+
* - Automatic reconnection with configurable interval
|
|
1995
|
+
* - Dual queue system (inbound messages, outbound message generators)
|
|
1996
|
+
* - Inbox address subscription management
|
|
1997
|
+
* - Periodic queue processing
|
|
1998
|
+
*/
|
|
1999
|
+
declare class BrowserWebSocketClient implements WebSocketClient {
|
|
2000
|
+
private url;
|
|
2001
|
+
private reconnectInterval;
|
|
2002
|
+
private maxReconnectAttempts;
|
|
2003
|
+
private queueProcessInterval;
|
|
2004
|
+
private ws;
|
|
2005
|
+
private _state;
|
|
2006
|
+
private reconnectAttempts;
|
|
2007
|
+
private shouldReconnect;
|
|
2008
|
+
private inboundQueue;
|
|
2009
|
+
private outboundQueue;
|
|
2010
|
+
private isProcessing;
|
|
2011
|
+
private processIntervalId;
|
|
2012
|
+
private messageHandler;
|
|
2013
|
+
private resubscribeHandler;
|
|
2014
|
+
private stateChangeHandlers;
|
|
2015
|
+
private errorHandlers;
|
|
2016
|
+
constructor(options: WebSocketClientOptions);
|
|
2017
|
+
get state(): WebSocketConnectionState;
|
|
2018
|
+
get isConnected(): boolean;
|
|
2019
|
+
private setState;
|
|
2020
|
+
private emitError;
|
|
2021
|
+
connect(): Promise<void>;
|
|
2022
|
+
private doConnect;
|
|
2023
|
+
disconnect(): void;
|
|
2024
|
+
send(message: string): Promise<void>;
|
|
2025
|
+
enqueueOutbound(prepareMessage: () => Promise<string[]>): void;
|
|
2026
|
+
subscribe(inboxAddresses: string[]): Promise<void>;
|
|
2027
|
+
unsubscribe(inboxAddresses: string[]): Promise<void>;
|
|
2028
|
+
setMessageHandler(handler: MessageHandler): void;
|
|
2029
|
+
setResubscribeHandler(handler: () => Promise<void>): void;
|
|
2030
|
+
onStateChange(handler: StateChangeHandler): () => void;
|
|
2031
|
+
onError(handler: ErrorHandler): () => void;
|
|
2032
|
+
private startQueueProcessing;
|
|
2033
|
+
private stopQueueProcessing;
|
|
2034
|
+
private processQueues;
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Factory function to create a browser WebSocket client
|
|
2038
|
+
*/
|
|
2039
|
+
declare function createBrowserWebSocketClient(options: WebSocketClientOptions): WebSocketClient;
|
|
2040
|
+
|
|
2041
|
+
/**
|
|
2042
|
+
* React Native WebSocket client implementation
|
|
2043
|
+
*
|
|
2044
|
+
* Uses the React Native WebSocket API which is similar to the browser's.
|
|
2045
|
+
* Implements automatic reconnection, dual queue system, and subscription management.
|
|
2046
|
+
*
|
|
2047
|
+
* Note: This implementation is nearly identical to browser-websocket.ts
|
|
2048
|
+
* but kept separate to allow for React Native-specific optimizations if needed.
|
|
2049
|
+
*/
|
|
2050
|
+
|
|
2051
|
+
/**
|
|
2052
|
+
* RNWebSocketClient - WebSocket implementation for React Native
|
|
2053
|
+
*
|
|
2054
|
+
* Features:
|
|
2055
|
+
* - Automatic reconnection with configurable interval
|
|
2056
|
+
* - Dual queue system (inbound messages, outbound message generators)
|
|
2057
|
+
* - Inbox address subscription management
|
|
2058
|
+
* - Periodic queue processing
|
|
2059
|
+
*/
|
|
2060
|
+
declare class RNWebSocketClient implements WebSocketClient {
|
|
2061
|
+
private url;
|
|
2062
|
+
private reconnectInterval;
|
|
2063
|
+
private maxReconnectAttempts;
|
|
2064
|
+
private queueProcessInterval;
|
|
2065
|
+
private ws;
|
|
2066
|
+
private _state;
|
|
2067
|
+
private reconnectAttempts;
|
|
2068
|
+
private shouldReconnect;
|
|
2069
|
+
private inboundQueue;
|
|
2070
|
+
private outboundQueue;
|
|
2071
|
+
private isProcessing;
|
|
2072
|
+
private processIntervalId;
|
|
2073
|
+
private messageHandler;
|
|
2074
|
+
private resubscribeHandler;
|
|
2075
|
+
private stateChangeHandlers;
|
|
2076
|
+
private errorHandlers;
|
|
2077
|
+
constructor(options: WebSocketClientOptions);
|
|
2078
|
+
get state(): WebSocketConnectionState;
|
|
2079
|
+
get isConnected(): boolean;
|
|
2080
|
+
private setState;
|
|
2081
|
+
private emitError;
|
|
2082
|
+
connect(): Promise<void>;
|
|
2083
|
+
private doConnect;
|
|
2084
|
+
disconnect(): void;
|
|
2085
|
+
send(message: string): Promise<void>;
|
|
2086
|
+
enqueueOutbound(prepareMessage: () => Promise<string[]>): void;
|
|
2087
|
+
subscribe(inboxAddresses: string[]): Promise<void>;
|
|
2088
|
+
unsubscribe(inboxAddresses: string[]): Promise<void>;
|
|
2089
|
+
setMessageHandler(handler: MessageHandler): void;
|
|
2090
|
+
setResubscribeHandler(handler: () => Promise<void>): void;
|
|
2091
|
+
onStateChange(handler: StateChangeHandler): () => void;
|
|
2092
|
+
onError(handler: ErrorHandler): () => void;
|
|
2093
|
+
private startQueueProcessing;
|
|
2094
|
+
private stopQueueProcessing;
|
|
2095
|
+
private processQueues;
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Factory function to create a React Native WebSocket client
|
|
2099
|
+
*/
|
|
2100
|
+
declare function createRNWebSocketClient(options: WebSocketClientOptions): WebSocketClient;
|
|
2101
|
+
|
|
2102
|
+
/**
|
|
2103
|
+
* Sync Utility Functions
|
|
2104
|
+
*
|
|
2105
|
+
* Hash computation, digest creation, and delta calculation for sync protocol.
|
|
2106
|
+
*/
|
|
2107
|
+
|
|
2108
|
+
/** Maximum chunk size for message transmission (5MB) */
|
|
2109
|
+
declare const MAX_CHUNK_SIZE: number;
|
|
2110
|
+
/** Default sync request expiry (30 seconds) */
|
|
2111
|
+
declare const DEFAULT_SYNC_EXPIRY_MS = 30000;
|
|
2112
|
+
/** Aggressive sync timeout after receiving first response (1 second) */
|
|
2113
|
+
declare const AGGRESSIVE_SYNC_TIMEOUT_MS = 1000;
|
|
2114
|
+
/**
|
|
2115
|
+
* Compute SHA-256 hash of a string
|
|
2116
|
+
*/
|
|
2117
|
+
declare function computeHash(data: string): string;
|
|
2118
|
+
/**
|
|
2119
|
+
* Compute hash of message content for comparison.
|
|
2120
|
+
* Uses canonical representation: senderId + type + content-specific fields
|
|
2121
|
+
*/
|
|
2122
|
+
declare function computeContentHash(message: Message): string;
|
|
2123
|
+
/**
|
|
2124
|
+
* Compute hash of reaction state for a message
|
|
2125
|
+
*/
|
|
2126
|
+
declare function computeReactionHash(reactions: Reaction[]): string;
|
|
2127
|
+
/**
|
|
2128
|
+
* Compute hash of member's mutable fields
|
|
2129
|
+
*/
|
|
2130
|
+
declare function computeMemberHash(member: SpaceMember): {
|
|
2131
|
+
displayNameHash: string;
|
|
2132
|
+
iconHash: string;
|
|
2133
|
+
};
|
|
2134
|
+
/**
|
|
2135
|
+
* Compute manifest hash for quick comparison.
|
|
2136
|
+
* Hash of sorted message IDs.
|
|
2137
|
+
*/
|
|
2138
|
+
declare function computeManifestHash(digests: MessageDigest[]): string;
|
|
2139
|
+
/**
|
|
2140
|
+
* Create digest from message
|
|
2141
|
+
*/
|
|
2142
|
+
declare function createMessageDigest(message: Message): MessageDigest;
|
|
2143
|
+
/**
|
|
2144
|
+
* Create reaction digest for a message
|
|
2145
|
+
*/
|
|
2146
|
+
declare function createReactionDigest(messageId: string, reactions: Reaction[]): ReactionDigest[];
|
|
2147
|
+
/**
|
|
2148
|
+
* Create manifest from messages
|
|
2149
|
+
*/
|
|
2150
|
+
declare function createManifest(spaceId: string, channelId: string, messages: Message[]): SyncManifest;
|
|
2151
|
+
/**
|
|
2152
|
+
* Create member digest
|
|
2153
|
+
*/
|
|
2154
|
+
declare function createMemberDigest(member: SpaceMember): MemberDigest;
|
|
2155
|
+
interface MessageDiffResult {
|
|
2156
|
+
/** Message IDs we don't have */
|
|
2157
|
+
missingIds: string[];
|
|
2158
|
+
/** Message IDs we have but are outdated (content changed) */
|
|
2159
|
+
outdatedIds: string[];
|
|
2160
|
+
/** Message IDs we have but they don't (we should send to them) */
|
|
2161
|
+
extraIds: string[];
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Compare manifests and determine what messages differ
|
|
2165
|
+
*/
|
|
2166
|
+
declare function computeMessageDiff(ourManifest: SyncManifest, theirManifest: SyncManifest): MessageDiffResult;
|
|
2167
|
+
interface ReactionDiffResult {
|
|
2168
|
+
/** Reactions to add: { messageId, emojiId, memberIds to add } */
|
|
2169
|
+
toAdd: Array<{
|
|
2170
|
+
messageId: string;
|
|
2171
|
+
emojiId: string;
|
|
2172
|
+
memberIds: string[];
|
|
2173
|
+
}>;
|
|
2174
|
+
/** Reactions to remove: { messageId, emojiId, memberIds to remove } */
|
|
2175
|
+
toRemove: Array<{
|
|
2176
|
+
messageId: string;
|
|
2177
|
+
emojiId: string;
|
|
2178
|
+
memberIds: string[];
|
|
2179
|
+
}>;
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Compare reaction digests and determine differences.
|
|
2183
|
+
* This requires the full reaction data to compute actual member diffs.
|
|
2184
|
+
*/
|
|
2185
|
+
declare function computeReactionDiff(ourReactions: Map<string, Reaction[]>, // messageId -> reactions
|
|
2186
|
+
theirDigests: ReactionDigest[]): ReactionDiffResult;
|
|
2187
|
+
interface MemberDiffResult {
|
|
2188
|
+
/** Member addresses we don't have */
|
|
2189
|
+
missingAddresses: string[];
|
|
2190
|
+
/** Member addresses where our data is outdated */
|
|
2191
|
+
outdatedAddresses: string[];
|
|
2192
|
+
/** Member addresses we have that they don't */
|
|
2193
|
+
extraAddresses: string[];
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Compare member digests and determine differences
|
|
2197
|
+
*/
|
|
2198
|
+
declare function computeMemberDiff(ourDigests: MemberDigest[], theirDigests: MemberDigest[]): MemberDiffResult;
|
|
2199
|
+
interface PeerDiffResult {
|
|
2200
|
+
/** Peer IDs they have that we don't */
|
|
2201
|
+
missingPeerIds: number[];
|
|
2202
|
+
/** Peer IDs we have that they don't */
|
|
2203
|
+
extraPeerIds: number[];
|
|
2204
|
+
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Compare peer ID sets
|
|
2207
|
+
*/
|
|
2208
|
+
declare function computePeerDiff(ourPeerIds: number[], theirPeerIds: number[]): PeerDiffResult;
|
|
2209
|
+
/**
|
|
2210
|
+
* Build message delta from diff result and message lookup
|
|
2211
|
+
*/
|
|
2212
|
+
declare function buildMessageDelta(spaceId: string, channelId: string, diff: MessageDiffResult, messageMap: Map<string, Message>, tombstones: DeletedMessageTombstone[]): MessageDelta;
|
|
2213
|
+
/**
|
|
2214
|
+
* Build reaction delta for messages
|
|
2215
|
+
*/
|
|
2216
|
+
declare function buildReactionDelta(spaceId: string, channelId: string, messageMap: Map<string, Message>, messageIds: string[]): ReactionDelta;
|
|
2217
|
+
/**
|
|
2218
|
+
* Build member delta from diff result and member lookup
|
|
2219
|
+
*/
|
|
2220
|
+
declare function buildMemberDelta(spaceId: string, diff: MemberDiffResult, memberMap: Map<string, SpaceMember>): MemberDelta;
|
|
2221
|
+
/**
|
|
2222
|
+
* Chunk messages for transmission to stay under size limit
|
|
2223
|
+
*/
|
|
2224
|
+
declare function chunkMessages(messages: Message[]): Message[][];
|
|
2225
|
+
/**
|
|
2226
|
+
* Chunk members for transmission
|
|
2227
|
+
*/
|
|
2228
|
+
declare function chunkMembers(members: SpaceMember[]): SpaceMember[][];
|
|
2229
|
+
/**
|
|
2230
|
+
* Create sync summary from messages and members
|
|
2231
|
+
*/
|
|
2232
|
+
declare function createSyncSummary(messages: Message[], memberCount: number): {
|
|
2233
|
+
memberCount: number;
|
|
2234
|
+
messageCount: number;
|
|
2235
|
+
newestMessageTimestamp: number;
|
|
2236
|
+
oldestMessageTimestamp: number;
|
|
2237
|
+
manifestHash: string;
|
|
2238
|
+
};
|
|
2239
|
+
|
|
2240
|
+
/**
|
|
2241
|
+
* SyncService
|
|
2242
|
+
*
|
|
2243
|
+
* Platform-agnostic sync orchestration logic.
|
|
2244
|
+
* Handles sync protocol without encryption (that's platform-specific).
|
|
2245
|
+
*
|
|
2246
|
+
* Usage:
|
|
2247
|
+
* 1. Platform creates SyncService with storage adapter
|
|
2248
|
+
* 2. Platform calls build* methods to create payloads
|
|
2249
|
+
* 3. Platform handles encryption and transmission
|
|
2250
|
+
* 4. Platform calls apply* methods to process received data
|
|
2251
|
+
*/
|
|
2252
|
+
|
|
2253
|
+
interface SyncServiceConfig {
|
|
2254
|
+
storage: StorageAdapter;
|
|
2255
|
+
/** Maximum messages to include in sync (default: 1000) */
|
|
2256
|
+
maxMessages?: number;
|
|
2257
|
+
/** Sync request expiry in ms (default: 30000) */
|
|
2258
|
+
requestExpiry?: number;
|
|
2259
|
+
/** Callback when sync should be initiated */
|
|
2260
|
+
onInitiateSync?: (spaceId: string, target: string) => void;
|
|
2261
|
+
/** Cache TTL in ms (default: 5000) */
|
|
2262
|
+
cacheTtl?: number;
|
|
2263
|
+
}
|
|
2264
|
+
declare class SyncService {
|
|
2265
|
+
private storage;
|
|
2266
|
+
private maxMessages;
|
|
2267
|
+
private requestExpiry;
|
|
2268
|
+
private onInitiateSync?;
|
|
2269
|
+
/** Active sync sessions by spaceId */
|
|
2270
|
+
private sessions;
|
|
2271
|
+
/** Deleted message tombstones (caller must persist these) */
|
|
2272
|
+
private tombstones;
|
|
2273
|
+
/** Pre-computed sync payload cache per space:channel - always ready to use */
|
|
2274
|
+
private payloadCache;
|
|
2275
|
+
constructor(config: SyncServiceConfig);
|
|
2276
|
+
/**
|
|
2277
|
+
* Get cache key for space/channel
|
|
2278
|
+
*/
|
|
2279
|
+
private getCacheKey;
|
|
2280
|
+
/**
|
|
2281
|
+
* Get or initialize the payload cache for a space/channel.
|
|
2282
|
+
* If not cached, loads from storage and builds the payload once.
|
|
2283
|
+
*/
|
|
2284
|
+
private getPayloadCache;
|
|
2285
|
+
/**
|
|
2286
|
+
* Build the payload cache from messages and members
|
|
2287
|
+
*/
|
|
2288
|
+
private buildPayloadCache;
|
|
2289
|
+
/**
|
|
2290
|
+
* Rebuild derived fields (manifest, digests, summary) from the maps
|
|
2291
|
+
*/
|
|
2292
|
+
private rebuildPayloadCache;
|
|
2293
|
+
/**
|
|
2294
|
+
* Invalidate cache for a space/channel (forces reload from storage on next access)
|
|
2295
|
+
*/
|
|
2296
|
+
invalidateCache(spaceId: string, channelId?: string): void;
|
|
2297
|
+
/**
|
|
2298
|
+
* Update cache with a new/updated message (incremental update - no storage query)
|
|
2299
|
+
*/
|
|
2300
|
+
updateCacheWithMessage(spaceId: string, channelId: string, message: Message): void;
|
|
2301
|
+
/**
|
|
2302
|
+
* Remove a message from cache (incremental update - no storage query)
|
|
2303
|
+
*/
|
|
2304
|
+
removeCacheMessage(spaceId: string, channelId: string, messageId: string): void;
|
|
2305
|
+
/**
|
|
2306
|
+
* Update cache with a new/updated member (incremental update - no storage query)
|
|
2307
|
+
*/
|
|
2308
|
+
updateCacheWithMember(spaceId: string, channelId: string, member: SpaceMember): void;
|
|
2309
|
+
/**
|
|
2310
|
+
* Check if cache exists for a space/channel
|
|
2311
|
+
*/
|
|
2312
|
+
hasCachedPayload(spaceId: string, channelId: string): boolean;
|
|
2313
|
+
/**
|
|
2314
|
+
* Check if a sync session is active for a space
|
|
2315
|
+
*/
|
|
2316
|
+
hasActiveSession(spaceId: string): boolean;
|
|
2317
|
+
/**
|
|
2318
|
+
* Check if sync is in progress for a space
|
|
2319
|
+
*/
|
|
2320
|
+
isSyncInProgress(spaceId: string): boolean;
|
|
2321
|
+
/**
|
|
2322
|
+
* Mark sync as in progress
|
|
2323
|
+
*/
|
|
2324
|
+
setSyncInProgress(spaceId: string, inProgress: boolean): void;
|
|
2325
|
+
/**
|
|
2326
|
+
* Set the sync target (who we're syncing with)
|
|
2327
|
+
*/
|
|
2328
|
+
setSyncTarget(spaceId: string, targetInbox: string): void;
|
|
2329
|
+
/**
|
|
2330
|
+
* Get the sync target for a space
|
|
2331
|
+
*/
|
|
2332
|
+
getSyncTarget(spaceId: string): string | undefined;
|
|
2333
|
+
/**
|
|
2334
|
+
* Build sync-request payload to broadcast via hub
|
|
2335
|
+
*/
|
|
2336
|
+
buildSyncRequest(spaceId: string, channelId: string, inboxAddress: string): Promise<SyncRequestPayload>;
|
|
2337
|
+
/**
|
|
2338
|
+
* Schedule sync initiation after timeout
|
|
2339
|
+
*/
|
|
2340
|
+
scheduleSyncInitiation(spaceId: string, callback: () => void, timeoutMs?: number): void;
|
|
2341
|
+
/**
|
|
2342
|
+
* Build sync-info response if we have useful data.
|
|
2343
|
+
* Returns null if we have nothing to offer or are already in sync.
|
|
2344
|
+
*/
|
|
2345
|
+
buildSyncInfo(spaceId: string, channelId: string, inboxAddress: string, theirSummary: SyncSummary): Promise<SyncInfoPayload | null>;
|
|
2346
|
+
/**
|
|
2347
|
+
* Add candidate from sync-info response
|
|
2348
|
+
*/
|
|
2349
|
+
addCandidate(spaceId: string, candidate: SyncCandidate): void;
|
|
2350
|
+
/**
|
|
2351
|
+
* Select best candidate based on data availability
|
|
2352
|
+
*/
|
|
2353
|
+
selectBestCandidate(spaceId: string): SyncCandidate | null;
|
|
2354
|
+
/**
|
|
2355
|
+
* Build sync-initiate payload for selected peer
|
|
2356
|
+
*/
|
|
2357
|
+
buildSyncInitiate(spaceId: string, channelId: string, inboxAddress: string, peerIds: number[]): Promise<{
|
|
2358
|
+
target: string;
|
|
2359
|
+
payload: SyncInitiatePayload;
|
|
2360
|
+
} | null>;
|
|
2361
|
+
/**
|
|
2362
|
+
* Build sync-manifest response to sync-initiate
|
|
2363
|
+
*/
|
|
2364
|
+
buildSyncManifest(spaceId: string, channelId: string, peerIds: number[], inboxAddress: string): Promise<SyncManifestPayload>;
|
|
2365
|
+
/**
|
|
2366
|
+
* Build sync-delta payloads based on manifest comparison.
|
|
2367
|
+
* May return multiple payloads for chunking.
|
|
2368
|
+
*/
|
|
2369
|
+
buildSyncDelta(spaceId: string, channelId: string, theirManifest: SyncManifest, theirMemberDigests: MemberDigest[], theirPeerIds: number[], ourPeerEntries: Map<number, PeerEntry>): Promise<SyncDeltaPayload[]>;
|
|
2370
|
+
/**
|
|
2371
|
+
* Apply received message delta to local storage
|
|
2372
|
+
*/
|
|
2373
|
+
applyMessageDelta(delta: MessageDelta): Promise<void>;
|
|
2374
|
+
/**
|
|
2375
|
+
* Apply received reaction delta to local storage.
|
|
2376
|
+
* This updates the reactions on existing messages.
|
|
2377
|
+
*/
|
|
2378
|
+
applyReactionDelta(delta: ReactionDelta): Promise<void>;
|
|
2379
|
+
/**
|
|
2380
|
+
* Apply received member delta to local storage
|
|
2381
|
+
*/
|
|
2382
|
+
applyMemberDelta(delta: MemberDelta): Promise<void>;
|
|
2383
|
+
/**
|
|
2384
|
+
* Apply full sync delta
|
|
2385
|
+
*/
|
|
2386
|
+
applySyncDelta(delta: SyncDeltaPayload): Promise<void>;
|
|
2387
|
+
/**
|
|
2388
|
+
* Record a deleted message tombstone
|
|
2389
|
+
*/
|
|
2390
|
+
addTombstone(tombstone: DeletedMessageTombstone): void;
|
|
2391
|
+
/**
|
|
2392
|
+
* Get all tombstones (for persistence by caller)
|
|
2393
|
+
*/
|
|
2394
|
+
getTombstones(): DeletedMessageTombstone[];
|
|
2395
|
+
/**
|
|
2396
|
+
* Load tombstones (from caller's persistence)
|
|
2397
|
+
*/
|
|
2398
|
+
loadTombstones(tombstones: DeletedMessageTombstone[]): void;
|
|
2399
|
+
/**
|
|
2400
|
+
* Clean up old tombstones (older than 30 days)
|
|
2401
|
+
*/
|
|
2402
|
+
cleanupTombstones(maxAgeMs?: number): void;
|
|
2403
|
+
private getChannelMessages;
|
|
2404
|
+
/**
|
|
2405
|
+
* Clean up expired sessions
|
|
2406
|
+
*/
|
|
2407
|
+
cleanupSessions(): void;
|
|
2408
|
+
/**
|
|
2409
|
+
* Cancel active sync for a space
|
|
2410
|
+
*/
|
|
2411
|
+
cancelSync(spaceId: string): void;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
export { AGGRESSIVE_SYNC_TIMEOUT_MS, type AddReactionParams, type ApiConfig, ApiError, ApiErrorCode, type ApiErrorDetails, BOOKMARKS_CONFIG, type Bookmark, BrowserWebSocketClient, type Channel, type ChannelWasmModule, type ChannelsKey, type Conversation, type ConversationInboxKeypair, type ConversationSource, type CreateWebSocketClient, type CryptoProvider, DEFAULT_SYNC_EXPIRY_MS, type DeleteConversationMessage, type DeleteMessageParams, type DeletedMessageTombstone, type DeviceKeys, type DoubleRatchetStateAndEnvelope, type DoubleRatchetStateAndMessage, ENCRYPTION_STORAGE_KEYS, type Ed448Keypair, type EditMessage, type EditMessageParams, type EmbedMessage, type Emoji, type EncryptedEnvelope, type EncryptedWebSocketMessage, type EncryptionState, type EncryptionStateStorageInterface, type ErrorHandler, type EventMessage, type FolderColor, type GetMessagesParams, type GetMessagesResult, type Group, type HttpMethod, type InboxMapping, type InboxMessageDecryptRequest, type InboxMessageEncryptRequest, type InitializationEnvelope, type JoinMessage, type KeyValueStorageProvider, type Keypair, type KickMessage, type LatestState, type LeaveMessage, type ListenMessage, MAX_CHUNK_SIZE, MAX_MENTIONS, MAX_MESSAGE_LENGTH, MENTION_PATTERNS, type MemberDelta, type MemberDiffResult, type MemberDigest, type Mentions, type Message, type MessageCiphertext, type MessageContent, type MessageDelta, type MessageDiffResult, type MessageDigest, type MessageHandler, type MessageListParams, type MessageSendStatus, type MessagesInfiniteKey, type MuteMessage, type NavItem, type NewDoubleRatchetParams, type NewTripleRatchetParams, type NotificationSettings, type OutboundWebSocketMessage, type P2PChannelEnvelope, type PaginationParams, type ParsedMention, type PeerDiffResult, type PeerEntry, type PeerInfo, type PeerMapDelta, type Permission, type PinMessage, type PostMessage, type QuorumApiClient, RNWebSocketClient, type Reaction, type ReactionDelta, type ReactionDiffResult, type ReactionDigest, type ReactionMessage, type ReceiverX3DHParams, type ReceivingInbox, type RecipientInfo, type RemoveMessage, type RemoveReactionMessage, type RemoveReactionParams, type RequestOptions, type Role, type SealedMessage, type SendDirectMessageParams, type SendMessageParams, type SenderX3DHParams, type SendingInbox, type SignedMessage, type SigningProvider, type Space, type SpaceDetailKey, type SpaceMember, type SpacesKey, type StateChangeHandler, type Sticker, type StickerMessage, type StorageAdapter, type SyncCandidate, type SyncControlPayload, type SyncDeltaPayload, type SyncInfoPayload, type SyncInitiatePayload, type SyncManifest, type SyncManifestPayload, type SyncRequestPayload, SyncService, type SyncServiceConfig, type SyncSession, type SyncSummary, type TransportClient, type TransportConfig, type TransportRequestOptions, type TransportResponse, type TripleRatchetStateAndEnvelope, type TripleRatchetStateAndMessage, type TripleRatchetStateAndMetadata, type UnlistenMessage, type UnsealedEnvelope, type UpdateProfileMessage, type UseChannelsOptions, type UseDeleteMessageOptions, type UseEditMessageOptions, type UseMessagesOptions, type UseReactionOptions, type UseSendMessageOptions, type UseSpaceOptions, type UseSpacesOptions, type UserConfig, type UserProfile, type ValidationResult, WasmCryptoProvider, WasmSigningProvider, type WebSocketClient, type WebSocketClientOptions, type WebSocketConnectionState, type X448Keypair, base64ToBytes, buildMemberDelta, buildMessageDelta, buildReactionDelta, bytesToBase64, bytesToHex, bytesToString, chunkMembers, chunkMessages, computeContentHash, computeHash, computeManifestHash, computeMemberDiff, computeMemberHash, computeMessageDiff, computePeerDiff, computeReactionDiff, computeReactionHash, createApiError, createBrowserWebSocketClient, createManifest, createMemberDigest, createMessageDigest, createNetworkError, createRNWebSocketClient, createReactionDigest, createSignedMessage, createSyncSummary, endpoints, extractMentions, findChannel, flattenChannels, flattenMessages, formatDate, formatDateTime, formatFileSize, formatMemberCount, formatMention, formatMessageDate, formatRelativeTime, formatTime, hexToBytes, int64ToBytes, isSameDay, isSyncDelta, isSyncInfo, isSyncInitiate, isSyncManifest, isSyncRequest, logger, parseMentions, queryKeys, sanitizeContent, stringToBytes, truncateText, useAddReaction, useChannels, useDeleteMessage, useEditMessage, useInvalidateMessages, useMessages, useRemoveReaction, useSendMessage, useSpace, useSpaceMembers, useSpaces, validateMessage, validateMessageContent, verifySignedMessage };
|