@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.
- package/dist/api/messageProtocol/index.d.ts +19 -0
- package/dist/api/messageProtocol/index.js +26 -0
- package/dist/api/messageProtocol/mock.d.ts +12 -0
- package/{src/api/messageProtocol/mock.ts → dist/api/messageProtocol/mock.js} +2 -3
- package/dist/api/messageProtocol/rest.d.ts +22 -0
- package/dist/api/messageProtocol/rest.js +161 -0
- package/dist/api/messageProtocol/types.d.ts +61 -0
- package/dist/api/messageProtocol/types.js +6 -0
- package/dist/assets/generated/wasm/README.md +281 -0
- package/dist/assets/generated/wasm/gossip_wasm.d.ts +498 -0
- package/dist/assets/generated/wasm/gossip_wasm.js +1399 -0
- package/dist/assets/generated/wasm/gossip_wasm_bg.wasm +0 -0
- package/dist/assets/generated/wasm/gossip_wasm_bg.wasm.d.ts +68 -0
- package/dist/assets/generated/wasm/package.json +15 -0
- package/dist/config/protocol.d.ts +36 -0
- package/dist/config/protocol.js +77 -0
- package/dist/config/sdk.d.ts +82 -0
- package/dist/config/sdk.js +55 -0
- package/{src/contacts.ts → dist/contacts.d.ts} +10 -94
- package/dist/contacts.js +166 -0
- package/dist/core/SdkEventEmitter.d.ts +36 -0
- package/dist/core/SdkEventEmitter.js +59 -0
- package/dist/core/SdkPolling.d.ts +35 -0
- package/dist/core/SdkPolling.js +100 -0
- package/{src/core/index.ts → dist/core/index.d.ts} +0 -2
- package/dist/core/index.js +5 -0
- package/dist/crypto/bip39.d.ts +34 -0
- package/dist/crypto/bip39.js +62 -0
- package/dist/crypto/encryption.d.ts +37 -0
- package/dist/crypto/encryption.js +46 -0
- package/dist/db.d.ts +190 -0
- package/dist/db.js +311 -0
- package/dist/gossipSdk.d.ts +274 -0
- package/dist/gossipSdk.js +690 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +77 -0
- package/dist/services/announcement.d.ts +43 -0
- package/dist/services/announcement.js +491 -0
- package/dist/services/auth.d.ts +37 -0
- package/dist/services/auth.js +76 -0
- package/dist/services/discussion.d.ts +63 -0
- package/dist/services/discussion.js +297 -0
- package/dist/services/message.d.ts +74 -0
- package/dist/services/message.js +826 -0
- package/dist/services/refresh.d.ts +41 -0
- package/dist/services/refresh.js +205 -0
- package/{src/sw.ts → dist/sw.d.ts} +1 -8
- package/dist/sw.js +10 -0
- package/dist/types/events.d.ts +80 -0
- package/dist/types/events.js +7 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +7 -0
- package/dist/utils/base64.d.ts +10 -0
- package/dist/utils/base64.js +30 -0
- package/dist/utils/contacts.d.ts +42 -0
- package/dist/utils/contacts.js +113 -0
- package/dist/utils/discussions.d.ts +24 -0
- package/dist/utils/discussions.js +38 -0
- package/dist/utils/logs.d.ts +19 -0
- package/dist/utils/logs.js +89 -0
- package/dist/utils/messageSerialization.d.ts +64 -0
- package/dist/utils/messageSerialization.js +184 -0
- package/dist/utils/queue.d.ts +50 -0
- package/dist/utils/queue.js +110 -0
- package/dist/utils/type.d.ts +10 -0
- package/dist/utils/type.js +4 -0
- package/dist/utils/userId.d.ts +40 -0
- package/dist/utils/userId.js +90 -0
- package/dist/utils/validation.d.ts +50 -0
- package/dist/utils/validation.js +112 -0
- package/dist/utils.d.ts +30 -0
- package/{src/utils.ts → dist/utils.js} +9 -19
- package/dist/wasm/encryption.d.ts +56 -0
- package/{src/wasm/encryption.ts → dist/wasm/encryption.js} +22 -51
- package/dist/wasm/index.d.ts +10 -0
- package/{src/wasm/index.ts → dist/wasm/index.js} +1 -8
- package/dist/wasm/loader.d.ts +21 -0
- package/dist/wasm/loader.js +103 -0
- package/dist/wasm/session.d.ts +85 -0
- package/dist/wasm/session.js +226 -0
- package/dist/wasm/userKeys.d.ts +17 -0
- package/{src/wasm/userKeys.ts → dist/wasm/userKeys.js} +6 -13
- package/package.json +5 -1
- package/src/api/messageProtocol/index.ts +0 -53
- package/src/api/messageProtocol/rest.ts +0 -209
- package/src/api/messageProtocol/types.ts +0 -70
- package/src/config/protocol.ts +0 -97
- package/src/config/sdk.ts +0 -131
- package/src/core/SdkEventEmitter.ts +0 -91
- package/src/core/SdkPolling.ts +0 -134
- package/src/crypto/bip39.ts +0 -84
- package/src/crypto/encryption.ts +0 -77
- package/src/db.ts +0 -465
- package/src/gossipSdk.ts +0 -994
- package/src/index.ts +0 -211
- package/src/services/announcement.ts +0 -653
- package/src/services/auth.ts +0 -95
- package/src/services/discussion.ts +0 -380
- package/src/services/message.ts +0 -1055
- package/src/services/refresh.ts +0 -234
- package/src/types/events.ts +0 -108
- package/src/types.ts +0 -70
- package/src/utils/base64.ts +0 -39
- package/src/utils/contacts.ts +0 -161
- package/src/utils/discussions.ts +0 -55
- package/src/utils/logs.ts +0 -86
- package/src/utils/messageSerialization.ts +0 -257
- package/src/utils/queue.ts +0 -106
- package/src/utils/type.ts +0 -7
- package/src/utils/userId.ts +0 -114
- package/src/utils/validation.ts +0 -144
- package/src/wasm/loader.ts +0 -123
- package/src/wasm/session.ts +0 -276
- package/test/config/protocol.spec.ts +0 -31
- package/test/config/sdk.spec.ts +0 -163
- package/test/db/helpers.spec.ts +0 -142
- package/test/db/operations.spec.ts +0 -128
- package/test/db/states.spec.ts +0 -535
- package/test/integration/discussion-flow.spec.ts +0 -422
- package/test/integration/messaging-flow.spec.ts +0 -708
- package/test/integration/sdk-lifecycle.spec.ts +0 -325
- package/test/mocks/index.ts +0 -9
- package/test/mocks/mockMessageProtocol.ts +0 -100
- package/test/services/auth.spec.ts +0 -311
- package/test/services/discussion.spec.ts +0 -279
- package/test/services/message-deduplication.spec.ts +0 -299
- package/test/services/message-startup.spec.ts +0 -331
- package/test/services/message.spec.ts +0 -817
- package/test/services/refresh.spec.ts +0 -199
- package/test/services/session-status.spec.ts +0 -349
- package/test/session/wasm.spec.ts +0 -227
- package/test/setup.ts +0 -52
- package/test/utils/contacts.spec.ts +0 -156
- package/test/utils/discussions.spec.ts +0 -66
- package/test/utils/queue.spec.ts +0 -52
- package/test/utils/serialization.spec.ts +0 -120
- package/test/utils/userId.spec.ts +0 -120
- package/test/utils/validation.spec.ts +0 -223
- package/test/utils.ts +0 -212
- package/tsconfig.json +0 -26
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -28
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
|
+
});
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GossipSdk - Singleton SDK with clean lifecycle API
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { gossipSdk } from 'gossip-sdk';
|
|
7
|
+
*
|
|
8
|
+
* // Initialize once at app startup
|
|
9
|
+
* await gossipSdk.init({
|
|
10
|
+
* db,
|
|
11
|
+
* protocolBaseUrl: 'https://api.example.com',
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* // Open session (login) - SDK handles keys/session internally
|
|
15
|
+
* await gossipSdk.openSession({
|
|
16
|
+
* mnemonic: 'word1 word2 ...',
|
|
17
|
+
* onPersist: async (blob) => { /* save to db *\/ },
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // Or restore existing session
|
|
21
|
+
* await gossipSdk.openSession({
|
|
22
|
+
* mnemonic: 'word1 word2 ...',
|
|
23
|
+
* encryptedSession: savedBlob,
|
|
24
|
+
* encryptionKey: key,
|
|
25
|
+
* onPersist: async (blob) => { /* save to db *\/ },
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Use clean API
|
|
29
|
+
* await gossipSdk.messages.send(contactId, 'Hello!');
|
|
30
|
+
* await gossipSdk.discussions.start(contact);
|
|
31
|
+
* const contacts = await gossipSdk.contacts.list(ownerUserId);
|
|
32
|
+
*
|
|
33
|
+
* // Events
|
|
34
|
+
* gossipSdk.on('message', (msg) => { ... });
|
|
35
|
+
* gossipSdk.on('discussionRequest', (discussion, contact) => { ... });
|
|
36
|
+
*
|
|
37
|
+
* // Logout
|
|
38
|
+
* await gossipSdk.closeSession();
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
import { GossipDatabase, type Contact, type Discussion, type Message } from './db';
|
|
42
|
+
import { type SdkConfig, type DeepPartial } from './config/sdk';
|
|
43
|
+
import { EncryptionKey } from './wasm/encryption';
|
|
44
|
+
import { type AnnouncementReceptionResult } from './services/announcement';
|
|
45
|
+
import { type MessageResult, type SendMessageResult } from './services/message';
|
|
46
|
+
import { AuthService } from './services/auth';
|
|
47
|
+
import type { DeleteContactResult, UpdateContactNameResult } from './utils/contacts';
|
|
48
|
+
import { type ValidationResult } from './utils/validation';
|
|
49
|
+
import type { UserPublicKeys } from './assets/generated/wasm/gossip_wasm';
|
|
50
|
+
import { type SdkEventType, type SdkEventHandlers } from './core/SdkEventEmitter';
|
|
51
|
+
export type { SdkEventType, SdkEventHandlers };
|
|
52
|
+
export interface GossipSdkInitOptions {
|
|
53
|
+
/** Database instance */
|
|
54
|
+
db: GossipDatabase;
|
|
55
|
+
/** Protocol API base URL (shorthand for config.protocol.baseUrl) */
|
|
56
|
+
protocolBaseUrl?: string;
|
|
57
|
+
/** SDK configuration (optional - uses defaults if not provided) */
|
|
58
|
+
config?: DeepPartial<SdkConfig>;
|
|
59
|
+
}
|
|
60
|
+
export interface OpenSessionOptions {
|
|
61
|
+
/** BIP39 mnemonic phrase */
|
|
62
|
+
mnemonic: string;
|
|
63
|
+
/** Existing encrypted session blob (for restoring session) */
|
|
64
|
+
encryptedSession?: Uint8Array;
|
|
65
|
+
/** Encryption key for decrypting session */
|
|
66
|
+
encryptionKey?: EncryptionKey;
|
|
67
|
+
/** Callback when session state changes (for persistence) */
|
|
68
|
+
onPersist?: (encryptedBlob: Uint8Array, encryptionKey: EncryptionKey) => Promise<void>;
|
|
69
|
+
/** Encryption key for persisting session (required if onPersist is provided) */
|
|
70
|
+
persistEncryptionKey?: EncryptionKey;
|
|
71
|
+
}
|
|
72
|
+
declare class GossipSdkImpl {
|
|
73
|
+
private state;
|
|
74
|
+
private eventEmitter;
|
|
75
|
+
private pollingManager;
|
|
76
|
+
private messageQueues;
|
|
77
|
+
private _auth;
|
|
78
|
+
private _announcement;
|
|
79
|
+
private _discussion;
|
|
80
|
+
private _message;
|
|
81
|
+
private _refresh;
|
|
82
|
+
private _messagesAPI;
|
|
83
|
+
private _discussionsAPI;
|
|
84
|
+
private _announcementsAPI;
|
|
85
|
+
private _contactsAPI;
|
|
86
|
+
private _refreshAPI;
|
|
87
|
+
/**
|
|
88
|
+
* Initialize the SDK. Call once at app startup.
|
|
89
|
+
*/
|
|
90
|
+
init(options: GossipSdkInitOptions): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Open a session (login).
|
|
93
|
+
* Generates keys from mnemonic and initializes session.
|
|
94
|
+
*/
|
|
95
|
+
openSession(options: OpenSessionOptions): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Create cached service API wrappers.
|
|
98
|
+
* Called once during openSession to avoid creating new objects on each getter access.
|
|
99
|
+
*/
|
|
100
|
+
private createServiceAPIWrappers;
|
|
101
|
+
/**
|
|
102
|
+
* Close the current session (logout).
|
|
103
|
+
*/
|
|
104
|
+
closeSession(): Promise<void>;
|
|
105
|
+
/** Current user ID (encoded). Throws if no session is open. */
|
|
106
|
+
get userId(): string;
|
|
107
|
+
/** Current user ID (raw bytes). Throws if no session is open. */
|
|
108
|
+
get userIdBytes(): Uint8Array;
|
|
109
|
+
/** User's public keys. Throws if no session is open. */
|
|
110
|
+
get publicKeys(): UserPublicKeys;
|
|
111
|
+
/** Whether a session is currently open */
|
|
112
|
+
get isSessionOpen(): boolean;
|
|
113
|
+
/** Whether SDK is initialized */
|
|
114
|
+
get isInitialized(): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Get encrypted session blob for persistence.
|
|
117
|
+
* Throws if no session is open.
|
|
118
|
+
*/
|
|
119
|
+
getEncryptedSession(encryptionKey: EncryptionKey): Uint8Array;
|
|
120
|
+
/**
|
|
121
|
+
* Configure session persistence after session is opened.
|
|
122
|
+
* Use this when you need to set up persistence after account creation.
|
|
123
|
+
*
|
|
124
|
+
* @param encryptionKey - Key to encrypt session blob
|
|
125
|
+
* @param onPersist - Callback to save encrypted session blob
|
|
126
|
+
*/
|
|
127
|
+
configurePersistence(encryptionKey: EncryptionKey, onPersist: (encryptedBlob: Uint8Array, encryptionKey: EncryptionKey) => Promise<void>): void;
|
|
128
|
+
/** Auth service (available after init, before session) */
|
|
129
|
+
get auth(): AuthService;
|
|
130
|
+
/** Message service */
|
|
131
|
+
get messages(): MessageServiceAPI;
|
|
132
|
+
/** Discussion service */
|
|
133
|
+
get discussions(): DiscussionServiceAPI;
|
|
134
|
+
/** Announcement service */
|
|
135
|
+
get announcements(): AnnouncementServiceAPI;
|
|
136
|
+
/** Contact management */
|
|
137
|
+
get contacts(): ContactsAPI;
|
|
138
|
+
/** Refresh/sync service */
|
|
139
|
+
get refresh(): RefreshServiceAPI;
|
|
140
|
+
/** Utility functions */
|
|
141
|
+
get utils(): SdkUtils;
|
|
142
|
+
/** Current SDK configuration (read-only) */
|
|
143
|
+
get config(): SdkConfig;
|
|
144
|
+
/** Polling control API */
|
|
145
|
+
get polling(): PollingAPI;
|
|
146
|
+
/**
|
|
147
|
+
* Start polling for messages, announcements, and session refresh.
|
|
148
|
+
* Uses intervals from config.polling.
|
|
149
|
+
*/
|
|
150
|
+
private startPolling;
|
|
151
|
+
/**
|
|
152
|
+
* Register an event handler
|
|
153
|
+
*/
|
|
154
|
+
on<K extends SdkEventType>(event: K, handler: SdkEventHandlers[K]): void;
|
|
155
|
+
/**
|
|
156
|
+
* Remove an event handler
|
|
157
|
+
*/
|
|
158
|
+
off<K extends SdkEventType>(event: K, handler: SdkEventHandlers[K]): void;
|
|
159
|
+
private requireSession;
|
|
160
|
+
/**
|
|
161
|
+
* Handle automatic session renewal when session is lost.
|
|
162
|
+
* Called by onSessionRenewalNeeded event.
|
|
163
|
+
*/
|
|
164
|
+
private handleSessionRenewal;
|
|
165
|
+
/**
|
|
166
|
+
* Handle automatic session accept when peer has sent us an announcement.
|
|
167
|
+
* Called by onSessionAcceptNeeded event.
|
|
168
|
+
* This is different from renewal - we respond to their session request.
|
|
169
|
+
*/
|
|
170
|
+
private handleSessionAccept;
|
|
171
|
+
/**
|
|
172
|
+
* Handle session becoming Active after peer accepts our announcement.
|
|
173
|
+
* Called by onSessionBecameActive event.
|
|
174
|
+
*
|
|
175
|
+
* This is different from handleSessionAccept:
|
|
176
|
+
* - handleSessionAccept: WE accept a session (peer initiated)
|
|
177
|
+
* - handleSessionBecameActive: PEER accepts our session (we initiated)
|
|
178
|
+
*/
|
|
179
|
+
private handleSessionBecameActive;
|
|
180
|
+
private handleSessionPersist;
|
|
181
|
+
/**
|
|
182
|
+
* Reset any messages stuck in SENDING status to FAILED.
|
|
183
|
+
* This handles the case where the app crashed or was closed during message send.
|
|
184
|
+
* Per spec: SENDING should never be persisted - if we find it on startup, it failed.
|
|
185
|
+
*/
|
|
186
|
+
/**
|
|
187
|
+
* Reset messages stuck in SENDING status to WAITING_SESSION.
|
|
188
|
+
*
|
|
189
|
+
* Per spec: SENDING is a transient state that should never be persisted.
|
|
190
|
+
* If the app crashes/closes during a send, the message would be stuck forever.
|
|
191
|
+
*
|
|
192
|
+
* By resetting to WAITING_SESSION:
|
|
193
|
+
* - Message will be re-encrypted with current session keys
|
|
194
|
+
* - Message will be automatically sent when session is active
|
|
195
|
+
* - No manual user intervention required
|
|
196
|
+
*
|
|
197
|
+
* We also clear encryptedMessage and seeker since they may be stale.
|
|
198
|
+
*/
|
|
199
|
+
private resetStuckSendingMessages;
|
|
200
|
+
}
|
|
201
|
+
interface MessageServiceAPI {
|
|
202
|
+
/** Send a message */
|
|
203
|
+
send(message: Omit<Message, 'id'>): Promise<SendMessageResult>;
|
|
204
|
+
/** Fetch and decrypt messages from the protocol */
|
|
205
|
+
fetch(): Promise<MessageResult>;
|
|
206
|
+
/** Resend failed messages */
|
|
207
|
+
resend(messages: Map<string, Message[]>): Promise<void>;
|
|
208
|
+
/** Find a message by its seeker */
|
|
209
|
+
findBySeeker(seeker: Uint8Array, ownerUserId: string): Promise<Message | undefined>;
|
|
210
|
+
}
|
|
211
|
+
interface DiscussionServiceAPI {
|
|
212
|
+
/** Start a new discussion with a contact */
|
|
213
|
+
start(contact: Contact, message?: string): Promise<{
|
|
214
|
+
discussionId: number;
|
|
215
|
+
announcement: Uint8Array;
|
|
216
|
+
}>;
|
|
217
|
+
/** Accept an incoming discussion request */
|
|
218
|
+
accept(discussion: Discussion): Promise<void>;
|
|
219
|
+
/** Renew a broken discussion */
|
|
220
|
+
renew(contactUserId: string): Promise<void>;
|
|
221
|
+
/** Check if a discussion is in a stable state */
|
|
222
|
+
isStable(ownerUserId: string, contactUserId: string): Promise<boolean>;
|
|
223
|
+
/** List all discussions for the owner */
|
|
224
|
+
list(ownerUserId: string): Promise<Discussion[]>;
|
|
225
|
+
/** Get a specific discussion */
|
|
226
|
+
get(ownerUserId: string, contactUserId: string): Promise<Discussion | undefined>;
|
|
227
|
+
}
|
|
228
|
+
interface AnnouncementServiceAPI {
|
|
229
|
+
/** Fetch and process announcements from the protocol */
|
|
230
|
+
fetch(): Promise<AnnouncementReceptionResult>;
|
|
231
|
+
/** Resend failed announcements */
|
|
232
|
+
resend(failedDiscussions: Discussion[]): Promise<void>;
|
|
233
|
+
}
|
|
234
|
+
interface ContactsAPI {
|
|
235
|
+
/** List all contacts for the owner */
|
|
236
|
+
list(ownerUserId: string): Promise<Contact[]>;
|
|
237
|
+
/** Get a specific contact */
|
|
238
|
+
get(ownerUserId: string, contactUserId: string): Promise<Contact | null>;
|
|
239
|
+
/** Add a new contact */
|
|
240
|
+
add(ownerUserId: string, userId: string, name: string, publicKeys: UserPublicKeys): Promise<{
|
|
241
|
+
success: boolean;
|
|
242
|
+
error?: string;
|
|
243
|
+
contact?: Contact;
|
|
244
|
+
}>;
|
|
245
|
+
/** Update a contact's name */
|
|
246
|
+
updateName(ownerUserId: string, contactUserId: string, newName: string): Promise<UpdateContactNameResult>;
|
|
247
|
+
/** Delete a contact and all related data */
|
|
248
|
+
delete(ownerUserId: string, contactUserId: string): Promise<DeleteContactResult>;
|
|
249
|
+
}
|
|
250
|
+
interface RefreshServiceAPI {
|
|
251
|
+
/** Handle session refresh (keep-alive, broken sessions, etc.) */
|
|
252
|
+
handleSessionRefresh(activeDiscussions: Discussion[]): Promise<void>;
|
|
253
|
+
}
|
|
254
|
+
interface SdkUtils {
|
|
255
|
+
/** Validate a user ID format */
|
|
256
|
+
validateUserId(userId: string): ValidationResult;
|
|
257
|
+
/** Validate a username format */
|
|
258
|
+
validateUsername(username: string): ValidationResult;
|
|
259
|
+
/** Encode raw bytes to user ID string */
|
|
260
|
+
encodeUserId(rawId: Uint8Array): string;
|
|
261
|
+
/** Decode user ID string to raw bytes */
|
|
262
|
+
decodeUserId(encodedId: string): Uint8Array;
|
|
263
|
+
}
|
|
264
|
+
interface PollingAPI {
|
|
265
|
+
/** Start polling for messages, announcements, and session refresh */
|
|
266
|
+
start(): void;
|
|
267
|
+
/** Stop all polling */
|
|
268
|
+
stop(): void;
|
|
269
|
+
/** Whether polling is currently running */
|
|
270
|
+
isRunning: boolean;
|
|
271
|
+
}
|
|
272
|
+
/** The singleton GossipSdk instance */
|
|
273
|
+
export declare const gossipSdk: GossipSdkImpl;
|
|
274
|
+
export { GossipSdkImpl };
|