@massalabs/gossip-sdk 0.0.2-dev.20260128094509 → 0.0.2-dev.20260128160824

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 (148) 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 +638 -0
  11. package/dist/assets/generated/wasm/gossip_wasm.js +1557 -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 +164 -0
  14. package/dist/assets/generated/wasm/package.json +15 -0
  15. package/dist/assets/generated/wasm-node/README.md +281 -0
  16. package/dist/assets/generated/wasm-node/gossip_wasm.d.ts +443 -0
  17. package/dist/assets/generated/wasm-node/gossip_wasm.js +1488 -0
  18. package/dist/assets/generated/wasm-node/gossip_wasm_bg.wasm +0 -0
  19. package/dist/assets/generated/wasm-node/gossip_wasm_bg.wasm.d.ts +164 -0
  20. package/dist/assets/generated/wasm-node/package.json +11 -0
  21. package/dist/config/protocol.d.ts +36 -0
  22. package/dist/config/protocol.js +77 -0
  23. package/dist/config/sdk.d.ts +82 -0
  24. package/dist/config/sdk.js +55 -0
  25. package/{src/contacts.ts → dist/contacts.d.ts} +11 -95
  26. package/dist/contacts.js +166 -0
  27. package/dist/core/SdkEventEmitter.d.ts +36 -0
  28. package/dist/core/SdkEventEmitter.js +59 -0
  29. package/dist/core/SdkPolling.d.ts +35 -0
  30. package/dist/core/SdkPolling.js +100 -0
  31. package/{src/core/index.ts → dist/core/index.d.ts} +0 -2
  32. package/dist/core/index.js +5 -0
  33. package/dist/crypto/bip39.d.ts +34 -0
  34. package/dist/crypto/bip39.js +62 -0
  35. package/dist/crypto/encryption.d.ts +37 -0
  36. package/dist/crypto/encryption.js +46 -0
  37. package/dist/db.d.ts +190 -0
  38. package/dist/db.js +311 -0
  39. package/dist/gossipSdk.d.ts +274 -0
  40. package/dist/gossipSdk.js +690 -0
  41. package/dist/index.d.ts +59 -0
  42. package/dist/index.js +61 -0
  43. package/dist/services/announcement.d.ts +43 -0
  44. package/dist/services/announcement.js +491 -0
  45. package/dist/services/auth.d.ts +37 -0
  46. package/dist/services/auth.js +76 -0
  47. package/dist/services/discussion.d.ts +63 -0
  48. package/dist/services/discussion.js +297 -0
  49. package/dist/services/message.d.ts +74 -0
  50. package/dist/services/message.js +826 -0
  51. package/dist/services/refresh.d.ts +41 -0
  52. package/dist/services/refresh.js +205 -0
  53. package/{src/sw.ts → dist/sw.d.ts} +1 -8
  54. package/dist/sw.js +10 -0
  55. package/dist/types/events.d.ts +80 -0
  56. package/dist/types/events.js +7 -0
  57. package/dist/types.d.ts +32 -0
  58. package/dist/types.js +7 -0
  59. package/dist/utils/base64.d.ts +10 -0
  60. package/dist/utils/base64.js +30 -0
  61. package/dist/utils/contacts.d.ts +42 -0
  62. package/dist/utils/contacts.js +113 -0
  63. package/dist/utils/discussions.d.ts +24 -0
  64. package/dist/utils/discussions.js +38 -0
  65. package/dist/utils/logs.d.ts +19 -0
  66. package/dist/utils/logs.js +89 -0
  67. package/dist/utils/messageSerialization.d.ts +64 -0
  68. package/dist/utils/messageSerialization.js +184 -0
  69. package/dist/utils/queue.d.ts +50 -0
  70. package/dist/utils/queue.js +110 -0
  71. package/dist/utils/type.d.ts +10 -0
  72. package/dist/utils/type.js +4 -0
  73. package/dist/utils/userId.d.ts +40 -0
  74. package/dist/utils/userId.js +90 -0
  75. package/dist/utils/validation.d.ts +50 -0
  76. package/dist/utils/validation.js +112 -0
  77. package/dist/utils.d.ts +30 -0
  78. package/{src/utils.ts → dist/utils.js} +9 -19
  79. package/dist/wasm/encryption.d.ts +56 -0
  80. package/{src/wasm/encryption.ts → dist/wasm/encryption.js} +22 -51
  81. package/dist/wasm/index.d.ts +10 -0
  82. package/{src/wasm/index.ts → dist/wasm/index.js} +1 -8
  83. package/dist/wasm/loader.d.ts +22 -0
  84. package/dist/wasm/loader.js +78 -0
  85. package/dist/wasm/session.d.ts +85 -0
  86. package/dist/wasm/session.js +226 -0
  87. package/dist/wasm/userKeys.d.ts +17 -0
  88. package/{src/wasm/userKeys.ts → dist/wasm/userKeys.js} +6 -13
  89. package/package.json +15 -2
  90. package/src/api/messageProtocol/index.ts +0 -53
  91. package/src/api/messageProtocol/rest.ts +0 -209
  92. package/src/api/messageProtocol/types.ts +0 -70
  93. package/src/config/protocol.ts +0 -97
  94. package/src/config/sdk.ts +0 -131
  95. package/src/core/SdkEventEmitter.ts +0 -91
  96. package/src/core/SdkPolling.ts +0 -134
  97. package/src/crypto/bip39.ts +0 -84
  98. package/src/crypto/encryption.ts +0 -77
  99. package/src/db.ts +0 -465
  100. package/src/gossipSdk.ts +0 -994
  101. package/src/index.ts +0 -211
  102. package/src/services/announcement.ts +0 -653
  103. package/src/services/auth.ts +0 -95
  104. package/src/services/discussion.ts +0 -380
  105. package/src/services/message.ts +0 -1055
  106. package/src/services/refresh.ts +0 -234
  107. package/src/types/events.ts +0 -108
  108. package/src/types.ts +0 -70
  109. package/src/utils/base64.ts +0 -39
  110. package/src/utils/contacts.ts +0 -161
  111. package/src/utils/discussions.ts +0 -55
  112. package/src/utils/logs.ts +0 -86
  113. package/src/utils/messageSerialization.ts +0 -257
  114. package/src/utils/queue.ts +0 -106
  115. package/src/utils/type.ts +0 -7
  116. package/src/utils/userId.ts +0 -114
  117. package/src/utils/validation.ts +0 -144
  118. package/src/wasm/loader.ts +0 -123
  119. package/src/wasm/session.ts +0 -276
  120. package/test/config/protocol.spec.ts +0 -31
  121. package/test/config/sdk.spec.ts +0 -163
  122. package/test/db/helpers.spec.ts +0 -142
  123. package/test/db/operations.spec.ts +0 -128
  124. package/test/db/states.spec.ts +0 -535
  125. package/test/integration/discussion-flow.spec.ts +0 -422
  126. package/test/integration/messaging-flow.spec.ts +0 -708
  127. package/test/integration/sdk-lifecycle.spec.ts +0 -325
  128. package/test/mocks/index.ts +0 -9
  129. package/test/mocks/mockMessageProtocol.ts +0 -100
  130. package/test/services/auth.spec.ts +0 -311
  131. package/test/services/discussion.spec.ts +0 -279
  132. package/test/services/message-deduplication.spec.ts +0 -299
  133. package/test/services/message-startup.spec.ts +0 -331
  134. package/test/services/message.spec.ts +0 -817
  135. package/test/services/refresh.spec.ts +0 -199
  136. package/test/services/session-status.spec.ts +0 -349
  137. package/test/session/wasm.spec.ts +0 -227
  138. package/test/setup.ts +0 -52
  139. package/test/utils/contacts.spec.ts +0 -156
  140. package/test/utils/discussions.spec.ts +0 -66
  141. package/test/utils/queue.spec.ts +0 -52
  142. package/test/utils/serialization.spec.ts +0 -120
  143. package/test/utils/userId.spec.ts +0 -120
  144. package/test/utils/validation.spec.ts +0 -223
  145. package/test/utils.ts +0 -212
  146. package/tsconfig.json +0 -26
  147. package/tsconfig.tsbuildinfo +0 -1
  148. package/vitest.config.ts +0 -28
package/dist/db.d.ts ADDED
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Gossip Database
3
+ *
4
+ * IndexedDB database implementation using Dexie for the Gossip messenger.
5
+ * Provides tables for contacts, messages, discussions, user profiles, and more.
6
+ */
7
+ import Dexie, { Table } from 'dexie';
8
+ export type AuthMethod = 'capacitor' | 'webauthn' | 'password';
9
+ export interface Contact {
10
+ id?: number;
11
+ ownerUserId: string;
12
+ userId: string;
13
+ name: string;
14
+ avatar?: string;
15
+ publicKeys: Uint8Array;
16
+ isOnline: boolean;
17
+ lastSeen: Date;
18
+ createdAt: Date;
19
+ }
20
+ export interface Message {
21
+ id?: number;
22
+ ownerUserId: string;
23
+ contactUserId: string;
24
+ content: string;
25
+ serializedContent?: Uint8Array;
26
+ type: MessageType;
27
+ direction: MessageDirection;
28
+ status: MessageStatus;
29
+ timestamp: Date;
30
+ metadata?: Record<string, unknown>;
31
+ seeker?: Uint8Array;
32
+ replyTo?: {
33
+ originalContent?: string;
34
+ originalSeeker: Uint8Array;
35
+ };
36
+ forwardOf?: {
37
+ originalContent?: string;
38
+ originalSeeker: Uint8Array;
39
+ };
40
+ encryptedMessage?: Uint8Array;
41
+ }
42
+ export interface UserProfile {
43
+ userId: string;
44
+ username: string;
45
+ avatar?: string;
46
+ security: {
47
+ encKeySalt: Uint8Array;
48
+ authMethod: AuthMethod;
49
+ webauthn?: {
50
+ credentialId?: string;
51
+ };
52
+ iCloudSync?: boolean;
53
+ mnemonicBackup: {
54
+ encryptedMnemonic: Uint8Array;
55
+ createdAt: Date;
56
+ backedUp: boolean;
57
+ };
58
+ };
59
+ session: Uint8Array;
60
+ bio?: string;
61
+ status: 'online' | 'away' | 'busy' | 'offline';
62
+ lastSeen: Date;
63
+ createdAt: Date;
64
+ updatedAt: Date;
65
+ lastPublicKeyPush?: Date;
66
+ lastBulletinCounter?: string;
67
+ }
68
+ export declare enum DiscussionStatus {
69
+ PENDING = "pending",
70
+ ACTIVE = "active",
71
+ CLOSED = "closed",// closed by the user
72
+ BROKEN = "broken",// The session is killed. Need to be reinitiated
73
+ SEND_FAILED = "sendFailed",// The discussion was initiated by the session manager but could not be broadcasted on network
74
+ RECONNECTING = "reconnecting"
75
+ }
76
+ export declare enum MessageDirection {
77
+ INCOMING = "incoming",
78
+ OUTGOING = "outgoing"
79
+ }
80
+ export declare enum MessageStatus {
81
+ WAITING_SESSION = "waiting_session",// Waiting for active session with peer
82
+ SENDING = "sending",
83
+ SENT = "sent",
84
+ DELIVERED = "delivered",
85
+ READ = "read",
86
+ FAILED = "failed"
87
+ }
88
+ export declare enum DiscussionDirection {
89
+ INITIATED = "initiated",
90
+ RECEIVED = "received"
91
+ }
92
+ export declare enum MessageType {
93
+ TEXT = "text",
94
+ KEEP_ALIVE = "keep_alive",
95
+ IMAGE = "image",
96
+ FILE = "file",
97
+ AUDIO = "audio",
98
+ VIDEO = "video"
99
+ }
100
+ export interface Discussion {
101
+ id?: number;
102
+ ownerUserId: string;
103
+ contactUserId: string;
104
+ direction: DiscussionDirection;
105
+ status: DiscussionStatus;
106
+ nextSeeker?: Uint8Array;
107
+ initiationAnnouncement?: Uint8Array;
108
+ announcementMessage?: string;
109
+ lastSyncTimestamp?: Date;
110
+ customName?: string;
111
+ lastMessageId?: number;
112
+ lastMessageContent?: string;
113
+ lastMessageTimestamp?: Date;
114
+ unreadCount: number;
115
+ createdAt: Date;
116
+ updatedAt: Date;
117
+ }
118
+ export interface PendingEncryptedMessage {
119
+ id?: number;
120
+ seeker: Uint8Array;
121
+ ciphertext: Uint8Array;
122
+ fetchedAt: Date;
123
+ }
124
+ export interface PendingAnnouncement {
125
+ id?: number;
126
+ announcement: Uint8Array;
127
+ fetchedAt: Date;
128
+ counter?: string;
129
+ }
130
+ export interface ActiveSeeker {
131
+ id?: number;
132
+ seeker: Uint8Array;
133
+ }
134
+ export declare class GossipDatabase extends Dexie {
135
+ contacts: Table<Contact>;
136
+ messages: Table<Message>;
137
+ userProfile: Table<UserProfile>;
138
+ discussions: Table<Discussion>;
139
+ pendingEncryptedMessages: Table<PendingEncryptedMessage>;
140
+ pendingAnnouncements: Table<PendingAnnouncement>;
141
+ activeSeekers: Table<ActiveSeeker>;
142
+ constructor();
143
+ /** CONTACTS */
144
+ getContactsByOwner(ownerUserId: string): Promise<Contact[]>;
145
+ getContactByOwnerAndUserId(ownerUserId: string, userId: string): Promise<Contact | undefined>;
146
+ /** DISCUSSIONS */
147
+ getDiscussionsByOwner(ownerUserId: string): Promise<Discussion[]>;
148
+ getUnreadCountByOwner(ownerUserId: string): Promise<number>;
149
+ getDiscussionByOwnerAndContact(ownerUserId: string, contactUserId: string): Promise<Discussion | undefined>;
150
+ /**
151
+ * Get all active discussions with their sync status
152
+ * @returns Array of active discussions
153
+ */
154
+ getActiveDiscussionsByOwner(ownerUserId: string): Promise<Discussion[]>;
155
+ markMessagesAsRead(ownerUserId: string, contactUserId: string): Promise<void>;
156
+ getMessagesForContactByOwner(ownerUserId: string, contactUserId: string, limit?: number): Promise<Message[]>;
157
+ addMessage(message: Omit<Message, 'id'>): Promise<number>;
158
+ /**
159
+ * Update the last sync timestamp for a discussion
160
+ * @param discussionId - The discussion ID
161
+ * @param timestamp - The sync timestamp
162
+ */
163
+ updateLastSyncTimestamp(discussionId: number, timestamp: Date): Promise<void>;
164
+ deleteDb(): Promise<void>;
165
+ /**
166
+ * Set all active seekers, replacing any existing ones
167
+ * @param seekers - Array of seeker Uint8Arrays to store
168
+ */
169
+ setActiveSeekers(seekers: Uint8Array[]): Promise<void>;
170
+ /**
171
+ * Get all active seekers from the database
172
+ * @returns Array of seeker Uint8Arrays
173
+ */
174
+ getActiveSeekers(): Promise<Uint8Array[]>;
175
+ }
176
+ /**
177
+ * Get the database instance.
178
+ * Creates a default instance if none was set via setDb().
179
+ */
180
+ export declare function getDb(): GossipDatabase;
181
+ /**
182
+ * Set the database instance.
183
+ * Call this before using any SDK functions if you need a custom db instance.
184
+ */
185
+ export declare function setDb(database: GossipDatabase): void;
186
+ /**
187
+ * Get the database instance.
188
+ * Creates a default instance if none was set via setDb().
189
+ */
190
+ export declare const db: GossipDatabase;
package/dist/db.js ADDED
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Gossip Database
3
+ *
4
+ * IndexedDB database implementation using Dexie for the Gossip messenger.
5
+ * Provides tables for contacts, messages, discussions, user profiles, and more.
6
+ */
7
+ import Dexie from 'dexie';
8
+ // Unified discussion interface combining protocol state and UI metadata
9
+ export var DiscussionStatus;
10
+ (function (DiscussionStatus) {
11
+ DiscussionStatus["PENDING"] = "pending";
12
+ DiscussionStatus["ACTIVE"] = "active";
13
+ DiscussionStatus["CLOSED"] = "closed";
14
+ DiscussionStatus["BROKEN"] = "broken";
15
+ DiscussionStatus["SEND_FAILED"] = "sendFailed";
16
+ DiscussionStatus["RECONNECTING"] = "reconnecting";
17
+ })(DiscussionStatus || (DiscussionStatus = {}));
18
+ export var MessageDirection;
19
+ (function (MessageDirection) {
20
+ MessageDirection["INCOMING"] = "incoming";
21
+ MessageDirection["OUTGOING"] = "outgoing";
22
+ })(MessageDirection || (MessageDirection = {}));
23
+ export var MessageStatus;
24
+ (function (MessageStatus) {
25
+ MessageStatus["WAITING_SESSION"] = "waiting_session";
26
+ MessageStatus["SENDING"] = "sending";
27
+ MessageStatus["SENT"] = "sent";
28
+ MessageStatus["DELIVERED"] = "delivered";
29
+ MessageStatus["READ"] = "read";
30
+ MessageStatus["FAILED"] = "failed";
31
+ })(MessageStatus || (MessageStatus = {}));
32
+ export var DiscussionDirection;
33
+ (function (DiscussionDirection) {
34
+ DiscussionDirection["INITIATED"] = "initiated";
35
+ DiscussionDirection["RECEIVED"] = "received";
36
+ })(DiscussionDirection || (DiscussionDirection = {}));
37
+ export var MessageType;
38
+ (function (MessageType) {
39
+ MessageType["TEXT"] = "text";
40
+ MessageType["KEEP_ALIVE"] = "keep_alive";
41
+ MessageType["IMAGE"] = "image";
42
+ MessageType["FILE"] = "file";
43
+ MessageType["AUDIO"] = "audio";
44
+ MessageType["VIDEO"] = "video";
45
+ })(MessageType || (MessageType = {}));
46
+ // Define the database class
47
+ export class GossipDatabase extends Dexie {
48
+ constructor() {
49
+ super('GossipDatabase');
50
+ // Define tables
51
+ Object.defineProperty(this, "contacts", {
52
+ enumerable: true,
53
+ configurable: true,
54
+ writable: true,
55
+ value: void 0
56
+ });
57
+ Object.defineProperty(this, "messages", {
58
+ enumerable: true,
59
+ configurable: true,
60
+ writable: true,
61
+ value: void 0
62
+ });
63
+ Object.defineProperty(this, "userProfile", {
64
+ enumerable: true,
65
+ configurable: true,
66
+ writable: true,
67
+ value: void 0
68
+ });
69
+ Object.defineProperty(this, "discussions", {
70
+ enumerable: true,
71
+ configurable: true,
72
+ writable: true,
73
+ value: void 0
74
+ });
75
+ Object.defineProperty(this, "pendingEncryptedMessages", {
76
+ enumerable: true,
77
+ configurable: true,
78
+ writable: true,
79
+ value: void 0
80
+ });
81
+ Object.defineProperty(this, "pendingAnnouncements", {
82
+ enumerable: true,
83
+ configurable: true,
84
+ writable: true,
85
+ value: void 0
86
+ });
87
+ Object.defineProperty(this, "activeSeekers", {
88
+ enumerable: true,
89
+ configurable: true,
90
+ writable: true,
91
+ value: void 0
92
+ });
93
+ this.version(13).stores({
94
+ contacts: '++id, ownerUserId, userId, name, isOnline, lastSeen, createdAt, [ownerUserId+userId] , [ownerUserId+name]',
95
+ messages: '++id, ownerUserId, contactUserId, type, direction, status, timestamp, seeker, [ownerUserId+contactUserId], [ownerUserId+status], [ownerUserId+contactUserId+status], [ownerUserId+seeker], [ownerUserId+contactUserId+direction], [ownerUserId+direction+status]',
96
+ userProfile: 'userId, username, status, lastSeen',
97
+ discussions: '++id, ownerUserId, &[ownerUserId+contactUserId], status, [ownerUserId+status], lastSyncTimestamp, unreadCount, lastMessageTimestamp, createdAt, updatedAt',
98
+ pendingEncryptedMessages: '++id, fetchedAt, seeker',
99
+ pendingAnnouncements: '++id, fetchedAt, &announcement',
100
+ activeSeekers: '++id, seeker',
101
+ });
102
+ // Add hooks for automatic timestamps
103
+ this.contacts.hook('creating', function (_primKey, obj, _trans) {
104
+ obj.createdAt = new Date();
105
+ });
106
+ this.userProfile.hook('creating', function (_primKey, obj, _trans) {
107
+ obj.createdAt = new Date();
108
+ obj.updatedAt = new Date();
109
+ });
110
+ this.userProfile.hook('updating', function (modifications, _primKey, _obj, _trans) {
111
+ modifications.updatedAt = new Date();
112
+ });
113
+ this.discussions.hook('creating', function (_primKey, obj, _trans) {
114
+ obj.createdAt = new Date();
115
+ obj.updatedAt = new Date();
116
+ });
117
+ this.discussions.hook('updating', function (modifications, _primKey, _obj, _trans) {
118
+ modifications.updatedAt = new Date();
119
+ });
120
+ }
121
+ // Helper methods for common operations
122
+ /** CONTACTS */
123
+ async getContactsByOwner(ownerUserId) {
124
+ return await this.contacts
125
+ .where('ownerUserId')
126
+ .equals(ownerUserId)
127
+ .toArray();
128
+ }
129
+ async getContactByOwnerAndUserId(ownerUserId, userId) {
130
+ return await this.contacts
131
+ .where('[ownerUserId+userId]')
132
+ .equals([ownerUserId, userId])
133
+ .first();
134
+ }
135
+ /** DISCUSSIONS */
136
+ async getDiscussionsByOwner(ownerUserId) {
137
+ const all = await this.discussions
138
+ .where('ownerUserId')
139
+ .equals(ownerUserId)
140
+ .toArray();
141
+ return all.sort((a, b) => {
142
+ if (a.lastMessageTimestamp && b.lastMessageTimestamp) {
143
+ return (b.lastMessageTimestamp.getTime() - a.lastMessageTimestamp.getTime());
144
+ }
145
+ if (a.lastMessageTimestamp)
146
+ return -1;
147
+ if (b.lastMessageTimestamp)
148
+ return 1;
149
+ return b.createdAt.getTime() - a.createdAt.getTime();
150
+ });
151
+ }
152
+ async getUnreadCountByOwner(ownerUserId) {
153
+ const discussions = await this.discussions
154
+ .where('ownerUserId')
155
+ .equals(ownerUserId)
156
+ .toArray();
157
+ return discussions.reduce((total, d) => total + d.unreadCount, 0);
158
+ }
159
+ async getDiscussionByOwnerAndContact(ownerUserId, contactUserId) {
160
+ if (!ownerUserId || !contactUserId) {
161
+ return undefined;
162
+ }
163
+ return await this.discussions
164
+ .where('[ownerUserId+contactUserId]')
165
+ .equals([ownerUserId, contactUserId])
166
+ .first();
167
+ }
168
+ /**
169
+ * Get all active discussions with their sync status
170
+ * @returns Array of active discussions
171
+ */
172
+ async getActiveDiscussionsByOwner(ownerUserId) {
173
+ return await this.discussions
174
+ .where('[ownerUserId+status]')
175
+ .equals([ownerUserId, DiscussionStatus.ACTIVE])
176
+ .toArray();
177
+ }
178
+ async markMessagesAsRead(ownerUserId, contactUserId) {
179
+ await this.messages
180
+ .where('[ownerUserId+contactUserId+status]')
181
+ .equals([ownerUserId, contactUserId, MessageStatus.DELIVERED])
182
+ .and(msg => msg.direction === MessageDirection.INCOMING)
183
+ .modify({ status: MessageStatus.READ });
184
+ await this.discussions
185
+ .where('[ownerUserId+contactUserId]')
186
+ .equals([ownerUserId, contactUserId])
187
+ .modify({ unreadCount: 0 });
188
+ }
189
+ async getMessagesForContactByOwner(ownerUserId, contactUserId, limit = 50) {
190
+ return await this.messages
191
+ .where('[ownerUserId+contactUserId]')
192
+ .equals([ownerUserId, contactUserId])
193
+ .reverse()
194
+ .limit(limit)
195
+ .toArray();
196
+ }
197
+ async addMessage(message) {
198
+ const messageId = await this.messages.add(message);
199
+ // Get existing discussion
200
+ const discussion = await this.getDiscussionByOwnerAndContact(message.ownerUserId, message.contactUserId);
201
+ if (discussion) {
202
+ await this.discussions.update(discussion.id, {
203
+ lastMessageId: messageId,
204
+ lastMessageContent: message.content,
205
+ lastMessageTimestamp: message.timestamp,
206
+ unreadCount: message.direction === MessageDirection.INCOMING
207
+ ? discussion.unreadCount + 1
208
+ : discussion.unreadCount,
209
+ updatedAt: new Date(),
210
+ });
211
+ }
212
+ else {
213
+ // Note: For new messages, a discussion should already exist from the protocol
214
+ // If not, we'll create a minimal one (this shouldn't normally happen)
215
+ console.log('Warning: Creating discussion for contact without protocol setup:', message.contactUserId);
216
+ await this.discussions.put({
217
+ ownerUserId: message.ownerUserId,
218
+ contactUserId: message.contactUserId,
219
+ direction: message.direction === MessageDirection.INCOMING
220
+ ? DiscussionDirection.RECEIVED
221
+ : DiscussionDirection.INITIATED,
222
+ status: DiscussionStatus.PENDING,
223
+ nextSeeker: undefined,
224
+ lastMessageId: messageId,
225
+ lastMessageContent: message.content,
226
+ lastMessageTimestamp: message.timestamp,
227
+ unreadCount: message.direction === MessageDirection.INCOMING ? 1 : 0,
228
+ createdAt: new Date(),
229
+ updatedAt: new Date(),
230
+ });
231
+ }
232
+ return messageId;
233
+ }
234
+ /**
235
+ * Update the last sync timestamp for a discussion
236
+ * @param discussionId - The discussion ID
237
+ * @param timestamp - The sync timestamp
238
+ */
239
+ async updateLastSyncTimestamp(discussionId, timestamp) {
240
+ await this.discussions.update(discussionId, {
241
+ lastSyncTimestamp: timestamp,
242
+ updatedAt: new Date(),
243
+ });
244
+ }
245
+ async deleteDb() {
246
+ await this.close();
247
+ await this.delete();
248
+ }
249
+ /**
250
+ * Set all active seekers, replacing any existing ones
251
+ * @param seekers - Array of seeker Uint8Arrays to store
252
+ */
253
+ async setActiveSeekers(seekers) {
254
+ await this.transaction('rw', this.activeSeekers, async () => {
255
+ // Clear all existing seekers
256
+ await this.activeSeekers.clear();
257
+ // Bulk add all new seekers
258
+ if (seekers.length > 0) {
259
+ await this.activeSeekers.bulkAdd(seekers.map(seeker => ({
260
+ seeker,
261
+ })));
262
+ }
263
+ });
264
+ }
265
+ /**
266
+ * Get all active seekers from the database
267
+ * @returns Array of seeker Uint8Arrays
268
+ */
269
+ async getActiveSeekers() {
270
+ const activeSeekers = await this.activeSeekers.toArray();
271
+ return activeSeekers.map(item => item.seeker);
272
+ }
273
+ }
274
+ // Database instance - initialized lazily or via setDb()
275
+ let _db = null;
276
+ let _warnedGlobalDbAccess = false;
277
+ /**
278
+ * Get the database instance.
279
+ * Creates a default instance if none was set via setDb().
280
+ */
281
+ export function getDb() {
282
+ if (!_db) {
283
+ _db = new GossipDatabase();
284
+ }
285
+ return _db;
286
+ }
287
+ /**
288
+ * Set the database instance.
289
+ * Call this before using any SDK functions if you need a custom db instance.
290
+ */
291
+ export function setDb(database) {
292
+ _db = database;
293
+ }
294
+ /**
295
+ * Get the database instance.
296
+ * Creates a default instance if none was set via setDb().
297
+ */
298
+ export const db = new Proxy({}, {
299
+ get(_target, prop) {
300
+ if (!_warnedGlobalDbAccess) {
301
+ _warnedGlobalDbAccess = true;
302
+ console.warn('[GossipSdk] Global db access is deprecated. Use createGossipSdk() or setDb().');
303
+ }
304
+ const target = getDb();
305
+ const value = Reflect.get(target, prop);
306
+ if (typeof value === 'function') {
307
+ return value.bind(target);
308
+ }
309
+ return value;
310
+ },
311
+ });