@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
@@ -0,0 +1,60 @@
1
+ /**
2
+ * useSpaces hook
3
+ *
4
+ * Fetches and caches space data with offline support
5
+ */
6
+
7
+ import { useQuery, useQueryClient } from '@tanstack/react-query';
8
+ import type { Space } from '../types';
9
+ import type { StorageAdapter } from '../storage';
10
+ import { queryKeys } from './keys';
11
+
12
+ export interface UseSpacesOptions {
13
+ storage: StorageAdapter;
14
+ enabled?: boolean;
15
+ }
16
+
17
+ export function useSpaces({ storage, enabled = true }: UseSpacesOptions) {
18
+ return useQuery({
19
+ queryKey: queryKeys.spaces.all,
20
+ queryFn: async (): Promise<Space[]> => {
21
+ return storage.getSpaces();
22
+ },
23
+ enabled,
24
+ staleTime: 1000 * 60 * 5, // 5 minutes
25
+ });
26
+ }
27
+
28
+ export interface UseSpaceOptions {
29
+ storage: StorageAdapter;
30
+ spaceId: string | undefined;
31
+ enabled?: boolean;
32
+ }
33
+
34
+ export function useSpace({ storage, spaceId, enabled = true }: UseSpaceOptions) {
35
+ return useQuery({
36
+ queryKey: queryKeys.spaces.detail(spaceId ?? ''),
37
+ queryFn: async (): Promise<Space | null> => {
38
+ if (!spaceId) return null;
39
+ return storage.getSpace(spaceId);
40
+ },
41
+ enabled: enabled && !!spaceId,
42
+ staleTime: 1000 * 60 * 5, // 5 minutes
43
+ });
44
+ }
45
+
46
+ export function useSpaceMembers({
47
+ storage,
48
+ spaceId,
49
+ enabled = true,
50
+ }: UseSpaceOptions) {
51
+ return useQuery({
52
+ queryKey: queryKeys.spaces.members(spaceId ?? ''),
53
+ queryFn: async () => {
54
+ if (!spaceId) return [];
55
+ return storage.getSpaceMembers(spaceId);
56
+ },
57
+ enabled: enabled && !!spaceId,
58
+ staleTime: 1000 * 60 * 2, // 2 minutes
59
+ });
60
+ }
package/src/index.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @quorum/shared
3
+ *
4
+ * Shared types, hooks, and utilities for Quorum mobile and desktop apps
5
+ */
6
+
7
+ // Types
8
+ export * from './types';
9
+
10
+ // Storage
11
+ export * from './storage';
12
+
13
+ // API
14
+ export * from './api';
15
+
16
+ // Hooks
17
+ export * from './hooks';
18
+
19
+ // Utils
20
+ export * from './utils';
21
+
22
+ // Crypto (E2E encryption)
23
+ export * from './crypto';
24
+
25
+ // Signing (Ed448)
26
+ export * from './signing';
27
+
28
+ // Transport (HTTP, WebSocket)
29
+ export * from './transport';
30
+
31
+ // Sync (hash-based delta synchronization)
32
+ export * from './sync';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Signing module exports
3
+ */
4
+
5
+ export type { SigningProvider, SignedMessage } from './types';
6
+
7
+ export { verifySignedMessage, createSignedMessage } from './types';
8
+
9
+ // WASM implementation (for desktop/web)
10
+ export { WasmSigningProvider } from './wasm-provider';
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Signing types and SigningProvider interface
3
+ *
4
+ * Platform-agnostic message signing and verification.
5
+ * Uses Ed448 for all signing operations.
6
+ */
7
+
8
+ /**
9
+ * SigningProvider - Platform-agnostic interface for cryptographic signing
10
+ *
11
+ * Implementations:
12
+ * - WASM (desktop/web): Uses channel-wasm js_sign_ed448/js_verify_ed448
13
+ * - Native (iOS/Android): Uses uniffi-generated bindings
14
+ */
15
+ export interface SigningProvider {
16
+ /**
17
+ * Sign a message using Ed448
18
+ * @param privateKey Base64-encoded Ed448 private key (56 bytes)
19
+ * @param message Base64-encoded message to sign
20
+ * @returns Base64-encoded signature (114 bytes)
21
+ */
22
+ signEd448(privateKey: string, message: string): Promise<string>;
23
+
24
+ /**
25
+ * Verify an Ed448 signature
26
+ * @param publicKey Base64-encoded Ed448 public key (57 bytes)
27
+ * @param message Base64-encoded original message
28
+ * @param signature Base64-encoded signature to verify
29
+ * @returns true if signature is valid, false otherwise
30
+ */
31
+ verifyEd448(publicKey: string, message: string, signature: string): Promise<boolean>;
32
+ }
33
+
34
+ /**
35
+ * Signed message structure
36
+ */
37
+ export interface SignedMessage {
38
+ /** The message content (may be encrypted) */
39
+ content: string;
40
+ /** Base64-encoded Ed448 signature */
41
+ signature: string;
42
+ /** Base64-encoded Ed448 public key of signer */
43
+ publicKey: string;
44
+ /** Timestamp when signed */
45
+ timestamp: number;
46
+ }
47
+
48
+ /**
49
+ * Verify a signed message structure
50
+ */
51
+ export async function verifySignedMessage(
52
+ provider: SigningProvider,
53
+ message: SignedMessage
54
+ ): Promise<boolean> {
55
+ // Reconstruct the signed payload (content + timestamp)
56
+ const payload = `${message.content}:${message.timestamp}`;
57
+ const payloadBase64 = btoa(payload);
58
+
59
+ return provider.verifyEd448(message.publicKey, payloadBase64, message.signature);
60
+ }
61
+
62
+ /**
63
+ * Create a signed message
64
+ */
65
+ export async function createSignedMessage(
66
+ provider: SigningProvider,
67
+ privateKey: string,
68
+ publicKey: string,
69
+ content: string
70
+ ): Promise<SignedMessage> {
71
+ const timestamp = Date.now();
72
+ const payload = `${content}:${timestamp}`;
73
+ const payloadBase64 = btoa(payload);
74
+
75
+ const signature = await provider.signEd448(privateKey, payloadBase64);
76
+
77
+ return {
78
+ content,
79
+ signature,
80
+ publicKey,
81
+ timestamp,
82
+ };
83
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * WASM SigningProvider implementation
3
+ *
4
+ * Wraps the channel-wasm WASM module to implement the SigningProvider interface.
5
+ * Used by desktop/web applications.
6
+ */
7
+
8
+ import type { SigningProvider } from './types';
9
+ import type { ChannelWasmModule } from '../crypto/wasm-provider';
10
+
11
+ /**
12
+ * Parse WASM verification result
13
+ * Returns true for "true"/"valid", false for "false"/"invalid"
14
+ */
15
+ function parseVerifyResult(result: string): boolean {
16
+ const normalized = result.toLowerCase().trim();
17
+ if (normalized === 'true' || normalized === 'valid') {
18
+ return true;
19
+ }
20
+ if (normalized === 'false' || normalized === 'invalid') {
21
+ return false;
22
+ }
23
+ // Check for error patterns
24
+ if (
25
+ result.startsWith('invalid') ||
26
+ result.startsWith('error') ||
27
+ result.includes('failed') ||
28
+ result.includes('Error')
29
+ ) {
30
+ throw new Error(result);
31
+ }
32
+ // Try to parse as JSON boolean
33
+ try {
34
+ return JSON.parse(result) as boolean;
35
+ } catch {
36
+ throw new Error(`Unexpected verification result: ${result}`);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * WasmSigningProvider - Implements SigningProvider using channel-wasm
42
+ */
43
+ export class WasmSigningProvider implements SigningProvider {
44
+ private wasm: ChannelWasmModule;
45
+
46
+ constructor(wasmModule: ChannelWasmModule) {
47
+ this.wasm = wasmModule;
48
+ }
49
+
50
+ async signEd448(privateKey: string, message: string): Promise<string> {
51
+ const result = this.wasm.js_sign_ed448(privateKey, message);
52
+
53
+ // Check for error patterns
54
+ if (
55
+ result.startsWith('invalid') ||
56
+ result.startsWith('error') ||
57
+ result.includes('failed') ||
58
+ result.includes('Error')
59
+ ) {
60
+ throw new Error(result);
61
+ }
62
+
63
+ // Remove quotes if present (WASM returns quoted string)
64
+ if (result.startsWith('"') && result.endsWith('"')) {
65
+ return result.slice(1, -1);
66
+ }
67
+
68
+ return result;
69
+ }
70
+
71
+ async verifyEd448(publicKey: string, message: string, signature: string): Promise<boolean> {
72
+ const result = this.wasm.js_verify_ed448(publicKey, message, signature);
73
+ return parseVerifyResult(result);
74
+ }
75
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * StorageAdapter interface
3
+ *
4
+ * Platform-agnostic storage interface that can be implemented by:
5
+ * - IndexedDB (desktop/web)
6
+ * - MMKV (React Native mobile)
7
+ */
8
+
9
+ import type { Space, Channel, Message, Conversation, UserConfig, SpaceMember } from '../types';
10
+ import type { MessageDigest, MemberDigest, DeletedMessageTombstone } from '../sync/types';
11
+
12
+ export interface GetMessagesParams {
13
+ spaceId: string;
14
+ channelId: string;
15
+ cursor?: number;
16
+ direction?: 'forward' | 'backward';
17
+ limit?: number;
18
+ }
19
+
20
+ export interface GetMessagesResult {
21
+ messages: Message[];
22
+ nextCursor: number | null;
23
+ prevCursor: number | null;
24
+ }
25
+
26
+ export interface StorageAdapter {
27
+ // Initialization
28
+ init(): Promise<void>;
29
+
30
+ // Spaces
31
+ getSpaces(): Promise<Space[]>;
32
+ getSpace(spaceId: string): Promise<Space | null>;
33
+ saveSpace(space: Space): Promise<void>;
34
+ deleteSpace(spaceId: string): Promise<void>;
35
+
36
+ // Channels (embedded in Space, but may need separate access)
37
+ getChannels(spaceId: string): Promise<Channel[]>;
38
+
39
+ // Messages
40
+ getMessages(params: GetMessagesParams): Promise<GetMessagesResult>;
41
+ getMessage(params: {
42
+ spaceId: string;
43
+ channelId: string;
44
+ messageId: string;
45
+ }): Promise<Message | undefined>;
46
+ saveMessage(
47
+ message: Message,
48
+ lastMessageTimestamp: number,
49
+ address: string,
50
+ conversationType: string,
51
+ icon: string,
52
+ displayName: string
53
+ ): Promise<void>;
54
+ deleteMessage(messageId: string): Promise<void>;
55
+
56
+ // Conversations (DMs)
57
+ getConversations(params: {
58
+ type: 'direct' | 'group';
59
+ cursor?: number;
60
+ limit?: number;
61
+ }): Promise<{ conversations: Conversation[]; nextCursor: number | null }>;
62
+ getConversation(conversationId: string): Promise<Conversation | undefined>;
63
+ saveConversation(conversation: Conversation): Promise<void>;
64
+ deleteConversation(conversationId: string): Promise<void>;
65
+
66
+ // User Config
67
+ getUserConfig(address: string): Promise<UserConfig | undefined>;
68
+ saveUserConfig(userConfig: UserConfig): Promise<void>;
69
+
70
+ // Space Members
71
+ getSpaceMembers(spaceId: string): Promise<SpaceMember[]>;
72
+ getSpaceMember(spaceId: string, address: string): Promise<SpaceMember | undefined>;
73
+ saveSpaceMember(spaceId: string, member: SpaceMember): Promise<void>;
74
+
75
+ // Sync metadata
76
+ getLastSyncTime(key: string): Promise<number | undefined>;
77
+ setLastSyncTime(key: string, time: number): Promise<void>;
78
+
79
+ // Sync-specific queries (optional - for optimized sync)
80
+ // If not implemented, SyncService will compute these from full data
81
+
82
+ /**
83
+ * Get message digests for efficient sync comparison.
84
+ * If not implemented, returns undefined and SyncService computes from messages.
85
+ */
86
+ getMessageDigests?(spaceId: string, channelId: string): Promise<MessageDigest[] | undefined>;
87
+
88
+ /**
89
+ * Get messages by IDs for delta sync.
90
+ * If not implemented, returns undefined and caller fetches individually.
91
+ */
92
+ getMessagesByIds?(
93
+ spaceId: string,
94
+ channelId: string,
95
+ ids: string[]
96
+ ): Promise<Message[] | undefined>;
97
+
98
+ /**
99
+ * Get member digests for efficient sync comparison.
100
+ * If not implemented, returns undefined and SyncService computes from members.
101
+ */
102
+ getMemberDigests?(spaceId: string): Promise<MemberDigest[] | undefined>;
103
+
104
+ /**
105
+ * Get deleted message tombstones for sync.
106
+ */
107
+ getTombstones?(spaceId: string, channelId: string): Promise<DeletedMessageTombstone[]>;
108
+
109
+ /**
110
+ * Save deleted message tombstone.
111
+ */
112
+ saveTombstone?(tombstone: DeletedMessageTombstone): Promise<void>;
113
+
114
+ /**
115
+ * Clean up old tombstones.
116
+ */
117
+ cleanupTombstones?(maxAgeMs: number): Promise<void>;
118
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Storage exports
3
+ */
4
+
5
+ export type {
6
+ StorageAdapter,
7
+ GetMessagesParams,
8
+ GetMessagesResult,
9
+ } from './adapter';
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Sync Module
3
+ *
4
+ * Hash-based delta synchronization for efficient data transfer.
5
+ */
6
+
7
+ // Types
8
+ export type {
9
+ // Message sync
10
+ MessageDigest,
11
+ ReactionDigest,
12
+ SyncManifest,
13
+ MessageDelta,
14
+ ReactionDelta,
15
+ // Member sync
16
+ MemberDigest,
17
+ MemberDelta,
18
+ // Peer map sync
19
+ PeerEntry,
20
+ PeerMapDelta,
21
+ // Tombstones
22
+ DeletedMessageTombstone,
23
+ // Control messages
24
+ SyncSummary,
25
+ SyncRequestPayload,
26
+ SyncInfoPayload,
27
+ SyncInitiatePayload,
28
+ SyncManifestPayload,
29
+ SyncDeltaPayload,
30
+ // State
31
+ SyncCandidate,
32
+ SyncSession,
33
+ // Union
34
+ SyncControlPayload,
35
+ } from './types';
36
+
37
+ // Type guards
38
+ export {
39
+ isSyncRequest,
40
+ isSyncInfo,
41
+ isSyncInitiate,
42
+ isSyncManifest,
43
+ isSyncDelta,
44
+ } from './types';
45
+
46
+ // Utils
47
+ export {
48
+ // Constants
49
+ MAX_CHUNK_SIZE,
50
+ DEFAULT_SYNC_EXPIRY_MS,
51
+ AGGRESSIVE_SYNC_TIMEOUT_MS,
52
+ // Hash functions
53
+ computeHash,
54
+ computeContentHash,
55
+ computeReactionHash,
56
+ computeMemberHash,
57
+ computeManifestHash,
58
+ // Digest creation
59
+ createMessageDigest,
60
+ createReactionDigest,
61
+ createManifest,
62
+ createMemberDigest,
63
+ // Delta calculation
64
+ computeMessageDiff,
65
+ computeReactionDiff,
66
+ computeMemberDiff,
67
+ computePeerDiff,
68
+ // Delta building
69
+ buildMessageDelta,
70
+ buildReactionDelta,
71
+ buildMemberDelta,
72
+ // Chunking
73
+ chunkMessages,
74
+ chunkMembers,
75
+ // Summary
76
+ createSyncSummary,
77
+ } from './utils';
78
+
79
+ export type { MessageDiffResult, ReactionDiffResult, MemberDiffResult, PeerDiffResult } from './utils';
80
+
81
+ // Service
82
+ export { SyncService } from './service';
83
+ export type { SyncServiceConfig } from './service';