@massalabs/gossip-sdk 0.0.1 → 0.0.2-dev.20260128111120

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/api/messageProtocol/index.d.ts +19 -0
  2. package/dist/api/messageProtocol/index.js +26 -0
  3. package/dist/api/messageProtocol/mock.d.ts +12 -0
  4. package/{src/api/messageProtocol/mock.ts → dist/api/messageProtocol/mock.js} +2 -3
  5. package/dist/api/messageProtocol/rest.d.ts +22 -0
  6. package/dist/api/messageProtocol/rest.js +161 -0
  7. package/dist/api/messageProtocol/types.d.ts +61 -0
  8. package/dist/api/messageProtocol/types.js +6 -0
  9. package/dist/assets/generated/wasm/README.md +281 -0
  10. package/dist/assets/generated/wasm/gossip_wasm.d.ts +498 -0
  11. package/dist/assets/generated/wasm/gossip_wasm.js +1399 -0
  12. package/dist/assets/generated/wasm/gossip_wasm_bg.wasm +0 -0
  13. package/dist/assets/generated/wasm/gossip_wasm_bg.wasm.d.ts +68 -0
  14. package/dist/assets/generated/wasm/package.json +15 -0
  15. package/dist/config/protocol.d.ts +36 -0
  16. package/dist/config/protocol.js +77 -0
  17. package/dist/config/sdk.d.ts +82 -0
  18. package/dist/config/sdk.js +55 -0
  19. package/{src/contacts.ts → dist/contacts.d.ts} +10 -94
  20. package/dist/contacts.js +166 -0
  21. package/dist/core/SdkEventEmitter.d.ts +36 -0
  22. package/dist/core/SdkEventEmitter.js +59 -0
  23. package/dist/core/SdkPolling.d.ts +35 -0
  24. package/dist/core/SdkPolling.js +100 -0
  25. package/{src/core/index.ts → dist/core/index.d.ts} +0 -2
  26. package/dist/core/index.js +5 -0
  27. package/dist/crypto/bip39.d.ts +34 -0
  28. package/dist/crypto/bip39.js +62 -0
  29. package/dist/crypto/encryption.d.ts +37 -0
  30. package/dist/crypto/encryption.js +46 -0
  31. package/dist/db.d.ts +190 -0
  32. package/dist/db.js +311 -0
  33. package/dist/gossipSdk.d.ts +274 -0
  34. package/dist/gossipSdk.js +690 -0
  35. package/dist/index.d.ts +73 -0
  36. package/dist/index.js +77 -0
  37. package/dist/services/announcement.d.ts +43 -0
  38. package/dist/services/announcement.js +491 -0
  39. package/dist/services/auth.d.ts +37 -0
  40. package/dist/services/auth.js +76 -0
  41. package/dist/services/discussion.d.ts +63 -0
  42. package/dist/services/discussion.js +297 -0
  43. package/dist/services/message.d.ts +74 -0
  44. package/dist/services/message.js +826 -0
  45. package/dist/services/refresh.d.ts +41 -0
  46. package/dist/services/refresh.js +205 -0
  47. package/{src/sw.ts → dist/sw.d.ts} +1 -8
  48. package/dist/sw.js +10 -0
  49. package/dist/types/events.d.ts +80 -0
  50. package/dist/types/events.js +7 -0
  51. package/dist/types.d.ts +32 -0
  52. package/dist/types.js +7 -0
  53. package/dist/utils/base64.d.ts +10 -0
  54. package/dist/utils/base64.js +30 -0
  55. package/dist/utils/contacts.d.ts +42 -0
  56. package/dist/utils/contacts.js +113 -0
  57. package/dist/utils/discussions.d.ts +24 -0
  58. package/dist/utils/discussions.js +38 -0
  59. package/dist/utils/logs.d.ts +19 -0
  60. package/dist/utils/logs.js +89 -0
  61. package/dist/utils/messageSerialization.d.ts +64 -0
  62. package/dist/utils/messageSerialization.js +184 -0
  63. package/dist/utils/queue.d.ts +50 -0
  64. package/dist/utils/queue.js +110 -0
  65. package/dist/utils/type.d.ts +10 -0
  66. package/dist/utils/type.js +4 -0
  67. package/dist/utils/userId.d.ts +40 -0
  68. package/dist/utils/userId.js +90 -0
  69. package/dist/utils/validation.d.ts +50 -0
  70. package/dist/utils/validation.js +112 -0
  71. package/dist/utils.d.ts +30 -0
  72. package/{src/utils.ts → dist/utils.js} +9 -19
  73. package/dist/wasm/encryption.d.ts +56 -0
  74. package/{src/wasm/encryption.ts → dist/wasm/encryption.js} +22 -51
  75. package/dist/wasm/index.d.ts +10 -0
  76. package/{src/wasm/index.ts → dist/wasm/index.js} +1 -8
  77. package/dist/wasm/loader.d.ts +21 -0
  78. package/dist/wasm/loader.js +103 -0
  79. package/dist/wasm/session.d.ts +85 -0
  80. package/dist/wasm/session.js +226 -0
  81. package/dist/wasm/userKeys.d.ts +17 -0
  82. package/{src/wasm/userKeys.ts → dist/wasm/userKeys.js} +6 -13
  83. package/package.json +5 -1
  84. package/src/api/messageProtocol/index.ts +0 -53
  85. package/src/api/messageProtocol/rest.ts +0 -209
  86. package/src/api/messageProtocol/types.ts +0 -70
  87. package/src/config/protocol.ts +0 -97
  88. package/src/config/sdk.ts +0 -131
  89. package/src/core/SdkEventEmitter.ts +0 -91
  90. package/src/core/SdkPolling.ts +0 -134
  91. package/src/crypto/bip39.ts +0 -84
  92. package/src/crypto/encryption.ts +0 -77
  93. package/src/db.ts +0 -465
  94. package/src/gossipSdk.ts +0 -994
  95. package/src/index.ts +0 -211
  96. package/src/services/announcement.ts +0 -653
  97. package/src/services/auth.ts +0 -95
  98. package/src/services/discussion.ts +0 -380
  99. package/src/services/message.ts +0 -1055
  100. package/src/services/refresh.ts +0 -234
  101. package/src/types/events.ts +0 -108
  102. package/src/types.ts +0 -70
  103. package/src/utils/base64.ts +0 -39
  104. package/src/utils/contacts.ts +0 -161
  105. package/src/utils/discussions.ts +0 -55
  106. package/src/utils/logs.ts +0 -86
  107. package/src/utils/messageSerialization.ts +0 -257
  108. package/src/utils/queue.ts +0 -106
  109. package/src/utils/type.ts +0 -7
  110. package/src/utils/userId.ts +0 -114
  111. package/src/utils/validation.ts +0 -144
  112. package/src/wasm/loader.ts +0 -123
  113. package/src/wasm/session.ts +0 -276
  114. package/test/config/protocol.spec.ts +0 -31
  115. package/test/config/sdk.spec.ts +0 -163
  116. package/test/db/helpers.spec.ts +0 -142
  117. package/test/db/operations.spec.ts +0 -128
  118. package/test/db/states.spec.ts +0 -535
  119. package/test/integration/discussion-flow.spec.ts +0 -422
  120. package/test/integration/messaging-flow.spec.ts +0 -708
  121. package/test/integration/sdk-lifecycle.spec.ts +0 -325
  122. package/test/mocks/index.ts +0 -9
  123. package/test/mocks/mockMessageProtocol.ts +0 -100
  124. package/test/services/auth.spec.ts +0 -311
  125. package/test/services/discussion.spec.ts +0 -279
  126. package/test/services/message-deduplication.spec.ts +0 -299
  127. package/test/services/message-startup.spec.ts +0 -331
  128. package/test/services/message.spec.ts +0 -817
  129. package/test/services/refresh.spec.ts +0 -199
  130. package/test/services/session-status.spec.ts +0 -349
  131. package/test/session/wasm.spec.ts +0 -227
  132. package/test/setup.ts +0 -52
  133. package/test/utils/contacts.spec.ts +0 -156
  134. package/test/utils/discussions.spec.ts +0 -66
  135. package/test/utils/queue.spec.ts +0 -52
  136. package/test/utils/serialization.spec.ts +0 -120
  137. package/test/utils/userId.spec.ts +0 -120
  138. package/test/utils/validation.spec.ts +0 -223
  139. package/test/utils.ts +0 -212
  140. package/tsconfig.json +0 -26
  141. package/tsconfig.tsbuildinfo +0 -1
  142. package/vitest.config.ts +0 -28
@@ -1,234 +0,0 @@
1
- /**
2
- * Session Refresh Service
3
- *
4
- * Class-based service to periodically refresh sessions and handle
5
- * keep-alive messages and broken sessions.
6
- */
7
-
8
- import {
9
- type Discussion,
10
- type GossipDatabase,
11
- MessageDirection,
12
- MessageStatus,
13
- MessageType,
14
- } from '../db';
15
- import { sessionStatusToString } from '../wasm/session';
16
- import type { SessionModule } from '../wasm/session';
17
- import { SessionStatus } from '../assets/generated/wasm/gossip_wasm';
18
- import { decodeUserId, encodeUserId } from '../utils/userId';
19
- import { MessageService } from './message';
20
- import { Logger } from '../utils/logs';
21
- import { GossipSdkEvents } from '../types/events';
22
-
23
- const logger = new Logger('RefreshService');
24
-
25
- /**
26
- * Service for refreshing sessions and handling keep-alive messages.
27
- *
28
- * @example
29
- * ```typescript
30
- * const refreshService = new RefreshService(db, messageService, session);
31
- *
32
- * // Handle session refresh for active discussions
33
- * await refreshService.handleSessionRefresh(activeDiscussions);
34
- * ```
35
- */
36
- export class RefreshService {
37
- private messageService: MessageService;
38
- private session: SessionModule;
39
- private events: GossipSdkEvents;
40
-
41
- constructor(
42
- _db: GossipDatabase,
43
- messageService: MessageService,
44
- session: SessionModule,
45
- events: GossipSdkEvents = {}
46
- ) {
47
- // Note: db parameter kept for API compatibility but not currently used
48
- this.messageService = messageService;
49
- this.session = session;
50
- this.events = events;
51
- }
52
-
53
- /**
54
- * Handle session refresh for a given user:
55
- * - Calls the SessionModule.refresh() function to get peers that require keep-alive.
56
- * - For each discussion of the user:
57
- * - If the peer session status is Killed, marks the discussion as BROKEN.
58
- * - If the discussion is not BROKEN and its peer requires a keep-alive,
59
- * sends a keep-alive message via the session manager.
60
- *
61
- * Errors are logged via the Logger instance (log.error) but do not throw, so callers can safely
62
- * invoke this periodically (e.g. from background tasks).
63
- *
64
- * @param activeDiscussions - Array of active discussions
65
- */
66
- async handleSessionRefresh(activeDiscussions: Discussion[]): Promise<void> {
67
- const log = logger.forMethod('handleSessionRefresh');
68
- const ownerUserId = this.session.userIdEncoded;
69
-
70
- log.info('calling session refresh', {
71
- ownerUserId: ownerUserId,
72
- discussions: activeDiscussions.map(
73
- discussion => discussion.contactUserId
74
- ),
75
- });
76
- if (!activeDiscussions.length) {
77
- return;
78
- }
79
-
80
- if (!ownerUserId) {
81
- log.error('ownerUserId is empty, skipping session refresh');
82
- return;
83
- }
84
-
85
- // Log session status BEFORE refresh for debugging
86
- const statusBefore = activeDiscussions.map(d => {
87
- try {
88
- const peerId = decodeUserId(d.contactUserId);
89
- const status = this.session.peerSessionStatus(peerId);
90
- return {
91
- contact: d.contactUserId.slice(0, 12),
92
- status: sessionStatusToString(status),
93
- };
94
- } catch {
95
- return { contact: d.contactUserId.slice(0, 12), status: 'ERROR' };
96
- }
97
- });
98
- console.log('[SessionStatus] Before refresh():', statusBefore);
99
-
100
- let keepAlivePeerIds: string[] = [];
101
- try {
102
- // Ask the session manager which peers require keep-alive messages
103
- // WARNING: refresh() may KILL sessions based on timeout logic!
104
- const refreshResult = await this.session.refresh();
105
- keepAlivePeerIds = refreshResult.map(peer => encodeUserId(peer));
106
- } catch (error) {
107
- log.error('error while refreshing session', { error });
108
- return;
109
- }
110
-
111
- // Log session status AFTER refresh for debugging
112
- const statusAfter = activeDiscussions.map(d => {
113
- try {
114
- const peerId = decodeUserId(d.contactUserId);
115
- const status = this.session.peerSessionStatus(peerId);
116
- return {
117
- contact: d.contactUserId.slice(0, 12),
118
- status: sessionStatusToString(status),
119
- };
120
- } catch {
121
- return { contact: d.contactUserId.slice(0, 12), status: 'ERROR' };
122
- }
123
- });
124
- console.log('[SessionStatus] After refresh():', statusAfter);
125
-
126
- /* refresh function kill sessions that have no incoming messages for a long time
127
- So we need to mark corresponding discussions as broken if it is the case */
128
- for (const discussion of activeDiscussions) {
129
- try {
130
- // Decode contact userId to the peerId format expected by SessionModule
131
- const peerId = decodeUserId(discussion.contactUserId);
132
-
133
- // Check current session status for this peer
134
- const status = this.session.peerSessionStatus(peerId);
135
-
136
- // Per spec: when session is Killed/Saturated/UnknownPeer/NoSession,
137
- // trigger auto-renewal instead of marking as BROKEN
138
- const needsRenewal = [
139
- SessionStatus.Killed,
140
- SessionStatus.Saturated,
141
- SessionStatus.NoSession,
142
- SessionStatus.UnknownPeer,
143
- ].includes(status);
144
-
145
- if (needsRenewal) {
146
- // Log clearly to console for debugging
147
- console.warn(
148
- `[SessionStatus] ${discussion.contactUserId.slice(0, 16)}... -> ${sessionStatusToString(status)} (triggering renewal)`
149
- );
150
-
151
- log.info('session needs renewal, triggering auto-renewal', {
152
- ownerUserId: discussion.ownerUserId,
153
- contactUserId: discussion.contactUserId,
154
- sessionStatus: sessionStatusToString(status),
155
- });
156
-
157
- // Trigger auto-renewal (spec: call create_session)
158
- this.events.onSessionRenewalNeeded?.(discussion.contactUserId);
159
- continue;
160
- }
161
-
162
- // PeerRequested for an active discussion is a state inconsistency:
163
- // - DB says discussion is ACTIVE (we accepted)
164
- // - Session manager says PeerRequested (we haven't accepted)
165
- // This should not happen - throw to surface the bug rather than hide it
166
- if (status === SessionStatus.PeerRequested) {
167
- const error = new Error(
168
- `Unexpected PeerRequested status for active discussion with ${discussion.contactUserId}. ` +
169
- `This indicates a state inconsistency between the database and session manager.`
170
- );
171
- log.error('state inconsistency detected', {
172
- error,
173
- discussionId: discussion.id,
174
- });
175
- throw error;
176
- }
177
- } catch (error) {
178
- log.error('error while processing discussion', {
179
- error: error,
180
- discussionId: discussion.id,
181
- });
182
- // Re-throw state inconsistency errors - they should surface
183
- if (
184
- error instanceof Error &&
185
- error.message.includes('state inconsistency')
186
- ) {
187
- throw error;
188
- }
189
- continue;
190
- }
191
-
192
- // Check if this peer requires a keep-alive
193
- const needsKeepAlive = keepAlivePeerIds.some(
194
- peer => peer === discussion.contactUserId
195
- );
196
-
197
- if (!needsKeepAlive) {
198
- continue;
199
- }
200
-
201
- log.info('discussion does require a keep-alive message.', {
202
- ownerUserId: discussion.ownerUserId,
203
- contactUserId: discussion.contactUserId,
204
- });
205
-
206
- try {
207
- // Send a keep-alive message via the session manager
208
- await this.messageService.sendMessage({
209
- ownerUserId: discussion.ownerUserId,
210
- contactUserId: discussion.contactUserId,
211
- content: '',
212
- type: MessageType.KEEP_ALIVE,
213
- direction: MessageDirection.OUTGOING,
214
- status: MessageStatus.SENDING,
215
- timestamp: new Date(),
216
- });
217
- log.info('keep-alive message sent successfully.', {
218
- ownerUserId: discussion.ownerUserId,
219
- contactUserId: discussion.contactUserId,
220
- });
221
- } catch (error) {
222
- log.error('failed to send keep-alive message', {
223
- error: error,
224
- discussionId: discussion.id,
225
- sessionStatus: sessionStatusToString(
226
- this.session.peerSessionStatus(
227
- decodeUserId(discussion.contactUserId)
228
- )
229
- ),
230
- });
231
- }
232
- }
233
- }
234
- }
@@ -1,108 +0,0 @@
1
- /**
2
- * SDK Event Types
3
- *
4
- * Event handlers for SDK events. These are used by the SDK to notify
5
- * the host application about state changes.
6
- */
7
-
8
- import { Message, Discussion, Contact } from '../db';
9
-
10
- /**
11
- * Event handlers for SDK events.
12
- *
13
- * The SDK emits events when things happen (messages received, discussions created, etc.)
14
- * Your app can listen to these events and update its own state (zustand, redux, etc.)
15
- *
16
- * This pattern keeps the SDK decoupled from any specific state management solution.
17
- */
18
- export interface GossipSdkEvents {
19
- // ─────────────────────────────────────────────────────────────────
20
- // Message Events
21
- // ─────────────────────────────────────────────────────────────────
22
-
23
- /**
24
- * Called when a new message is received from a contact.
25
- * Use this to update your message list UI.
26
- */
27
- onMessageReceived?: (message: Message) => void;
28
-
29
- /**
30
- * Called when a message is successfully sent.
31
- * Use this to update message status in UI (sending → sent).
32
- */
33
- onMessageSent?: (message: Message) => void;
34
-
35
- /**
36
- * Called when sending a message fails.
37
- * Use this to show error state and allow retry.
38
- */
39
- onMessageFailed?: (message: Message, error: Error) => void;
40
-
41
- // ─────────────────────────────────────────────────────────────────
42
- // Discussion Events
43
- // ─────────────────────────────────────────────────────────────────
44
-
45
- /**
46
- * Called when someone sends you a discussion request (announcement).
47
- * Use this to show the request in your discussion list.
48
- */
49
- onDiscussionRequest?: (discussion: Discussion, contact: Contact) => void;
50
-
51
- /**
52
- * Called when a discussion status changes (accepted, broken, etc.).
53
- * Use this to update discussion state in UI.
54
- */
55
- onDiscussionStatusChanged?: (discussion: Discussion) => void;
56
-
57
- // ─────────────────────────────────────────────────────────────────
58
- // Session Events
59
- // ─────────────────────────────────────────────────────────────────
60
-
61
- /**
62
- * Called when a session with a contact is broken and needs renewal.
63
- * Use this to show appropriate UI state.
64
- * @deprecated Use onSessionRenewalNeeded for auto-renewal flow
65
- */
66
- onSessionBroken?: (discussion: Discussion) => void;
67
-
68
- /**
69
- * Called when a session is successfully renewed.
70
- */
71
- onSessionRenewed?: (discussion: Discussion) => void;
72
-
73
- /**
74
- * Called when a session needs to be renewed (auto-renewal).
75
- * The SDK will automatically attempt to renew the session.
76
- * Messages are queued with WAITING_SESSION status until session is active.
77
- */
78
- onSessionRenewalNeeded?: (contactUserId: string) => void;
79
-
80
- /**
81
- * Called when a peer has requested a session and we need to accept it.
82
- * This is different from renewal - the peer has sent us an announcement
83
- * and we need to respond (via DiscussionService.accept()).
84
- * NOT calling this correctly can cause stuck sessions (both sides waiting).
85
- */
86
- onSessionAcceptNeeded?: (contactUserId: string) => void;
87
-
88
- /**
89
- * Called when a session becomes Active after peer accepts our announcement.
90
- * This happens when we initiated a session (SelfRequested) and the peer
91
- * accepted it by sending their announcement back.
92
- * Use this to process any messages that were queued as WAITING_SESSION.
93
- */
94
- onSessionBecameActive?: (contactUserId: string) => void;
95
-
96
- // ─────────────────────────────────────────────────────────────────
97
- // Error Events
98
- // ─────────────────────────────────────────────────────────────────
99
-
100
- /**
101
- * Called when an error occurs in any SDK operation.
102
- * Use this for logging, error reporting, or showing error toasts.
103
- *
104
- * @param error - The error that occurred
105
- * @param context - Where the error occurred (e.g., 'message.send', 'announcement.fetch')
106
- */
107
- onError?: (error: Error, context: string) => void;
108
- }
package/src/types.ts DELETED
@@ -1,70 +0,0 @@
1
- /**
2
- * SDK Types
3
- *
4
- * Re-exports types from internal SDK modules for external consumers.
5
- */
6
-
7
- // Database entity types
8
- export type {
9
- Contact,
10
- Message,
11
- Discussion,
12
- UserProfile,
13
- PendingEncryptedMessage,
14
- PendingAnnouncement,
15
- ActiveSeeker,
16
- AuthMethod,
17
- } from './db';
18
-
19
- // Database enums
20
- export {
21
- DiscussionStatus,
22
- MessageDirection,
23
- MessageStatus,
24
- DiscussionDirection,
25
- MessageType,
26
- } from './db';
27
-
28
- // Service result types
29
- export type { PublicKeyResult } from './services/auth';
30
- export type { MessageResult, SendMessageResult } from './services/message';
31
- export type { AnnouncementReceptionResult } from './services/announcement';
32
-
33
- // Utility result types
34
- export type {
35
- UpdateContactNameResult,
36
- DeleteContactResult,
37
- } from './utils/contacts';
38
- export type { UpdateDiscussionNameResult } from './utils/discussions';
39
-
40
- // API types
41
- export type {
42
- IMessageProtocol,
43
- EncryptedMessage,
44
- MessageProtocolResponse,
45
- BulletinItem,
46
- } from './api/messageProtocol/types';
47
-
48
- // Wallet types (pure TypeScript types for the app to use)
49
- export type Ticker = string;
50
-
51
- export interface TokenMeta {
52
- address: string;
53
- name: string;
54
- ticker: Ticker;
55
- icon: string;
56
- decimals: number;
57
- isNative: boolean;
58
- }
59
-
60
- export interface TokenState extends TokenMeta {
61
- balance: bigint | null;
62
- priceUsd: number | null;
63
- valueUsd: number | null;
64
- }
65
-
66
- export interface FeeConfig {
67
- type: 'preset' | 'custom';
68
- preset?: 'low' | 'standard' | 'high';
69
- customFee?: string;
70
- }
@@ -1,39 +0,0 @@
1
- /**
2
- * Base64 encoding/decoding utilities
3
- *
4
- * Provides functions for converting between Uint8Array and Base64 strings.
5
- * Supports both standard and URL-safe Base64 encoding.
6
- */
7
-
8
- export function encodeToBase64(data: Uint8Array): string {
9
- return btoa(
10
- Array.from(data)
11
- .map(byte => String.fromCharCode(byte))
12
- .join('')
13
- );
14
- }
15
-
16
- export function decodeFromBase64(b64: string): Uint8Array {
17
- const decoded = atob(b64);
18
- return new Uint8Array(Array.from(decoded).map(char => char.charCodeAt(0)));
19
- }
20
-
21
- export function encodeToBase64Url(data: Uint8Array): string {
22
- return btoa(
23
- Array.from(data)
24
- .map(byte => String.fromCharCode(byte))
25
- .join('')
26
- )
27
- .replace(/=/g, '')
28
- .replace(/\+/g, '-')
29
- .replace(/\//g, '_');
30
- }
31
-
32
- export function decodeFromBase64Url(base64url: string): Uint8Array {
33
- const base64 = base64url
34
- .replace(/-/g, '+')
35
- .replace(/_/g, '/')
36
- .padEnd(base64url.length + ((4 - (base64url.length % 4)) % 4), '=');
37
-
38
- return decodeFromBase64(base64);
39
- }
@@ -1,161 +0,0 @@
1
- /**
2
- * Contact Utilities
3
- *
4
- * Functions for managing contacts including updating names and deleting contacts.
5
- */
6
-
7
- import { type GossipDatabase } from '../db';
8
- import { decodeUserId } from './userId';
9
- import type { SessionModule } from '../wasm/session';
10
-
11
- export type UpdateContactNameResult =
12
- | { success: true; trimmedName: string }
13
- | {
14
- success: false;
15
- reason: 'empty' | 'duplicate' | 'error';
16
- message: string;
17
- };
18
-
19
- export type DeleteContactResult =
20
- | { success: true }
21
- | {
22
- success: false;
23
- reason: 'not_found' | 'error';
24
- message: string;
25
- };
26
-
27
- /**
28
- * Update the name of a contact
29
- *
30
- * @param ownerUserId - Owner user ID
31
- * @param contactUserId - Contact user ID
32
- * @param newName - New name for the contact
33
- * @param db - Database instance
34
- * @returns Result with success status
35
- */
36
- export async function updateContactName(
37
- ownerUserId: string,
38
- contactUserId: string,
39
- newName: string,
40
- db: GossipDatabase
41
- ): Promise<UpdateContactNameResult> {
42
- if (!ownerUserId || !contactUserId) {
43
- return {
44
- success: false,
45
- reason: 'error',
46
- message: 'Invalid parameters.',
47
- };
48
- }
49
-
50
- const trimmed = newName.trim();
51
- if (!trimmed)
52
- return {
53
- success: false,
54
- reason: 'empty',
55
- message: 'Name cannot be empty.',
56
- };
57
- try {
58
- const list = await db.getContactsByOwner(ownerUserId);
59
- const duplicate = list.find(
60
- contact =>
61
- contact.userId !== contactUserId &&
62
- contact.name.toLowerCase() === trimmed.toLowerCase()
63
- );
64
- if (duplicate)
65
- return {
66
- success: false,
67
- reason: 'duplicate',
68
- message: 'This name is already used by another contact.',
69
- };
70
-
71
- await db.contacts
72
- .where('[ownerUserId+userId]')
73
- .equals([ownerUserId, contactUserId])
74
- .modify({ name: trimmed });
75
-
76
- return { success: true, trimmedName: trimmed };
77
- } catch (e) {
78
- console.error('updateContactName failed', e);
79
- return {
80
- success: false,
81
- reason: 'error',
82
- message: 'Failed to update name. Please try again.',
83
- };
84
- }
85
- }
86
-
87
- /**
88
- * Delete a contact and all associated discussions and messages
89
- *
90
- * @param ownerUserId - Owner user ID
91
- * @param contactUserId - Contact user ID
92
- * @param db - Database instance
93
- * @param session - Session module for peer management
94
- * @returns Result with success status
95
- */
96
- export async function deleteContact(
97
- ownerUserId: string,
98
- contactUserId: string,
99
- db: GossipDatabase,
100
- session: SessionModule
101
- ): Promise<DeleteContactResult> {
102
- try {
103
- if (!ownerUserId || !contactUserId) {
104
- return {
105
- success: false,
106
- reason: 'error',
107
- message: 'Invalid parameters.',
108
- };
109
- }
110
-
111
- // Verify contact exists
112
- const contact = await db.getContactByOwnerAndUserId(
113
- ownerUserId,
114
- contactUserId
115
- );
116
- if (!contact) {
117
- return {
118
- success: false,
119
- reason: 'not_found',
120
- message: 'Contact not found.',
121
- };
122
- }
123
-
124
- // Delete in a transaction to ensure atomicity
125
- await db.transaction(
126
- 'rw',
127
- [db.contacts, db.discussions, db.messages],
128
- async () => {
129
- // Delete the contact
130
- await db.contacts
131
- .where('[ownerUserId+userId]')
132
- .equals([ownerUserId, contactUserId])
133
- .delete();
134
-
135
- // Delete related discussions
136
- await db.discussions
137
- .where('[ownerUserId+contactUserId]')
138
- .equals([ownerUserId, contactUserId])
139
- .delete();
140
-
141
- // Delete related messages
142
- await db.messages
143
- .where('[ownerUserId+contactUserId]')
144
- .equals([ownerUserId, contactUserId])
145
- .delete();
146
- }
147
- );
148
-
149
- // Discard peer from session manager and persist
150
- await session.peerDiscard(decodeUserId(contactUserId));
151
-
152
- return { success: true };
153
- } catch (e) {
154
- console.error('deleteContact failed', e);
155
- return {
156
- success: false,
157
- reason: 'error',
158
- message: 'Failed to delete contact. Please try again.',
159
- };
160
- }
161
- }
@@ -1,55 +0,0 @@
1
- /**
2
- * Discussion Utilities
3
- *
4
- * Functions for managing discussion metadata.
5
- */
6
-
7
- import { type GossipDatabase } from '../db';
8
-
9
- export type UpdateDiscussionNameResult =
10
- | { success: true; trimmedName: string | undefined }
11
- | {
12
- success: false;
13
- reason: 'not_found' | 'error';
14
- message: string;
15
- };
16
-
17
- /**
18
- * Update the custom name of a discussion.
19
- * Pass an empty string or undefined to clear the custom name (will revert to contact name).
20
- *
21
- * @param discussionId - The discussion ID to update
22
- * @param newName - The new custom name (or empty/undefined to clear)
23
- * @param db - Database instance
24
- * @returns Result with success status
25
- */
26
- export async function updateDiscussionName(
27
- discussionId: number,
28
- newName: string | undefined,
29
- db: GossipDatabase
30
- ): Promise<UpdateDiscussionNameResult> {
31
- const trimmed = newName?.trim();
32
- const customName = trimmed && trimmed.length > 0 ? trimmed : undefined;
33
-
34
- try {
35
- const discussion = await db.discussions.get(discussionId);
36
- if (!discussion) {
37
- return {
38
- success: false,
39
- reason: 'not_found',
40
- message: 'Discussion not found.',
41
- };
42
- }
43
-
44
- await db.discussions.update(discussionId, { customName });
45
-
46
- return { success: true, trimmedName: customName };
47
- } catch (e) {
48
- console.error('updateDiscussionName failed', e);
49
- return {
50
- success: false,
51
- reason: 'error',
52
- message: 'Failed to update name. Please try again.',
53
- };
54
- }
55
- }