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