@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.
- 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 +638 -0
- package/dist/assets/generated/wasm/gossip_wasm.js +1557 -0
- package/dist/assets/generated/wasm/gossip_wasm_bg.wasm +0 -0
- package/dist/assets/generated/wasm/gossip_wasm_bg.wasm.d.ts +164 -0
- package/dist/assets/generated/wasm/package.json +15 -0
- package/dist/assets/generated/wasm-node/README.md +281 -0
- package/dist/assets/generated/wasm-node/gossip_wasm.d.ts +443 -0
- package/dist/assets/generated/wasm-node/gossip_wasm.js +1488 -0
- package/dist/assets/generated/wasm-node/gossip_wasm_bg.wasm +0 -0
- package/dist/assets/generated/wasm-node/gossip_wasm_bg.wasm.d.ts +164 -0
- package/dist/assets/generated/wasm-node/package.json +11 -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} +11 -95
- 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 +59 -0
- package/dist/index.js +61 -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 +22 -0
- package/dist/wasm/loader.js +78 -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 +15 -2
- 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.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
|
+
});
|