@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.
Files changed (51) hide show
  1. package/dist/index.d.mts +2414 -0
  2. package/dist/index.d.ts +2414 -0
  3. package/dist/index.js +2788 -0
  4. package/dist/index.mjs +2678 -0
  5. package/package.json +49 -0
  6. package/src/api/client.ts +86 -0
  7. package/src/api/endpoints.ts +87 -0
  8. package/src/api/errors.ts +179 -0
  9. package/src/api/index.ts +35 -0
  10. package/src/crypto/encryption-state.ts +249 -0
  11. package/src/crypto/index.ts +55 -0
  12. package/src/crypto/types.ts +307 -0
  13. package/src/crypto/wasm-provider.ts +298 -0
  14. package/src/hooks/index.ts +31 -0
  15. package/src/hooks/keys.ts +62 -0
  16. package/src/hooks/mutations/index.ts +15 -0
  17. package/src/hooks/mutations/useDeleteMessage.ts +67 -0
  18. package/src/hooks/mutations/useEditMessage.ts +87 -0
  19. package/src/hooks/mutations/useReaction.ts +163 -0
  20. package/src/hooks/mutations/useSendMessage.ts +131 -0
  21. package/src/hooks/useChannels.ts +49 -0
  22. package/src/hooks/useMessages.ts +77 -0
  23. package/src/hooks/useSpaces.ts +60 -0
  24. package/src/index.ts +32 -0
  25. package/src/signing/index.ts +10 -0
  26. package/src/signing/types.ts +83 -0
  27. package/src/signing/wasm-provider.ts +75 -0
  28. package/src/storage/adapter.ts +118 -0
  29. package/src/storage/index.ts +9 -0
  30. package/src/sync/index.ts +83 -0
  31. package/src/sync/service.test.ts +822 -0
  32. package/src/sync/service.ts +947 -0
  33. package/src/sync/types.ts +267 -0
  34. package/src/sync/utils.ts +588 -0
  35. package/src/transport/browser-websocket.ts +299 -0
  36. package/src/transport/index.ts +34 -0
  37. package/src/transport/rn-websocket.ts +321 -0
  38. package/src/transport/types.ts +56 -0
  39. package/src/transport/websocket.ts +212 -0
  40. package/src/types/bookmark.ts +29 -0
  41. package/src/types/conversation.ts +25 -0
  42. package/src/types/index.ts +57 -0
  43. package/src/types/message.ts +178 -0
  44. package/src/types/space.ts +75 -0
  45. package/src/types/user.ts +72 -0
  46. package/src/utils/encoding.ts +106 -0
  47. package/src/utils/formatting.ts +139 -0
  48. package/src/utils/index.ts +9 -0
  49. package/src/utils/logger.ts +141 -0
  50. package/src/utils/mentions.ts +135 -0
  51. package/src/utils/validation.ts +84 -0
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@quilibrium/quorum-shared",
3
+ "version": "2.1.0-1",
4
+ "description": "Shared types, hooks, and utilities for Quorum mobile and desktop apps",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
17
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
18
+ "lint": "eslint src --ext .ts,.tsx",
19
+ "typecheck": "tsc --noEmit",
20
+ "test": "vitest",
21
+ "test:run": "vitest run"
22
+ },
23
+ "dependencies": {
24
+ "@noble/hashes": "^1.3.0"
25
+ },
26
+ "peerDependencies": {
27
+ "@tanstack/react-query": ">=5.0.0",
28
+ "react": ">=18.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@tanstack/react-query": "^5.62.0",
32
+ "@types/react": "^18.2.0",
33
+ "@vitest/coverage-v8": "^4.0.16",
34
+ "react": "^18.2.0",
35
+ "tsup": "^8.0.0",
36
+ "typescript": "^5.3.0",
37
+ "vitest": "^4.0.16"
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "src"
42
+ ],
43
+ "keywords": [
44
+ "quorum",
45
+ "chat",
46
+ "shared"
47
+ ],
48
+ "license": "MIT"
49
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * QuorumApiClient interface
3
+ *
4
+ * Platform-agnostic API client that can be implemented differently
5
+ * for mobile (fetch) and desktop (Electron IPC or fetch)
6
+ */
7
+
8
+ import type { Space, Message, Conversation } from '../types';
9
+
10
+ export interface SendMessageParams {
11
+ spaceId: string;
12
+ channelId: string;
13
+ text: string;
14
+ repliesToMessageId?: string;
15
+ }
16
+
17
+ export interface AddReactionParams {
18
+ spaceId: string;
19
+ channelId: string;
20
+ messageId: string;
21
+ reaction: string;
22
+ }
23
+
24
+ export interface RemoveReactionParams {
25
+ spaceId: string;
26
+ channelId: string;
27
+ messageId: string;
28
+ reaction: string;
29
+ }
30
+
31
+ export interface EditMessageParams {
32
+ spaceId: string;
33
+ channelId: string;
34
+ messageId: string;
35
+ text: string;
36
+ }
37
+
38
+ export interface DeleteMessageParams {
39
+ spaceId: string;
40
+ channelId: string;
41
+ messageId: string;
42
+ }
43
+
44
+ export interface SendDirectMessageParams {
45
+ conversationId: string;
46
+ text: string;
47
+ repliesToMessageId?: string;
48
+ }
49
+
50
+ export interface QuorumApiClient {
51
+ // Spaces
52
+ fetchSpaces(): Promise<Space[]>;
53
+ fetchSpace(spaceId: string): Promise<Space>;
54
+ joinSpace(inviteCode: string): Promise<Space>;
55
+
56
+ // Messages
57
+ fetchMessages(params: {
58
+ spaceId: string;
59
+ channelId: string;
60
+ cursor?: string;
61
+ limit?: number;
62
+ }): Promise<{ messages: Message[]; nextPageToken?: string }>;
63
+
64
+ sendMessage(params: SendMessageParams): Promise<Message>;
65
+ editMessage(params: EditMessageParams): Promise<Message>;
66
+ deleteMessage(params: DeleteMessageParams): Promise<void>;
67
+
68
+ // Reactions
69
+ addReaction(params: AddReactionParams): Promise<void>;
70
+ removeReaction(params: RemoveReactionParams): Promise<void>;
71
+
72
+ // Conversations
73
+ fetchConversations(): Promise<Conversation[]>;
74
+ createConversation(params: { address: string }): Promise<Conversation>;
75
+ sendDirectMessage(params: SendDirectMessageParams): Promise<Message>;
76
+ fetchDirectMessages(params: {
77
+ conversationId: string;
78
+ cursor?: string;
79
+ limit?: number;
80
+ }): Promise<{ messages: Message[]; nextPageToken?: string }>;
81
+
82
+ // Pinning
83
+ pinMessage(params: { spaceId: string; channelId: string; messageId: string }): Promise<void>;
84
+ unpinMessage(params: { spaceId: string; channelId: string; messageId: string }): Promise<void>;
85
+ }
86
+
@@ -0,0 +1,87 @@
1
+ /**
2
+ * API endpoint definitions
3
+ */
4
+
5
+ /** Base API configuration */
6
+ export interface ApiConfig {
7
+ baseUrl: string;
8
+ timeout?: number;
9
+ }
10
+
11
+ /** API endpoint builders */
12
+ export const endpoints = {
13
+ // Spaces
14
+ spaces: {
15
+ list: () => '/spaces',
16
+ detail: (spaceId: string) => `/spaces/${spaceId}`,
17
+ members: (spaceId: string) => `/spaces/${spaceId}/members`,
18
+ join: (spaceId: string) => `/spaces/${spaceId}/join`,
19
+ leave: (spaceId: string) => `/spaces/${spaceId}/leave`,
20
+ },
21
+
22
+ // Channels
23
+ channels: {
24
+ list: (spaceId: string) => `/spaces/${spaceId}/channels`,
25
+ detail: (spaceId: string, channelId: string) =>
26
+ `/spaces/${spaceId}/channels/${channelId}`,
27
+ },
28
+
29
+ // Messages
30
+ messages: {
31
+ list: (spaceId: string, channelId: string) =>
32
+ `/spaces/${spaceId}/channels/${channelId}/messages`,
33
+ detail: (spaceId: string, channelId: string, messageId: string) =>
34
+ `/spaces/${spaceId}/channels/${channelId}/messages/${messageId}`,
35
+ send: (spaceId: string, channelId: string) =>
36
+ `/spaces/${spaceId}/channels/${channelId}/messages`,
37
+ react: (spaceId: string, channelId: string, messageId: string) =>
38
+ `/spaces/${spaceId}/channels/${channelId}/messages/${messageId}/reactions`,
39
+ pin: (spaceId: string, channelId: string, messageId: string) =>
40
+ `/spaces/${spaceId}/channels/${channelId}/messages/${messageId}/pin`,
41
+ },
42
+
43
+ // Conversations (DMs)
44
+ conversations: {
45
+ list: () => '/conversations',
46
+ detail: (conversationId: string) => `/conversations/${conversationId}`,
47
+ messages: (conversationId: string) => `/conversations/${conversationId}/messages`,
48
+ },
49
+
50
+ // User
51
+ user: {
52
+ config: () => '/user/config',
53
+ profile: () => '/user/profile',
54
+ notifications: () => '/user/notifications',
55
+ },
56
+
57
+ // Search
58
+ search: {
59
+ messages: (spaceId: string) => `/spaces/${spaceId}/search/messages`,
60
+ members: (spaceId: string) => `/spaces/${spaceId}/search/members`,
61
+ },
62
+ } as const;
63
+
64
+ /** HTTP methods */
65
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
66
+
67
+ /** Request options */
68
+ export interface RequestOptions {
69
+ method?: HttpMethod;
70
+ headers?: Record<string, string>;
71
+ body?: unknown;
72
+ timeout?: number;
73
+ signal?: AbortSignal;
74
+ }
75
+
76
+ /** Pagination parameters */
77
+ export interface PaginationParams {
78
+ cursor?: string;
79
+ limit?: number;
80
+ }
81
+
82
+ /** Message list parameters */
83
+ export interface MessageListParams extends PaginationParams {
84
+ before?: string;
85
+ after?: string;
86
+ around?: string;
87
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * API error classes
3
+ */
4
+
5
+ /** API error codes */
6
+ export enum ApiErrorCode {
7
+ // Network errors
8
+ NETWORK_ERROR = 'NETWORK_ERROR',
9
+ TIMEOUT = 'TIMEOUT',
10
+
11
+ // Auth errors
12
+ UNAUTHORIZED = 'UNAUTHORIZED',
13
+ FORBIDDEN = 'FORBIDDEN',
14
+ TOKEN_EXPIRED = 'TOKEN_EXPIRED',
15
+
16
+ // Validation errors
17
+ VALIDATION_ERROR = 'VALIDATION_ERROR',
18
+ BAD_REQUEST = 'BAD_REQUEST',
19
+
20
+ // Resource errors
21
+ NOT_FOUND = 'NOT_FOUND',
22
+ CONFLICT = 'CONFLICT',
23
+
24
+ // Rate limiting
25
+ RATE_LIMITED = 'RATE_LIMITED',
26
+
27
+ // Server errors
28
+ SERVER_ERROR = 'SERVER_ERROR',
29
+ SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
30
+
31
+ // Unknown
32
+ UNKNOWN = 'UNKNOWN',
33
+ }
34
+
35
+ /** API error details */
36
+ export interface ApiErrorDetails {
37
+ code: ApiErrorCode;
38
+ message: string;
39
+ status?: number;
40
+ field?: string;
41
+ retryAfter?: number;
42
+ originalError?: Error;
43
+ }
44
+
45
+ /**
46
+ * Custom API error class
47
+ */
48
+ export class ApiError extends Error {
49
+ readonly code: ApiErrorCode;
50
+ readonly status?: number;
51
+ readonly field?: string;
52
+ readonly retryAfter?: number;
53
+ readonly originalError?: Error;
54
+
55
+ constructor(details: ApiErrorDetails) {
56
+ super(details.message);
57
+ this.name = 'ApiError';
58
+ this.code = details.code;
59
+ this.status = details.status;
60
+ this.field = details.field;
61
+ this.retryAfter = details.retryAfter;
62
+ this.originalError = details.originalError;
63
+
64
+ // Maintain proper stack trace
65
+ if (Error.captureStackTrace) {
66
+ Error.captureStackTrace(this, ApiError);
67
+ }
68
+ }
69
+
70
+ /** Check if error is retryable */
71
+ get isRetryable(): boolean {
72
+ return [
73
+ ApiErrorCode.NETWORK_ERROR,
74
+ ApiErrorCode.TIMEOUT,
75
+ ApiErrorCode.RATE_LIMITED,
76
+ ApiErrorCode.SERVER_ERROR,
77
+ ApiErrorCode.SERVICE_UNAVAILABLE,
78
+ ].includes(this.code);
79
+ }
80
+
81
+ /** Check if error requires re-authentication */
82
+ get requiresAuth(): boolean {
83
+ return [
84
+ ApiErrorCode.UNAUTHORIZED,
85
+ ApiErrorCode.TOKEN_EXPIRED,
86
+ ].includes(this.code);
87
+ }
88
+
89
+ /** Convert to JSON-serializable object */
90
+ toJSON(): ApiErrorDetails {
91
+ return {
92
+ code: this.code,
93
+ message: this.message,
94
+ status: this.status,
95
+ field: this.field,
96
+ retryAfter: this.retryAfter,
97
+ };
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Create ApiError from HTTP response
103
+ */
104
+ export function createApiError(
105
+ status: number,
106
+ message?: string,
107
+ field?: string
108
+ ): ApiError {
109
+ let code: ApiErrorCode;
110
+ let defaultMessage: string;
111
+
112
+ switch (status) {
113
+ case 400:
114
+ code = ApiErrorCode.BAD_REQUEST;
115
+ defaultMessage = 'Invalid request';
116
+ break;
117
+ case 401:
118
+ code = ApiErrorCode.UNAUTHORIZED;
119
+ defaultMessage = 'Authentication required';
120
+ break;
121
+ case 403:
122
+ code = ApiErrorCode.FORBIDDEN;
123
+ defaultMessage = 'Access denied';
124
+ break;
125
+ case 404:
126
+ code = ApiErrorCode.NOT_FOUND;
127
+ defaultMessage = 'Resource not found';
128
+ break;
129
+ case 409:
130
+ code = ApiErrorCode.CONFLICT;
131
+ defaultMessage = 'Resource conflict';
132
+ break;
133
+ case 422:
134
+ code = ApiErrorCode.VALIDATION_ERROR;
135
+ defaultMessage = 'Validation failed';
136
+ break;
137
+ case 429:
138
+ code = ApiErrorCode.RATE_LIMITED;
139
+ defaultMessage = 'Rate limit exceeded';
140
+ break;
141
+ case 500:
142
+ code = ApiErrorCode.SERVER_ERROR;
143
+ defaultMessage = 'Server error';
144
+ break;
145
+ case 503:
146
+ code = ApiErrorCode.SERVICE_UNAVAILABLE;
147
+ defaultMessage = 'Service unavailable';
148
+ break;
149
+ default:
150
+ code = ApiErrorCode.UNKNOWN;
151
+ defaultMessage = 'An unexpected error occurred';
152
+ }
153
+
154
+ return new ApiError({
155
+ code,
156
+ message: message || defaultMessage,
157
+ status,
158
+ field,
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Create ApiError from network error
164
+ */
165
+ export function createNetworkError(error: Error): ApiError {
166
+ if (error.name === 'AbortError') {
167
+ return new ApiError({
168
+ code: ApiErrorCode.TIMEOUT,
169
+ message: 'Request timed out',
170
+ originalError: error,
171
+ });
172
+ }
173
+
174
+ return new ApiError({
175
+ code: ApiErrorCode.NETWORK_ERROR,
176
+ message: 'Network error',
177
+ originalError: error,
178
+ });
179
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * API exports
3
+ */
4
+
5
+ // Client interface and types
6
+ export type {
7
+ QuorumApiClient,
8
+ SendMessageParams,
9
+ SendDirectMessageParams,
10
+ AddReactionParams,
11
+ RemoveReactionParams,
12
+ EditMessageParams,
13
+ DeleteMessageParams,
14
+ } from './client';
15
+
16
+ // Errors
17
+ export {
18
+ ApiError,
19
+ ApiErrorCode,
20
+ createApiError,
21
+ createNetworkError,
22
+ } from './errors';
23
+ export type { ApiErrorDetails } from './errors';
24
+
25
+ // Endpoints
26
+ export {
27
+ endpoints,
28
+ } from './endpoints';
29
+ export type {
30
+ ApiConfig,
31
+ HttpMethod,
32
+ RequestOptions,
33
+ PaginationParams,
34
+ MessageListParams,
35
+ } from './endpoints';
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Encryption State Types and Storage Interface
3
+ *
4
+ * Platform-agnostic types for managing Double Ratchet encryption states.
5
+ * The actual storage implementation is platform-specific (MMKV for mobile, IndexedDB for desktop).
6
+ */
7
+
8
+ // ============ Device & Recipient Types ============
9
+
10
+ /**
11
+ * DeviceKeys - Device's cryptographic keypairs for E2E encryption
12
+ * Used for X3DH key exchange and inbox message unsealing
13
+ */
14
+ export interface DeviceKeys {
15
+ /** X448 identity key for X3DH - private key */
16
+ identityPrivateKey: number[];
17
+ /** X448 identity key for X3DH - public key */
18
+ identityPublicKey: number[];
19
+ /** X448 signed pre-key for X3DH - private key */
20
+ preKeyPrivateKey: number[];
21
+ /** X448 signed pre-key for X3DH - public key */
22
+ preKeyPublicKey: number[];
23
+ /** X448 inbox encryption key for unsealing - private key */
24
+ inboxEncryptionPrivateKey: number[];
25
+ /** X448 inbox encryption key for unsealing - public key */
26
+ inboxEncryptionPublicKey: number[];
27
+ }
28
+
29
+ /**
30
+ * RecipientInfo - Recipient's public keys needed for encryption
31
+ * Used to establish X3DH session and seal messages
32
+ */
33
+ export interface RecipientInfo {
34
+ /** Recipient's address */
35
+ address: string;
36
+ /** X448 identity public key for X3DH */
37
+ identityKey: number[];
38
+ /** X448 signed pre-key for X3DH */
39
+ signedPreKey: number[];
40
+ /** Recipient's inbox address */
41
+ inboxAddress: string;
42
+ /** X448 public key for sealing envelopes to recipient's inbox */
43
+ inboxEncryptionKey?: number[];
44
+ }
45
+
46
+ /**
47
+ * EncryptedEnvelope - Result of encrypting a message
48
+ */
49
+ export interface EncryptedEnvelope {
50
+ /** The encrypted Double Ratchet envelope */
51
+ envelope: string;
52
+ /** Recipient's inbox address */
53
+ inboxAddress: string;
54
+ /** Ephemeral public key (only set for first message in new session) */
55
+ ephemeralPublicKey?: number[];
56
+ /** Ephemeral private key (only set for first message - needed for sealing) */
57
+ ephemeralPrivateKey?: number[];
58
+ }
59
+
60
+ // ============ Storage Provider Interface ============
61
+
62
+ /**
63
+ * KeyValueStorageProvider - Platform-agnostic key-value storage interface
64
+ *
65
+ * Implementations:
66
+ * - MMKV (React Native)
67
+ * - IndexedDB (Desktop/Web)
68
+ * - AsyncStorage (React Native fallback)
69
+ */
70
+ export interface KeyValueStorageProvider {
71
+ /**
72
+ * Get a string value by key
73
+ * @returns The value or null if not found
74
+ */
75
+ getString(key: string): string | null;
76
+
77
+ /**
78
+ * Set a string value
79
+ */
80
+ set(key: string, value: string): void;
81
+
82
+ /**
83
+ * Remove a key
84
+ */
85
+ remove(key: string): void;
86
+
87
+ /**
88
+ * Get all keys in storage
89
+ */
90
+ getAllKeys(): string[];
91
+
92
+ /**
93
+ * Clear all data from storage
94
+ */
95
+ clearAll(): void;
96
+ }
97
+
98
+ // ============ Inbox Types ============
99
+
100
+ /**
101
+ * SendingInbox - Recipient's inbox info needed for sealing messages
102
+ * Matches desktop SDK's SendingInbox type
103
+ */
104
+ export interface SendingInbox {
105
+ /** Recipient's inbox address where we send to */
106
+ inbox_address: string;
107
+ /** Recipient's X448 public key for sealing (hex) */
108
+ inbox_encryption_key: string;
109
+ /** Recipient's Ed448 public key (hex) - empty until confirmed */
110
+ inbox_public_key: string;
111
+ /** Always empty - we don't have their private key */
112
+ inbox_private_key: string;
113
+ }
114
+
115
+ /**
116
+ * ReceivingInbox - Our inbox info for receiving replies
117
+ * Simplified version (full keypair stored separately in ConversationInboxKeypair)
118
+ */
119
+ export interface ReceivingInbox {
120
+ /** Our inbox address where we receive replies */
121
+ inbox_address: string;
122
+ }
123
+
124
+ // ============ Encryption State ============
125
+
126
+ /**
127
+ * EncryptionState - Double Ratchet state for a conversation+inbox pair
128
+ * Matches desktop's DoubleRatchetStateAndInboxKeys structure
129
+ */
130
+ export interface EncryptionState {
131
+ /** JSON-serialized ratchet state */
132
+ state: string;
133
+ /** When state was created/updated */
134
+ timestamp: number;
135
+ /** Conversation identifier */
136
+ conversationId: string;
137
+ /** Associated inbox ID (our receiving inbox) */
138
+ inboxId: string;
139
+ /** Whether we've sent an accept message */
140
+ sentAccept?: boolean;
141
+ /** Recipient's inbox info for sealing messages */
142
+ sendingInbox?: SendingInbox;
143
+ /** Session tag (usually our inbox address) */
144
+ tag?: string;
145
+ /**
146
+ * X3DH ephemeral public key (hex) used for session establishment.
147
+ * MUST be reused for all init envelopes until session is confirmed.
148
+ * This ensures the receiver can derive the same session key via X3DH.
149
+ */
150
+ x3dhEphemeralPublicKey?: string;
151
+ /**
152
+ * X3DH ephemeral private key (hex) used for session establishment.
153
+ * MUST be reused for sealing init envelopes until session is confirmed.
154
+ */
155
+ x3dhEphemeralPrivateKey?: string;
156
+ }
157
+
158
+ // ============ Inbox Mapping ============
159
+
160
+ /**
161
+ * InboxMapping - Maps inbox address to conversation
162
+ */
163
+ export interface InboxMapping {
164
+ inboxId: string;
165
+ conversationId: string;
166
+ }
167
+
168
+ /**
169
+ * LatestState - Tracks the most recent state for a conversation
170
+ */
171
+ export interface LatestState {
172
+ conversationId: string;
173
+ inboxId: string;
174
+ timestamp: number;
175
+ }
176
+
177
+ // ============ Conversation Inbox Keypair ============
178
+
179
+ /**
180
+ * ConversationInboxKeypair - Per-conversation inbox keypair for receiving replies
181
+ * Mirrors desktop's InboxKeyset structure with both Ed448 and X448 keys
182
+ */
183
+ export interface ConversationInboxKeypair {
184
+ conversationId: string;
185
+ inboxAddress: string;
186
+ /** X448 encryption public key (for sealing/unsealing messages) */
187
+ encryptionPublicKey: number[];
188
+ /** X448 encryption private key */
189
+ encryptionPrivateKey: number[];
190
+ /** Ed448 signing public key (for signing/verifying inbox messages) */
191
+ signingPublicKey?: number[];
192
+ /** Ed448 signing private key */
193
+ signingPrivateKey?: number[];
194
+ }
195
+
196
+ // ============ Storage Key Prefixes ============
197
+
198
+ /**
199
+ * Storage key prefixes for encryption state data
200
+ */
201
+ export const ENCRYPTION_STORAGE_KEYS = {
202
+ /** enc_state:{conversationId}:{inboxId} */
203
+ ENCRYPTION_STATE: 'enc_state:',
204
+ /** inbox_map:{inboxId} */
205
+ INBOX_MAPPING: 'inbox_map:',
206
+ /** latest:{conversationId} */
207
+ LATEST_STATE: 'latest:',
208
+ /** conv_inboxes:{conversationId} -> inboxId[] */
209
+ CONVERSATION_INBOXES: 'conv_inboxes:',
210
+ /** conv_inbox_key:{conversationId} -> ConversationInboxKeypair */
211
+ CONVERSATION_INBOX_KEY: 'conv_inbox_key:',
212
+ } as const;
213
+
214
+ // ============ Encryption State Storage Interface ============
215
+
216
+ /**
217
+ * EncryptionStateStorageInterface - Interface for managing encryption states
218
+ *
219
+ * This interface defines the contract for encryption state storage.
220
+ * Implementations use platform-specific storage backends.
221
+ */
222
+ export interface EncryptionStateStorageInterface {
223
+ // Encryption States
224
+ getEncryptionState(conversationId: string, inboxId: string): EncryptionState | null;
225
+ getEncryptionStates(conversationId: string): EncryptionState[];
226
+ saveEncryptionState(state: EncryptionState, updateLatest?: boolean): void;
227
+ deleteEncryptionState(conversationId: string, inboxId: string): void;
228
+ deleteAllEncryptionStates(conversationId: string): void;
229
+
230
+ // Inbox Mapping
231
+ getInboxMapping(inboxId: string): InboxMapping | null;
232
+ saveInboxMapping(inboxId: string, conversationId: string): void;
233
+ deleteInboxMapping(inboxId: string): void;
234
+
235
+ // Latest State
236
+ getLatestState(conversationId: string): LatestState | null;
237
+
238
+ // Conversation Inbox Keypairs
239
+ saveConversationInboxKeypair(keypair: ConversationInboxKeypair): void;
240
+ getConversationInboxKeypair(conversationId: string): ConversationInboxKeypair | null;
241
+ deleteConversationInboxKeypair(conversationId: string): void;
242
+ getConversationInboxKeypairByAddress(inboxAddress: string): ConversationInboxKeypair | null;
243
+ getAllConversationInboxAddresses(): string[];
244
+
245
+ // Utilities
246
+ clearAll(): void;
247
+ hasEncryptionState(conversationId: string): boolean;
248
+ getStatesByInboxId(inboxId: string): Array<{ conversationId: string; state: EncryptionState }>;
249
+ }