@thezelijah/majik-message 1.0.16 → 1.0.17
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/core/contacts/majik-contact-directory.d.ts +4 -1
- package/dist/core/contacts/majik-contact-directory.js +24 -2
- package/dist/core/contacts/majik-contact.d.ts +5 -0
- package/dist/core/contacts/majik-contact.js +14 -0
- package/dist/core/database/chat/majik-message-chat.d.ts +3 -2
- package/dist/core/database/chat/majik-message-chat.js +13 -5
- package/dist/core/database/chat/types.d.ts +1 -0
- package/dist/majik-message.d.ts +5 -2
- package/dist/majik-message.js +18 -5
- package/package.json +1 -1
|
@@ -24,11 +24,14 @@ export declare class MajikContactDirectory {
|
|
|
24
24
|
*/
|
|
25
25
|
getContactByPublicKeyBase64(publicKeyBase64: string): Promise<MajikContact | undefined>;
|
|
26
26
|
hasFingerprint(fingerprint: string): boolean;
|
|
27
|
-
listContacts(sortedByLabel?: boolean): MajikContact[];
|
|
27
|
+
listContacts(sortedByLabel?: boolean, majikahOnly?: boolean): MajikContact[];
|
|
28
28
|
blockContact(id: string): MajikContact;
|
|
29
29
|
unblockContact(id: string): MajikContact;
|
|
30
30
|
hasContact(id: string): boolean;
|
|
31
31
|
clear(): this;
|
|
32
|
+
setMajikahStatus(id: string, status: boolean): MajikContact;
|
|
33
|
+
isMajikahIdentityChecked(id: string): boolean;
|
|
34
|
+
isMajikahRegistered(id: string): boolean;
|
|
32
35
|
/**
|
|
33
36
|
* Checks if a given envelope corresponds to a known contact
|
|
34
37
|
*/
|
|
@@ -100,8 +100,11 @@ export class MajikContactDirectory {
|
|
|
100
100
|
hasFingerprint(fingerprint) {
|
|
101
101
|
return this.fingerprintMap.has(fingerprint);
|
|
102
102
|
}
|
|
103
|
-
listContacts(sortedByLabel = false) {
|
|
104
|
-
|
|
103
|
+
listContacts(sortedByLabel = false, majikahOnly = false) {
|
|
104
|
+
let contacts = [...this.contacts.values()];
|
|
105
|
+
if (majikahOnly) {
|
|
106
|
+
contacts = contacts.filter((c) => c.isMajikahRegistered());
|
|
107
|
+
}
|
|
105
108
|
if (sortedByLabel) {
|
|
106
109
|
contacts.sort((a, b) => (a.meta.label || "").localeCompare(b.meta.label || ""));
|
|
107
110
|
}
|
|
@@ -127,6 +130,25 @@ export class MajikContactDirectory {
|
|
|
127
130
|
this.fingerprintMap.clear();
|
|
128
131
|
return this;
|
|
129
132
|
}
|
|
133
|
+
setMajikahStatus(id, status) {
|
|
134
|
+
const contact = this.getContact(id);
|
|
135
|
+
if (!contact)
|
|
136
|
+
throw new MajikContactDirectoryError("Contact not found");
|
|
137
|
+
contact.setMajikahStatus(status);
|
|
138
|
+
return contact;
|
|
139
|
+
}
|
|
140
|
+
isMajikahIdentityChecked(id) {
|
|
141
|
+
const contact = this.getContact(id);
|
|
142
|
+
if (!contact)
|
|
143
|
+
throw new MajikContactDirectoryError("Contact not found");
|
|
144
|
+
return contact.isMajikahIdentityChecked();
|
|
145
|
+
}
|
|
146
|
+
isMajikahRegistered(id) {
|
|
147
|
+
const contact = this.getContact(id);
|
|
148
|
+
if (!contact)
|
|
149
|
+
throw new MajikContactDirectoryError("Contact not found");
|
|
150
|
+
return contact.isMajikahRegistered();
|
|
151
|
+
}
|
|
130
152
|
/**
|
|
131
153
|
* Checks if a given envelope corresponds to a known contact
|
|
132
154
|
*/
|
|
@@ -17,6 +17,7 @@ export interface MajikContactData {
|
|
|
17
17
|
};
|
|
18
18
|
fingerprint: string;
|
|
19
19
|
meta?: MajikContactMeta;
|
|
20
|
+
majikah_registered?: boolean;
|
|
20
21
|
}
|
|
21
22
|
export interface MajikContactCard {
|
|
22
23
|
id: string;
|
|
@@ -35,6 +36,7 @@ export declare class MajikContact {
|
|
|
35
36
|
};
|
|
36
37
|
readonly fingerprint: string;
|
|
37
38
|
meta: MajikContactMeta;
|
|
39
|
+
private majikah_registered?;
|
|
38
40
|
constructor(data: MajikContactData);
|
|
39
41
|
static create(id: string, publicKey: CryptoKey | {
|
|
40
42
|
raw: Uint8Array;
|
|
@@ -49,6 +51,9 @@ export declare class MajikContact {
|
|
|
49
51
|
setBlocked(blocked: boolean): this;
|
|
50
52
|
block(): this;
|
|
51
53
|
unblock(): this;
|
|
54
|
+
isMajikahIdentityChecked(): boolean;
|
|
55
|
+
isMajikahRegistered(): boolean;
|
|
56
|
+
setMajikahStatus(status: boolean): this;
|
|
52
57
|
/**
|
|
53
58
|
* Support both CryptoKey and raw-key wrappers (fallbacks when WebCrypto X25519 unsupported)
|
|
54
59
|
*/
|
|
@@ -18,6 +18,7 @@ export class MajikContact {
|
|
|
18
18
|
publicKey;
|
|
19
19
|
fingerprint;
|
|
20
20
|
meta;
|
|
21
|
+
majikah_registered;
|
|
21
22
|
constructor(data) {
|
|
22
23
|
this.assertId(data.id);
|
|
23
24
|
this.assertPublicKey(data.publicKey);
|
|
@@ -103,6 +104,16 @@ export class MajikContact {
|
|
|
103
104
|
this.setBlocked(false);
|
|
104
105
|
return this;
|
|
105
106
|
}
|
|
107
|
+
isMajikahIdentityChecked() {
|
|
108
|
+
return this.majikah_registered !== undefined;
|
|
109
|
+
}
|
|
110
|
+
isMajikahRegistered() {
|
|
111
|
+
return this.majikah_registered || false;
|
|
112
|
+
}
|
|
113
|
+
setMajikahStatus(status) {
|
|
114
|
+
this.majikah_registered = status;
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
106
117
|
/**
|
|
107
118
|
* Support both CryptoKey and raw-key wrappers (fallbacks when WebCrypto X25519 unsupported)
|
|
108
119
|
*/
|
|
@@ -127,6 +138,7 @@ export class MajikContact {
|
|
|
127
138
|
fingerprint: this.fingerprint,
|
|
128
139
|
meta: { ...this.meta },
|
|
129
140
|
publicKeyBase64: await this.getPublicKeyBase64(),
|
|
141
|
+
majikah_registered: this.majikah_registered,
|
|
130
142
|
};
|
|
131
143
|
}
|
|
132
144
|
/**
|
|
@@ -140,6 +152,7 @@ export class MajikContact {
|
|
|
140
152
|
fingerprint: serialized.fingerprint,
|
|
141
153
|
meta: serialized.meta,
|
|
142
154
|
publicKey: { raw: publicKeyRaw },
|
|
155
|
+
majikah_registered: serialized.majikah_registered,
|
|
143
156
|
});
|
|
144
157
|
}
|
|
145
158
|
catch (err) {
|
|
@@ -162,6 +175,7 @@ export class MajikContact {
|
|
|
162
175
|
updatedAt: identityJSON.timestamp,
|
|
163
176
|
blocked: identityJSON.restricted,
|
|
164
177
|
},
|
|
178
|
+
majikah_registered: true,
|
|
165
179
|
};
|
|
166
180
|
return new MajikContact(contactData);
|
|
167
181
|
}
|
|
@@ -16,8 +16,9 @@ export declare class MajikMessageChat {
|
|
|
16
16
|
private expires_at;
|
|
17
17
|
private read_by;
|
|
18
18
|
private conversation_id;
|
|
19
|
+
private permanent;
|
|
19
20
|
private static readonly MAX_MESSAGE_LENGTH;
|
|
20
|
-
constructor(id: MajikMessageChatID, account: MajikMessageAccountID, message: string, sender: MajikMessagePublicKey, recipients: MajikMessagePublicKey[], timestamp: string, expires_at: string, read_by?: string[], conversation_id?: string);
|
|
21
|
+
constructor(id: MajikMessageChatID, account: MajikMessageAccountID, message: string, sender: MajikMessagePublicKey, recipients: MajikMessagePublicKey[], timestamp: string, expires_at: string, read_by?: string[], conversation_id?: string, permanent?: boolean);
|
|
21
22
|
getID(): string;
|
|
22
23
|
getConversationID(): string;
|
|
23
24
|
get account(): MajikMessageAccountID;
|
|
@@ -53,7 +54,7 @@ export declare class MajikMessageChat {
|
|
|
53
54
|
* @returns Promise resolving to new MajikMessageChat instance with compressed message
|
|
54
55
|
* @throws Error if validation or compression fails
|
|
55
56
|
*/
|
|
56
|
-
static create(account: MajikMessageIdentity, message: string, recipients: string[], expiresInMs?: number): Promise<MajikMessageChat>;
|
|
57
|
+
static create(account: MajikMessageIdentity, message: string, recipients: string[], expiresInMs?: number, permanent?: boolean): Promise<MajikMessageChat>;
|
|
57
58
|
/**
|
|
58
59
|
* Generate a deterministic conversation ID from a message JSON
|
|
59
60
|
* Reads sender and recipients directly from JSON without parsing
|
|
@@ -16,9 +16,10 @@ export class MajikMessageChat {
|
|
|
16
16
|
expires_at;
|
|
17
17
|
read_by;
|
|
18
18
|
conversation_id;
|
|
19
|
+
permanent;
|
|
19
20
|
// Maximum allowed length for the compressed message string
|
|
20
21
|
static MAX_MESSAGE_LENGTH = 10000;
|
|
21
|
-
constructor(id, account, message, sender, recipients, timestamp, expires_at, read_by = [], conversation_id) {
|
|
22
|
+
constructor(id, account, message, sender, recipients, timestamp, expires_at, read_by = [], conversation_id, permanent = false) {
|
|
22
23
|
this.validateID(id);
|
|
23
24
|
this.validateAccount(account);
|
|
24
25
|
this.validateMessage(message);
|
|
@@ -35,6 +36,7 @@ export class MajikMessageChat {
|
|
|
35
36
|
this.timestamp = timestamp;
|
|
36
37
|
this.expires_at = expires_at;
|
|
37
38
|
this.read_by = [...read_by]; // Clone to prevent external mutation
|
|
39
|
+
this.permanent = permanent;
|
|
38
40
|
this.conversation_id = conversation_id || this.generateConversationID();
|
|
39
41
|
}
|
|
40
42
|
// ============= GETTERS =============
|
|
@@ -117,7 +119,7 @@ export class MajikMessageChat {
|
|
|
117
119
|
* @returns Promise resolving to new MajikMessageChat instance with compressed message
|
|
118
120
|
* @throws Error if validation or compression fails
|
|
119
121
|
*/
|
|
120
|
-
static async create(account, message, recipients, expiresInMs = 24 * 60 * 60 * 1000) {
|
|
122
|
+
static async create(account, message, recipients, expiresInMs = 24 * 60 * 60 * 1000, permanent = false) {
|
|
121
123
|
if (!account) {
|
|
122
124
|
throw new Error("Invalid sender account: must be provided");
|
|
123
125
|
}
|
|
@@ -153,7 +155,7 @@ export class MajikMessageChat {
|
|
|
153
155
|
catch (error) {
|
|
154
156
|
throw new Error(`Failed to compress message: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
155
157
|
}
|
|
156
|
-
return new MajikMessageChat(autogenerateID(), accountID, compressedMessage, senderID.trim(), recipients.map((r) => r.trim()).filter((r) => r !== ""), now.toISOString(), expiresAt.toISOString(), []);
|
|
158
|
+
return new MajikMessageChat(autogenerateID(), accountID, compressedMessage, senderID.trim(), recipients.map((r) => r.trim()).filter((r) => r !== ""), now.toISOString(), expiresAt.toISOString(), [], undefined, permanent);
|
|
157
159
|
}
|
|
158
160
|
// ============= STATIC HELPER METHODS =============
|
|
159
161
|
/**
|
|
@@ -210,6 +212,9 @@ export class MajikMessageChat {
|
|
|
210
212
|
}
|
|
211
213
|
// ============= EXPIRATION METHODS =============
|
|
212
214
|
isExpired() {
|
|
215
|
+
if (this.permanent) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
213
218
|
const now = new Date();
|
|
214
219
|
const expiresAt = new Date(this.expires_at);
|
|
215
220
|
return now >= expiresAt;
|
|
@@ -292,7 +297,9 @@ export class MajikMessageChat {
|
|
|
292
297
|
}
|
|
293
298
|
// ============= READER MANAGEMENT =============
|
|
294
299
|
markAsRead(userPublicKey) {
|
|
295
|
-
if (!userPublicKey ||
|
|
300
|
+
if (!userPublicKey ||
|
|
301
|
+
typeof userPublicKey !== "string" ||
|
|
302
|
+
userPublicKey.trim() === "") {
|
|
296
303
|
throw new Error("Invalid userPublicKey: must be a non-empty string");
|
|
297
304
|
}
|
|
298
305
|
const trimmedId = userPublicKey.trim();
|
|
@@ -355,6 +362,7 @@ export class MajikMessageChat {
|
|
|
355
362
|
timestamp: this.timestamp,
|
|
356
363
|
expires_at: this.expires_at,
|
|
357
364
|
read_by: [...this.read_by],
|
|
365
|
+
permanent: this.permanent,
|
|
358
366
|
};
|
|
359
367
|
}
|
|
360
368
|
static fromJSON(json) {
|
|
@@ -362,7 +370,7 @@ export class MajikMessageChat {
|
|
|
362
370
|
if (!this.isValidJSON(rawParse)) {
|
|
363
371
|
throw new Error("Invalid JSON: missing required fields or invalid types");
|
|
364
372
|
}
|
|
365
|
-
return new MajikMessageChat(rawParse.id, rawParse.account, rawParse.message, rawParse.sender, rawParse.recipients || [], rawParse.timestamp, rawParse.expires_at, rawParse.read_by || [], rawParse?.conversation_id);
|
|
373
|
+
return new MajikMessageChat(rawParse.id, rawParse.account, rawParse.message, rawParse.sender, rawParse.recipients || [], rawParse.timestamp, rawParse.expires_at, rawParse.read_by || [], rawParse?.conversation_id, rawParse?.permanent);
|
|
366
374
|
}
|
|
367
375
|
// ============= REDIS METHODS =============
|
|
368
376
|
getRedisKey() {
|
package/dist/majik-message.d.ts
CHANGED
|
@@ -80,7 +80,7 @@ export declare class MajikMessage {
|
|
|
80
80
|
backup: string;
|
|
81
81
|
}>;
|
|
82
82
|
addOwnAccount(account: MajikContact): void;
|
|
83
|
-
listOwnAccounts(): MajikContact[];
|
|
83
|
+
listOwnAccounts(majikahOnly?: boolean): MajikContact[];
|
|
84
84
|
getOwnAccountById(id: string): MajikContact | undefined;
|
|
85
85
|
/**
|
|
86
86
|
* Set an active account (moves it to index 0)
|
|
@@ -136,7 +136,10 @@ export declare class MajikMessage {
|
|
|
136
136
|
updateContactMeta(id: string, meta: Partial<MajikContactMeta>): void;
|
|
137
137
|
blockContact(id: string): void;
|
|
138
138
|
unblockContact(id: string): void;
|
|
139
|
-
listContacts(all?: boolean): MajikContact[];
|
|
139
|
+
listContacts(all?: boolean, majikahOnly?: boolean): MajikContact[];
|
|
140
|
+
isContactMajikahRegistered(id: string): boolean;
|
|
141
|
+
isContactMajikahIdentityChecked(id: string): boolean;
|
|
142
|
+
setContactMajikahStatus(id: string, status: boolean): void;
|
|
140
143
|
/**
|
|
141
144
|
* Update the passphrase for an identity.
|
|
142
145
|
* - `id` defaults to the current active account if not provided.
|
package/dist/majik-message.js
CHANGED
|
@@ -148,10 +148,13 @@ export class MajikMessage {
|
|
|
148
148
|
}
|
|
149
149
|
this.scheduleAutosave();
|
|
150
150
|
}
|
|
151
|
-
listOwnAccounts() {
|
|
152
|
-
|
|
151
|
+
listOwnAccounts(majikahOnly = false) {
|
|
152
|
+
let userAccounts = this.ownAccountsOrder
|
|
153
153
|
.map((id) => this.ownAccounts.get(id))
|
|
154
154
|
.filter((c) => !!c);
|
|
155
|
+
if (majikahOnly) {
|
|
156
|
+
userAccounts = userAccounts.filter((acct) => this.isContactMajikahRegistered(acct.id));
|
|
157
|
+
}
|
|
155
158
|
return userAccounts;
|
|
156
159
|
}
|
|
157
160
|
getOwnAccountById(id) {
|
|
@@ -421,15 +424,25 @@ export class MajikMessage {
|
|
|
421
424
|
this.contactDirectory.unblockContact(id);
|
|
422
425
|
this.scheduleAutosave();
|
|
423
426
|
}
|
|
424
|
-
listContacts(all = true) {
|
|
425
|
-
const contacts = this.contactDirectory.listContacts(true);
|
|
427
|
+
listContacts(all = true, majikahOnly = false) {
|
|
428
|
+
const contacts = this.contactDirectory.listContacts(true, majikahOnly);
|
|
426
429
|
if (all) {
|
|
427
430
|
return contacts;
|
|
428
431
|
}
|
|
429
|
-
const userAccounts = this.listOwnAccounts();
|
|
432
|
+
const userAccounts = this.listOwnAccounts(majikahOnly);
|
|
430
433
|
const userAccountIds = new Set(userAccounts.map((a) => a.id));
|
|
431
434
|
return contacts.filter((contact) => !userAccountIds.has(contact.id));
|
|
432
435
|
}
|
|
436
|
+
isContactMajikahRegistered(id) {
|
|
437
|
+
return this.contactDirectory.isMajikahRegistered(id);
|
|
438
|
+
}
|
|
439
|
+
isContactMajikahIdentityChecked(id) {
|
|
440
|
+
return this.contactDirectory.isMajikahIdentityChecked(id);
|
|
441
|
+
}
|
|
442
|
+
setContactMajikahStatus(id, status) {
|
|
443
|
+
this.contactDirectory.setMajikahStatus(id, status);
|
|
444
|
+
this.scheduleAutosave();
|
|
445
|
+
}
|
|
433
446
|
/**
|
|
434
447
|
* Update the passphrase for an identity.
|
|
435
448
|
* - `id` defaults to the current active account if not provided.
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@thezelijah/majik-message",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"description": "Encrypt and decrypt messages on any website. Secure chats with keypairs and seed-based accounts. Open source.",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.17",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Zelijah",
|
|
8
8
|
"main": "./dist/index.js",
|