@thezelijah/majik-message 1.0.17 → 1.0.18
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.d.ts +1 -0
- package/dist/core/contacts/majik-contact.js +3 -0
- package/dist/core/crypto/keystore.d.ts +2 -2
- package/dist/core/crypto/keystore.js +6 -10
- package/dist/core/messages/envelope-cache.d.ts +2 -1
- package/dist/core/messages/envelope-cache.js +5 -2
- package/dist/core/utils/idb-majik-system.d.ts +3 -3
- package/dist/core/utils/idb-majik-system.js +7 -6
- package/dist/majik-message.d.ts +5 -2
- package/dist/majik-message.js +119 -72
- package/package.json +1 -1
|
@@ -54,6 +54,7 @@ export declare class MajikContact {
|
|
|
54
54
|
isMajikahIdentityChecked(): boolean;
|
|
55
55
|
isMajikahRegistered(): boolean;
|
|
56
56
|
setMajikahStatus(status: boolean): this;
|
|
57
|
+
getDisplayName(): Promise<string>;
|
|
57
58
|
/**
|
|
58
59
|
* Support both CryptoKey and raw-key wrappers (fallbacks when WebCrypto X25519 unsupported)
|
|
59
60
|
*/
|
|
@@ -114,6 +114,9 @@ export class MajikContact {
|
|
|
114
114
|
this.majikah_registered = status;
|
|
115
115
|
return this;
|
|
116
116
|
}
|
|
117
|
+
async getDisplayName() {
|
|
118
|
+
return this.meta.label || (await this.getPublicKeyBase64());
|
|
119
|
+
}
|
|
117
120
|
/**
|
|
118
121
|
* Support both CryptoKey and raw-key wrappers (fallbacks when WebCrypto X25519 unsupported)
|
|
119
122
|
*/
|
|
@@ -31,13 +31,13 @@ export declare class KeyStoreError extends Error {
|
|
|
31
31
|
* ⚠️ Background-only. Never expose private keys to content scripts.
|
|
32
32
|
*/
|
|
33
33
|
export declare class KeyStore {
|
|
34
|
-
private static
|
|
34
|
+
private static deviceID;
|
|
35
35
|
private static STORE_NAME;
|
|
36
36
|
private static DB_VERSION;
|
|
37
37
|
private static dbPromise;
|
|
38
38
|
private static unlockedIdentities;
|
|
39
39
|
static onUnlockRequested?: (id: string) => string | Promise<string>;
|
|
40
|
-
|
|
40
|
+
static init(deviceID: string): void;
|
|
41
41
|
private static getDB;
|
|
42
42
|
private static putSerializedIdentity;
|
|
43
43
|
private static getSerializedIdentity;
|
|
@@ -28,7 +28,7 @@ export class KeyStoreError extends Error {
|
|
|
28
28
|
* ⚠️ Background-only. Never expose private keys to content scripts.
|
|
29
29
|
*/
|
|
30
30
|
export class KeyStore {
|
|
31
|
-
static
|
|
31
|
+
static deviceID = "default";
|
|
32
32
|
static STORE_NAME = "identities";
|
|
33
33
|
static DB_VERSION = 1;
|
|
34
34
|
static dbPromise = null;
|
|
@@ -37,22 +37,18 @@ export class KeyStore {
|
|
|
37
37
|
// Optional callback: invoked when UI needs to request a passphrase to unlock an identity.
|
|
38
38
|
// Should return the passphrase string or a Promise<string>.
|
|
39
39
|
static onUnlockRequested;
|
|
40
|
-
/* ================================
|
|
41
|
-
* Chrome Storage Helpers
|
|
42
|
-
* ================================ */
|
|
43
|
-
static async computeChecksum(identities) {
|
|
44
|
-
const json = JSON.stringify(identities);
|
|
45
|
-
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(json));
|
|
46
|
-
return arrayBufferToBase64(hash);
|
|
47
|
-
}
|
|
48
40
|
/* ================================
|
|
49
41
|
* IndexedDB Helpers
|
|
50
42
|
* ================================ */
|
|
43
|
+
static init(deviceID) {
|
|
44
|
+
this.deviceID = deviceID;
|
|
45
|
+
}
|
|
51
46
|
static async getDB() {
|
|
52
47
|
if (this.dbPromise)
|
|
53
48
|
return this.dbPromise;
|
|
49
|
+
const dbName = this.deviceID;
|
|
54
50
|
this.dbPromise = new Promise((resolve, reject) => {
|
|
55
|
-
const request = indexedDB.open(
|
|
51
|
+
const request = indexedDB.open(dbName, this.DB_VERSION);
|
|
56
52
|
request.onupgradeneeded = () => {
|
|
57
53
|
const db = request.result;
|
|
58
54
|
if (!db.objectStoreNames.contains(this.STORE_NAME)) {
|
|
@@ -28,13 +28,14 @@ export interface EnvelopeCacheItem {
|
|
|
28
28
|
message?: string;
|
|
29
29
|
}
|
|
30
30
|
export declare class EnvelopeCache {
|
|
31
|
+
private userProfile;
|
|
31
32
|
private dbPromise;
|
|
32
33
|
private dbName;
|
|
33
34
|
private storeName;
|
|
34
35
|
private maxEntries?;
|
|
35
36
|
private memoryCache;
|
|
36
37
|
private memoryCacheSize;
|
|
37
|
-
constructor(config?: EnvelopeCacheConfig);
|
|
38
|
+
constructor(config?: EnvelopeCacheConfig, userProfile?: string);
|
|
38
39
|
private initDB;
|
|
39
40
|
private getEnvelopeId;
|
|
40
41
|
set(envelope: MessageEnvelope, source?: string): Promise<void>;
|
|
@@ -15,6 +15,7 @@ export class EnvelopeCacheError extends Error {
|
|
|
15
15
|
* EnvelopeCache
|
|
16
16
|
* ------------------------------- */
|
|
17
17
|
export class EnvelopeCache {
|
|
18
|
+
userProfile = "default";
|
|
18
19
|
dbPromise;
|
|
19
20
|
dbName;
|
|
20
21
|
storeName;
|
|
@@ -22,12 +23,13 @@ export class EnvelopeCache {
|
|
|
22
23
|
// In-memory cache
|
|
23
24
|
memoryCache;
|
|
24
25
|
memoryCacheSize;
|
|
25
|
-
constructor(config) {
|
|
26
|
-
this.dbName = config?.dbName ||
|
|
26
|
+
constructor(config, userProfile = "default") {
|
|
27
|
+
this.dbName = config?.dbName || `MajikEnvelopeDB_${userProfile}`;
|
|
27
28
|
this.storeName = config?.storeName || "envelopes";
|
|
28
29
|
this.maxEntries = config?.maxEntries;
|
|
29
30
|
this.memoryCacheSize = config?.memoryCacheSize || 100;
|
|
30
31
|
this.memoryCache = new Map();
|
|
32
|
+
this.userProfile = userProfile || "default";
|
|
31
33
|
this.dbPromise = this.initDB();
|
|
32
34
|
}
|
|
33
35
|
/* -------------------------------
|
|
@@ -35,6 +37,7 @@ export class EnvelopeCache {
|
|
|
35
37
|
* ------------------------------- */
|
|
36
38
|
initDB() {
|
|
37
39
|
return new Promise((resolve, reject) => {
|
|
40
|
+
console.log("DB: ", this.dbName);
|
|
38
41
|
const request = indexedDB.open(this.dbName, 1);
|
|
39
42
|
request.onupgradeneeded = (event) => {
|
|
40
43
|
const db = event.target.result;
|
|
@@ -7,9 +7,9 @@ export interface MajikIDBSaveData {
|
|
|
7
7
|
interface MajikAutosaveSchema {
|
|
8
8
|
majikdata: MajikIDBSaveData;
|
|
9
9
|
}
|
|
10
|
-
export declare function initDB(): Promise<IDBPDatabase<MajikAutosaveSchema>>;
|
|
11
|
-
export declare function idbSaveBlob(id: string, data: Blob): Promise<void>;
|
|
12
|
-
export declare function idbLoadBlob(id: string): Promise<MajikIDBSaveData | undefined>;
|
|
10
|
+
export declare function initDB(name?: string): Promise<IDBPDatabase<MajikAutosaveSchema>>;
|
|
11
|
+
export declare function idbSaveBlob(id: string, data: Blob, name?: string): Promise<void>;
|
|
12
|
+
export declare function idbLoadBlob(id: string, name?: string): Promise<MajikIDBSaveData | undefined>;
|
|
13
13
|
export declare function deleteBlob(id: string): Promise<void>;
|
|
14
14
|
export declare function clearAllBlobs(): Promise<void>;
|
|
15
15
|
export {};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// lib/indexedDB.ts
|
|
2
2
|
import { openDB } from "idb";
|
|
3
3
|
let dbPromise;
|
|
4
|
-
export function initDB() {
|
|
4
|
+
export function initDB(name = "default") {
|
|
5
5
|
if (!dbPromise) {
|
|
6
|
-
|
|
6
|
+
const dbName = `MajikAutosaveDB_${name}`;
|
|
7
|
+
dbPromise = openDB(dbName, 1, {
|
|
7
8
|
upgrade(db) {
|
|
8
9
|
if (!db.objectStoreNames.contains("majikdata")) {
|
|
9
10
|
db.createObjectStore("majikdata", { keyPath: "id" });
|
|
@@ -13,13 +14,13 @@ export function initDB() {
|
|
|
13
14
|
}
|
|
14
15
|
return dbPromise;
|
|
15
16
|
}
|
|
16
|
-
export async function idbSaveBlob(id, data) {
|
|
17
|
-
const db = await initDB();
|
|
17
|
+
export async function idbSaveBlob(id, data, name = "default") {
|
|
18
|
+
const db = await initDB(name);
|
|
18
19
|
await db.put("majikdata", { id, data, savedAt: Date.now() });
|
|
19
20
|
}
|
|
20
|
-
export async function idbLoadBlob(id) {
|
|
21
|
+
export async function idbLoadBlob(id, name = "default") {
|
|
21
22
|
try {
|
|
22
|
-
const db = await initDB();
|
|
23
|
+
const db = await initDB(name);
|
|
23
24
|
return await db.get("majikdata", id);
|
|
24
25
|
}
|
|
25
26
|
catch (err) {
|
package/dist/majik-message.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface MajikMessageJSON {
|
|
|
27
27
|
}
|
|
28
28
|
type EventCallback = (...args: any[]) => void;
|
|
29
29
|
export declare class MajikMessage {
|
|
30
|
+
private userProfile;
|
|
30
31
|
private pinHash?;
|
|
31
32
|
private id;
|
|
32
33
|
private contactDirectory;
|
|
@@ -38,7 +39,8 @@ export declare class MajikMessage {
|
|
|
38
39
|
private autosaveTimer;
|
|
39
40
|
private autosaveIntervalMs;
|
|
40
41
|
private autosaveDebounceMs;
|
|
41
|
-
|
|
42
|
+
private unlocked;
|
|
43
|
+
constructor(config: MajikMessageConfig, id?: string, userProfile?: string);
|
|
42
44
|
/**
|
|
43
45
|
* Create a new account (generates identity via KeyStore) and add it as an own account.
|
|
44
46
|
* Returns the created identity id and a backup blob (base64) that the user should store.
|
|
@@ -205,6 +207,7 @@ export declare class MajikMessage {
|
|
|
205
207
|
ensureIdentityUnlocked(id: string, promptFn?: (identityId: string) => string | Promise<string>): Promise<CryptoKey | {
|
|
206
208
|
raw: Uint8Array;
|
|
207
209
|
}>;
|
|
210
|
+
isUnlocked(): boolean;
|
|
208
211
|
isPassphraseValid(passphrase: string, id?: string): Promise<boolean>;
|
|
209
212
|
toJSON(): Promise<MajikMessageJSON>;
|
|
210
213
|
static fromJSON<T extends MajikMessage>(this: new (config: MajikMessageConfig, id?: string) => T, json: MajikMessageJSON): Promise<T>;
|
|
@@ -228,6 +231,6 @@ export declare class MajikMessage {
|
|
|
228
231
|
/**
|
|
229
232
|
* Try to load an existing state from IDB; if none exists, create a fresh instance and save it.
|
|
230
233
|
*/
|
|
231
|
-
static loadOrCreate<T extends MajikMessage>(this: MajikMessageStatic<T>, config: MajikMessageConfig): Promise<T>;
|
|
234
|
+
static loadOrCreate<T extends MajikMessage>(this: MajikMessageStatic<T>, config: MajikMessageConfig, userProfile?: string): Promise<T>;
|
|
232
235
|
}
|
|
233
236
|
export {};
|
package/dist/majik-message.js
CHANGED
|
@@ -14,6 +14,7 @@ import { idbLoadBlob, idbSaveBlob } from "./core/utils/idb-majik-system";
|
|
|
14
14
|
import { MajikMessageChat } from "./core/database/chat/majik-message-chat";
|
|
15
15
|
import { MajikCompressor } from "./core/compressor/majik-compressor";
|
|
16
16
|
export class MajikMessage {
|
|
17
|
+
userProfile = "default";
|
|
17
18
|
// Optional PIN protection (hashed). If set, UI should prompt for PIN to unlock.
|
|
18
19
|
pinHash = null;
|
|
19
20
|
id;
|
|
@@ -26,11 +27,14 @@ export class MajikMessage {
|
|
|
26
27
|
autosaveTimer = null;
|
|
27
28
|
autosaveIntervalMs = 15000; // periodic backup interval
|
|
28
29
|
autosaveDebounceMs = 500; // debounce for rapid changes
|
|
29
|
-
|
|
30
|
+
unlocked = false;
|
|
31
|
+
constructor(config, id, userProfile = "default") {
|
|
32
|
+
this.userProfile = userProfile || "default";
|
|
30
33
|
this.id = id || arrayToBase64(randomBytes(32));
|
|
31
34
|
this.contactDirectory =
|
|
32
35
|
config.contactDirectory || new MajikContactDirectory();
|
|
33
|
-
this.envelopeCache =
|
|
36
|
+
this.envelopeCache =
|
|
37
|
+
config.envelopeCache || new EnvelopeCache(undefined, userProfile);
|
|
34
38
|
// Initialize scanner
|
|
35
39
|
this.scanner = new ScannerEngine({
|
|
36
40
|
contactDirectory: this.contactDirectory,
|
|
@@ -142,6 +146,10 @@ export class MajikMessage {
|
|
|
142
146
|
if (!this.contactDirectory.hasContact(account.id)) {
|
|
143
147
|
this.contactDirectory.addContact(account);
|
|
144
148
|
}
|
|
149
|
+
if (!this.getActiveAccount()) {
|
|
150
|
+
this.setActiveAccount(account.id);
|
|
151
|
+
this.unlocked = true;
|
|
152
|
+
}
|
|
145
153
|
}
|
|
146
154
|
catch (e) {
|
|
147
155
|
// ignore if contact can't be added
|
|
@@ -356,44 +364,51 @@ export class MajikMessage {
|
|
|
356
364
|
* Will prompt to unlock identity if necessary.
|
|
357
365
|
*/
|
|
358
366
|
async decryptEnvelope(envelope, bypassIdentity = false) {
|
|
359
|
-
const fingerprint = envelope.extractFingerprint();
|
|
360
|
-
const authorizedAccount = this.listContacts(true).find((a) => a.fingerprint === fingerprint);
|
|
361
|
-
if (!authorizedAccount) {
|
|
362
|
-
throw new Error("No matching own account to decrypt this envelope");
|
|
363
|
-
}
|
|
364
|
-
let privateKey;
|
|
365
|
-
if (bypassIdentity) {
|
|
366
|
-
const activeAccount = this.getActiveAccount();
|
|
367
|
-
if (!activeAccount) {
|
|
368
|
-
throw new Error("No active account available to bypass identity check");
|
|
369
|
-
}
|
|
370
|
-
privateKey = await KeyStore.getPrivateKey(activeAccount.id);
|
|
371
|
-
}
|
|
372
|
-
else {
|
|
373
|
-
privateKey = await this.ensureIdentityUnlocked(authorizedAccount.id);
|
|
374
|
-
}
|
|
375
|
-
if (!privateKey) {
|
|
376
|
-
throw new Error("No private key found for this fingerprint.");
|
|
377
|
-
}
|
|
378
|
-
let decrypted;
|
|
379
367
|
if (envelope.isGroup()) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
368
|
+
// Group message - try all own accounts
|
|
369
|
+
const ownAccounts = this.listOwnAccounts();
|
|
370
|
+
if (ownAccounts.length === 0) {
|
|
371
|
+
throw new Error("No own accounts available to decrypt group message");
|
|
372
|
+
}
|
|
373
|
+
for (const ownAccount of ownAccounts) {
|
|
374
|
+
try {
|
|
375
|
+
const privateKey = await this.ensureIdentityUnlocked(ownAccount.id);
|
|
376
|
+
const decrypted = await EncryptionEngine.decryptGroupMessage(envelope.extractEncryptedPayload(), privateKey, ownAccount.fingerprint);
|
|
377
|
+
// Decompress if needed
|
|
378
|
+
let plaintext = decrypted;
|
|
379
|
+
if (decrypted.startsWith("mjkcmp:")) {
|
|
380
|
+
plaintext = (await MajikCompressor.decompress("plaintext", decrypted));
|
|
381
|
+
}
|
|
382
|
+
await this.envelopeCache.set(envelope, typeof window !== "undefined" && window.location
|
|
383
|
+
? window.location.hostname
|
|
384
|
+
: "extension");
|
|
385
|
+
return plaintext;
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
// This account can't decrypt, try next
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
383
391
|
}
|
|
384
|
-
|
|
392
|
+
throw new Error("None of your accounts can decrypt this group message");
|
|
385
393
|
}
|
|
386
394
|
else {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
395
|
+
// Solo message - original logic
|
|
396
|
+
const fingerprint = envelope.extractFingerprint();
|
|
397
|
+
const ownAccount = this.listOwnAccounts().find((a) => a.fingerprint === fingerprint);
|
|
398
|
+
if (!ownAccount) {
|
|
399
|
+
throw new Error("No matching account to decrypt this envelope");
|
|
400
|
+
}
|
|
401
|
+
const privateKey = await this.ensureIdentityUnlocked(ownAccount.id);
|
|
402
|
+
const decrypted = await EncryptionEngine.decryptSoloMessage(envelope.extractEncryptedPayload(), privateKey);
|
|
403
|
+
let plaintext = decrypted;
|
|
404
|
+
if (decrypted.startsWith("mjkcmp:")) {
|
|
405
|
+
plaintext = (await MajikCompressor.decompress("plaintext", decrypted));
|
|
406
|
+
}
|
|
407
|
+
await this.envelopeCache.set(envelope, typeof window !== "undefined" && window.location
|
|
408
|
+
? window.location.hostname
|
|
409
|
+
: "extension");
|
|
410
|
+
return plaintext;
|
|
392
411
|
}
|
|
393
|
-
await this.envelopeCache.set(envelope, typeof window !== "undefined" && window.location
|
|
394
|
-
? window.location.hostname
|
|
395
|
-
: "extension");
|
|
396
|
-
return plaintext;
|
|
397
412
|
}
|
|
398
413
|
async importContactFromString(base64Str) {
|
|
399
414
|
const jsonStr = base64ToUtf8(base64Str);
|
|
@@ -519,16 +534,20 @@ export class MajikMessage {
|
|
|
519
534
|
const payloadBytes = new TextEncoder().encode(JSON.stringify(payload));
|
|
520
535
|
// Envelope structure: [version byte][sender fingerprint][payload bytes]
|
|
521
536
|
const versionByte = new Uint8Array([2]);
|
|
522
|
-
// Use sender fingerprint for group envelope
|
|
523
|
-
const activeAccount = this.getActiveAccount();
|
|
524
|
-
if (!activeAccount)
|
|
525
|
-
|
|
526
|
-
|
|
537
|
+
// // Use sender fingerprint for group envelope
|
|
538
|
+
// const activeAccount = this.getActiveAccount();
|
|
539
|
+
// if (!activeAccount) throw new Error("No active account to send from");
|
|
540
|
+
// const fingerprintBytes = new Uint8Array(
|
|
541
|
+
// base64ToArrayBuffer(activeAccount.fingerprint),
|
|
542
|
+
// );
|
|
543
|
+
// ✅ Use a special marker instead of a specific fingerprint
|
|
544
|
+
// Option 1: All zeros to indicate "multi-recipient"
|
|
545
|
+
const markerBytes = new Uint8Array(32).fill(0);
|
|
527
546
|
// Combine all parts into a single Uint8Array
|
|
528
|
-
const blob = new Uint8Array(versionByte.length +
|
|
547
|
+
const blob = new Uint8Array(versionByte.length + markerBytes.length + payloadBytes.length);
|
|
529
548
|
blob.set(versionByte, 0);
|
|
530
|
-
blob.set(
|
|
531
|
-
blob.set(payloadBytes, versionByte.length +
|
|
549
|
+
blob.set(markerBytes, versionByte.length);
|
|
550
|
+
blob.set(payloadBytes, versionByte.length + markerBytes.length);
|
|
532
551
|
// Wrap as MessageEnvelope
|
|
533
552
|
const envelope = new MessageEnvelope(blob.buffer);
|
|
534
553
|
if (!!cache) {
|
|
@@ -755,38 +774,60 @@ export class MajikMessage {
|
|
|
755
774
|
if (cached)
|
|
756
775
|
return;
|
|
757
776
|
const fingerprint = envelope.extractFingerprint();
|
|
758
|
-
//
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
777
|
+
// Check if this is a group message (all zeros or special marker)
|
|
778
|
+
const isGroupMessage = envelope.isGroup();
|
|
779
|
+
if (isGroupMessage) {
|
|
780
|
+
// For group messages, try all own accounts until one works
|
|
781
|
+
const ownAccounts = this.listOwnAccounts();
|
|
782
|
+
if (ownAccounts.length === 0) {
|
|
783
|
+
this.emit("untrusted", envelope);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
let decrypted = null;
|
|
787
|
+
let successfulAccount = null;
|
|
788
|
+
for (const ownAccount of ownAccounts) {
|
|
789
|
+
try {
|
|
790
|
+
const privateKey = await this.ensureIdentityUnlocked(ownAccount.id);
|
|
791
|
+
// Try to decrypt with this account
|
|
792
|
+
decrypted = await EncryptionEngine.decryptGroupMessage(envelope.extractEncryptedPayload(), privateKey, ownAccount.fingerprint);
|
|
793
|
+
successfulAccount = ownAccount;
|
|
794
|
+
break; // Success! Stop trying other accounts
|
|
795
|
+
}
|
|
796
|
+
catch (err) {
|
|
797
|
+
// This account doesn't have access, try next one
|
|
798
|
+
continue;
|
|
775
799
|
}
|
|
776
|
-
decrypted = await EncryptionEngine.decryptGroupMessage(envelope.extractEncryptedPayload(), privateKey, fingerprint);
|
|
777
800
|
}
|
|
778
|
-
|
|
779
|
-
|
|
801
|
+
if (!decrypted || !successfulAccount) {
|
|
802
|
+
this.emit("untrusted", envelope);
|
|
803
|
+
return;
|
|
780
804
|
}
|
|
781
|
-
// Cache
|
|
805
|
+
// Cache and emit
|
|
782
806
|
await this.envelopeCache.set(envelope, typeof window !== "undefined" && window.location
|
|
783
807
|
? window.location.hostname
|
|
784
808
|
: "extension");
|
|
785
809
|
this.scheduleAutosave();
|
|
786
|
-
this.emit("message", decrypted, envelope,
|
|
810
|
+
this.emit("message", decrypted, envelope, successfulAccount);
|
|
787
811
|
}
|
|
788
|
-
|
|
789
|
-
|
|
812
|
+
else {
|
|
813
|
+
// Solo message - original logic
|
|
814
|
+
const ownAccount = this.listOwnAccounts().find((a) => a.fingerprint === fingerprint);
|
|
815
|
+
if (!ownAccount) {
|
|
816
|
+
this.emit("untrusted", envelope);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
try {
|
|
820
|
+
const privateKey = await this.ensureIdentityUnlocked(ownAccount.id);
|
|
821
|
+
const decrypted = await EncryptionEngine.decryptSoloMessage(envelope.extractEncryptedPayload(), privateKey);
|
|
822
|
+
await this.envelopeCache.set(envelope, typeof window !== "undefined" && window.location
|
|
823
|
+
? window.location.hostname
|
|
824
|
+
: "extension");
|
|
825
|
+
this.scheduleAutosave();
|
|
826
|
+
this.emit("message", decrypted, envelope, ownAccount);
|
|
827
|
+
}
|
|
828
|
+
catch (err) {
|
|
829
|
+
this.emit("error", err, { envelope });
|
|
830
|
+
}
|
|
790
831
|
}
|
|
791
832
|
}
|
|
792
833
|
/**
|
|
@@ -817,13 +858,19 @@ export class MajikMessage {
|
|
|
817
858
|
else if (typeof window !== "undefined" && window.prompt) {
|
|
818
859
|
passphrase = window.prompt("Enter passphrase to unlock identity:", "");
|
|
819
860
|
}
|
|
820
|
-
if (!passphrase)
|
|
861
|
+
if (!passphrase) {
|
|
862
|
+
this.unlocked = false;
|
|
821
863
|
throw new Error("Unlock cancelled");
|
|
864
|
+
}
|
|
822
865
|
// Attempt to unlock
|
|
823
866
|
await KeyStore.unlockIdentity(id, passphrase);
|
|
867
|
+
this.unlocked = true;
|
|
824
868
|
return await KeyStore.getPrivateKey(id);
|
|
825
869
|
}
|
|
826
870
|
}
|
|
871
|
+
isUnlocked() {
|
|
872
|
+
return this.unlocked;
|
|
873
|
+
}
|
|
827
874
|
async isPassphraseValid(passphrase, id) {
|
|
828
875
|
const target = id ? this.getOwnAccountById(id) : this.getActiveAccount();
|
|
829
876
|
if (!target)
|
|
@@ -1023,7 +1070,7 @@ export class MajikMessage {
|
|
|
1023
1070
|
try {
|
|
1024
1071
|
const jsonDocument = await this.toJSON();
|
|
1025
1072
|
const autosaveBlob = autoSaveMajikFileData(jsonDocument);
|
|
1026
|
-
await idbSaveBlob("majik-message-state", autosaveBlob);
|
|
1073
|
+
await idbSaveBlob("majik-message-state", autosaveBlob, this.userProfile);
|
|
1027
1074
|
}
|
|
1028
1075
|
catch (err) {
|
|
1029
1076
|
console.error("Failed to save MajikMessage state:", err);
|
|
@@ -1032,7 +1079,7 @@ export class MajikMessage {
|
|
|
1032
1079
|
/** Load state from IndexedDB and apply to this instance. */
|
|
1033
1080
|
async loadState() {
|
|
1034
1081
|
try {
|
|
1035
|
-
const autosaveData = await idbLoadBlob("majik-message-state");
|
|
1082
|
+
const autosaveData = await idbLoadBlob("majik-message-state", this.userProfile);
|
|
1036
1083
|
if (!autosaveData?.data)
|
|
1037
1084
|
return;
|
|
1038
1085
|
const blobFile = autosaveData.data;
|
|
@@ -1053,9 +1100,9 @@ export class MajikMessage {
|
|
|
1053
1100
|
/**
|
|
1054
1101
|
* Try to load an existing state from IDB; if none exists, create a fresh instance and save it.
|
|
1055
1102
|
*/
|
|
1056
|
-
static async loadOrCreate(config) {
|
|
1103
|
+
static async loadOrCreate(config, userProfile = "default") {
|
|
1057
1104
|
try {
|
|
1058
|
-
const saved = await idbLoadBlob("majik-message-state");
|
|
1105
|
+
const saved = await idbLoadBlob("majik-message-state", userProfile);
|
|
1059
1106
|
if (saved?.data) {
|
|
1060
1107
|
const loaded = await loadSavedMajikFileData(saved.data);
|
|
1061
1108
|
const parsedJSON = loaded.j;
|
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.18",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Zelijah",
|
|
8
8
|
"main": "./dist/index.js",
|