@majikah/majik-message 0.1.20 → 0.2.0

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.
@@ -1,9 +1,10 @@
1
1
  import { ISODateString, MajikMessageAccountID, MajikMessageMailID, MajikMessagePublicKey, MajikMessageThreadID } from "../../../types";
2
2
  import { MajikMessageIdentity } from "../../system/identity";
3
3
  import { MajikMessageThread } from "../majik-message-thread";
4
+ import { FileContext, MajikFile } from "@majikah/majik-file";
4
5
  export interface MailMetadata {
5
6
  subject?: string;
6
- attachments?: string[];
7
+ attachments?: MailAttachmentRef[];
7
8
  priority?: "low" | "medium" | "high" | "urgent";
8
9
  labels?: string[];
9
10
  isForwarded?: boolean;
@@ -23,6 +24,22 @@ export interface MajikMessageMailJSON {
23
24
  previous_mail_id?: MajikMessageMailID;
24
25
  read_by: MajikMessagePublicKey[];
25
26
  }
27
+ export interface MailAttachmentRef {
28
+ /** MajikFile UUID — used to fetch the .mjkb from R2 via your file service */
29
+ fileId: string;
30
+ /** SHA-256 hex of original bytes — for dedup checks */
31
+ fileHash: string;
32
+ /** Original filename for display (e.g. "resume.pdf") */
33
+ originalName: string | null;
34
+ /** MIME type for icon/preview logic */
35
+ mimeType: string | null;
36
+ /** Original size in bytes — for "2.3 MB" display */
37
+ sizeOriginal: number;
38
+ /** R2 key — lets the Worker fetch directly without a DB lookup */
39
+ r2Key: string;
40
+ /** context from MajikFile — so the UI knows if it's a thread_attachment */
41
+ context: FileContext;
42
+ }
26
43
  export declare class MajikMailError extends Error {
27
44
  code: string;
28
45
  constructor(message: string, code: string);
@@ -73,10 +90,11 @@ export declare class MajikMessageMail {
73
90
  * @param message - Plain text message (encrypted)
74
91
  * @param recipients - Array of recipient public keys (excluding sender)
75
92
  * @param metadata - Optional mail metadata
93
+ * @param id - Optional ID to use
76
94
  * @returns Promise resolving to new MajikMessageMail instance
77
95
  * @throws Error if validation fails or thread is closed
78
96
  */
79
- static create(thread: MajikMessageThread, identity: MajikMessageIdentity, message: string, recipients: MajikMessagePublicKey[], metadata?: MailMetadata): Promise<MajikMessageMail>;
97
+ static create(thread: MajikMessageThread, identity: MajikMessageIdentity, message: string, recipients: MajikMessagePublicKey[], metadata?: MailMetadata, id?: MajikMessageMailID): Promise<MajikMessageMail>;
80
98
  /**
81
99
  * Creates a reply to an existing mail item in the thread.
82
100
  * Uses the previous mail's hash as part of the p_hash.
@@ -87,10 +105,30 @@ export declare class MajikMessageMail {
87
105
  * @param message - Plain text message (encrypted)
88
106
  * @param recipients - Array of recipient public keys (excluding sender)
89
107
  * @param metadata - Optional mail metadata
108
+ * @param id - Optional ID to use
90
109
  * @returns Promise resolving to new MajikMessageMail instance
91
110
  * @throws Error if validation fails or thread is closed
92
111
  */
93
- static reply(thread: MajikMessageThread, previousMail: MajikMessageMail, identity: MajikMessageIdentity, message: string, recipients: MajikMessagePublicKey[], metadata?: MailMetadata): Promise<MajikMessageMail>;
112
+ static reply(thread: MajikMessageThread, previousMail: MajikMessageMail, identity: MajikMessageIdentity, message: string, recipients: MajikMessagePublicKey[], metadata?: MailMetadata, id?: MajikMessageMailID): Promise<MajikMessageMail>;
113
+ /**
114
+ * Attach a MajikFile to this mail.
115
+ * Automatically wires thread_id and thread_message_id from the instance.
116
+ *
117
+ * @throws MailValidationError if the file context is not "thread_attachment"
118
+ * @throws MailOperationError if this file is already attached (by fileId or fileHash)
119
+ */
120
+ attachFile(file: MajikFile): MailAttachmentRef;
121
+ /**
122
+ * Remove an attached file by fileId.
123
+ * Returns true if removed, false if it wasn't attached.
124
+ */
125
+ detachFile(fileId: string): boolean;
126
+ /** Returns all attachment refs on this mail, typed correctly. */
127
+ get attachments(): MailAttachmentRef[];
128
+ /** Returns true if this mail has at least one attachment. */
129
+ get hasAttachments(): boolean;
130
+ /** Returns the attachment ref for a given fileId, or null if not found. */
131
+ getAttachment(fileId: string): MailAttachmentRef | null;
94
132
  /**
95
133
  * Generates the hash for the current mail item.
96
134
  * Format: SHA256(id:message:sender:recipients:timestamp)
@@ -114,7 +152,6 @@ export declare class MajikMessageMail {
114
152
  * @returns true if p_hash is valid
115
153
  */
116
154
  validatePHash(previousHash: string): boolean;
117
- private validateMessage;
118
155
  /**
119
156
  * Validates an entire chain of mail items in a thread.
120
157
  * Verifies both hash and p_hash integrity for all items.
@@ -180,5 +217,6 @@ export declare class MajikMessageMail {
180
217
  * Validates raw message length
181
218
  */
182
219
  private static validateRawMessageLength;
220
+ private static isValidAttachmentRef;
183
221
  private static isValidJSON;
184
222
  }
@@ -113,10 +113,11 @@ export class MajikMessageMail {
113
113
  * @param message - Plain text message (encrypted)
114
114
  * @param recipients - Array of recipient public keys (excluding sender)
115
115
  * @param metadata - Optional mail metadata
116
+ * @param id - Optional ID to use
116
117
  * @returns Promise resolving to new MajikMessageMail instance
117
118
  * @throws Error if validation fails or thread is closed
118
119
  */
119
- static async create(thread, identity, message, recipients, metadata = {}) {
120
+ static async create(thread, identity, message, recipients, metadata = {}, id) {
120
121
  try {
121
122
  // Validate thread
122
123
  if (!thread) {
@@ -175,10 +176,11 @@ export class MajikMessageMail {
175
176
  // Normalize recipients (sort for consistency)
176
177
  const normalizedRecipients = [...recipients].sort();
177
178
  // Generate ID and timestamp
178
- const id = uuidv4();
179
+ const generatedID = uuidv4();
180
+ const finalID = id || generatedID;
179
181
  const timestamp = new Date();
180
182
  // Generate hash for this mail item
181
- const hash = this.generateHash(id, message.trim(), senderPublicKey, normalizedRecipients, timestamp);
183
+ const hash = this.generateHash(finalID, message.trim(), senderPublicKey, normalizedRecipients, timestamp);
182
184
  // For the first item, p_hash is the thread's hash
183
185
  const p_hash = this.generatePHash(hash, thread.hash);
184
186
  // Mark as reply metadata
@@ -186,7 +188,7 @@ export class MajikMessageMail {
186
188
  ...metadata,
187
189
  isReply: false,
188
190
  };
189
- return new MajikMessageMail(id, thread.id, accountID, message.trim(), senderPublicKey, normalizedRecipients, timestamp, finalMetadata, hash, p_hash, undefined, // No previous mail ID for first item
191
+ return new MajikMessageMail(finalID, thread.id, accountID, message.trim(), senderPublicKey, normalizedRecipients, timestamp, finalMetadata, hash, p_hash, undefined, // No previous mail ID for first item
190
192
  []);
191
193
  }
192
194
  catch (error) {
@@ -207,10 +209,11 @@ export class MajikMessageMail {
207
209
  * @param message - Plain text message (encrypted)
208
210
  * @param recipients - Array of recipient public keys (excluding sender)
209
211
  * @param metadata - Optional mail metadata
212
+ * @param id - Optional ID to use
210
213
  * @returns Promise resolving to new MajikMessageMail instance
211
214
  * @throws Error if validation fails or thread is closed
212
215
  */
213
- static async reply(thread, previousMail, identity, message, recipients, metadata = {}) {
216
+ static async reply(thread, previousMail, identity, message, recipients, metadata = {}, id) {
214
217
  try {
215
218
  // Validate thread
216
219
  if (!thread) {
@@ -279,10 +282,11 @@ export class MajikMessageMail {
279
282
  // Normalize recipients (sort for consistency)
280
283
  const normalizedRecipients = [...recipients].sort();
281
284
  // Generate ID and timestamp
282
- const id = uuidv4();
285
+ const generatedID = uuidv4();
286
+ const finalID = id || generatedID;
283
287
  const timestamp = new Date();
284
288
  // Generate hash for this mail item
285
- const hash = this.generateHash(id, message.trim(), senderPublicKey, normalizedRecipients, timestamp);
289
+ const hash = this.generateHash(finalID, message.trim(), senderPublicKey, normalizedRecipients, timestamp);
286
290
  // For replies, p_hash links to previous mail's hash
287
291
  const p_hash = this.generatePHash(hash, previousMail.hash);
288
292
  // Mark as reply in metadata
@@ -290,7 +294,7 @@ export class MajikMessageMail {
290
294
  ...metadata,
291
295
  isReply: true,
292
296
  };
293
- return new MajikMessageMail(id, thread.id, accountID, message.trim(), senderPublicKey, normalizedRecipients, timestamp, finalMetadata, hash, p_hash, previousMail.id, []);
297
+ return new MajikMessageMail(finalID, thread.id, accountID, message.trim(), senderPublicKey, normalizedRecipients, timestamp, finalMetadata, hash, p_hash, previousMail.id, []);
294
298
  }
295
299
  catch (error) {
296
300
  if (error instanceof MajikMailError) {
@@ -299,6 +303,78 @@ export class MajikMessageMail {
299
303
  throw new MailOperationError(`Failed to create reply: ${error instanceof Error ? error.message : "Unknown error"}`);
300
304
  }
301
305
  }
306
+ // In MajikMessageMail
307
+ /**
308
+ * Attach a MajikFile to this mail.
309
+ * Automatically wires thread_id and thread_message_id from the instance.
310
+ *
311
+ * @throws MailValidationError if the file context is not "thread_attachment"
312
+ * @throws MailOperationError if this file is already attached (by fileId or fileHash)
313
+ */
314
+ attachFile(file) {
315
+ // Enforce context — only thread attachments belong on mail
316
+ if (file.context !== "thread_attachment") {
317
+ throw new MailValidationError(`attachFile: file must have context "thread_attachment" (got "${file.context}")`);
318
+ }
319
+ const existing = (this._metadata.attachments ?? []);
320
+ // Guard: duplicate by fileId
321
+ if (existing.some((a) => a.fileId === file.id)) {
322
+ throw new MailOperationError(`attachFile: file "${file.id}" is already attached to this mail`);
323
+ }
324
+ // Guard: duplicate by content hash (same file re-encrypted)
325
+ if (existing.some((a) => a.fileHash === file.fileHash)) {
326
+ throw new MailOperationError(`attachFile: a file with hash "${file.fileHash.slice(0, 8)}…" is already attached`);
327
+ }
328
+ // Wire thread context onto the file if not already bound.
329
+ // bindToThreadMail is a no-op guard — it throws if already set,
330
+ // so we only call it when both IDs are missing.
331
+ if (!file.threadId && !file.threadMessageId) {
332
+ file.bindToThreadMail(this._threadID, this._id);
333
+ }
334
+ else if (file.threadId !== this._threadID ||
335
+ file.threadMessageId !== this._id) {
336
+ // File was pre-bound to a DIFFERENT mail/thread — that's a real error
337
+ throw new MailOperationError(`attachFile: file "${file.id}" is already bound to a different thread/mail`);
338
+ }
339
+ const ref = {
340
+ fileId: file.id,
341
+ fileHash: file.fileHash,
342
+ originalName: file.originalName,
343
+ mimeType: file.mimeType,
344
+ sizeOriginal: file.sizeOriginal,
345
+ r2Key: file.r2Key,
346
+ context: file.context,
347
+ };
348
+ this._metadata = {
349
+ ...this._metadata,
350
+ attachments: [...existing, ref],
351
+ };
352
+ return ref;
353
+ }
354
+ /**
355
+ * Remove an attached file by fileId.
356
+ * Returns true if removed, false if it wasn't attached.
357
+ */
358
+ detachFile(fileId) {
359
+ const existing = (this._metadata.attachments ?? []);
360
+ const filtered = existing.filter((a) => a.fileId !== fileId);
361
+ if (filtered.length === existing.length)
362
+ return false;
363
+ this._metadata = { ...this._metadata, attachments: filtered };
364
+ return true;
365
+ }
366
+ /** Returns all attachment refs on this mail, typed correctly. */
367
+ get attachments() {
368
+ return [...(this._metadata.attachments ?? [])];
369
+ }
370
+ /** Returns true if this mail has at least one attachment. */
371
+ get hasAttachments() {
372
+ return ((this._metadata.attachments ?? []).length > 0);
373
+ }
374
+ /** Returns the attachment ref for a given fileId, or null if not found. */
375
+ getAttachment(fileId) {
376
+ return ((this._metadata.attachments ?? []).find((a) => a.fileId === fileId) ?? null);
377
+ }
302
378
  // ==================== Hash Generation Methods ====================
303
379
  /**
304
380
  * Generates the hash for the current mail item.
@@ -426,11 +502,6 @@ export class MajikMessageMail {
426
502
  throw new MailValidationError(`p_hash validation failed: ${error instanceof Error ? error.message : "Unknown error"}`);
427
503
  }
428
504
  }
429
- validateMessage(message) {
430
- if (!message || typeof message !== "string" || message.trim() === "") {
431
- throw new Error("Invalid message: must be a non-empty string");
432
- }
433
- }
434
505
  // ==================== Static Blockchain Validation ====================
435
506
  /**
436
507
  * Validates an entire chain of mail items in a thread.
@@ -691,6 +762,16 @@ export class MajikMessageMail {
691
762
  if (!this.isValidJSON(data)) {
692
763
  throw new MailValidationError("Invalid JSON: missing required fields or invalid types");
693
764
  }
765
+ if (data.metadata?.attachments !== undefined) {
766
+ if (!Array.isArray(data.metadata.attachments)) {
767
+ throw new MailValidationError("Invalid JSON: metadata.attachments must be an array");
768
+ }
769
+ for (let i = 0; i < data.metadata.attachments.length; i++) {
770
+ if (!MajikMessageMail.isValidAttachmentRef(data.metadata.attachments[i])) {
771
+ throw new MailValidationError(`Invalid JSON: metadata.attachments[${i}] is not a valid MailAttachmentRef`);
772
+ }
773
+ }
774
+ }
694
775
  // Parse timestamp
695
776
  const timestamp = new Date(data.timestamp);
696
777
  if (isNaN(timestamp.getTime())) {
@@ -725,6 +806,17 @@ export class MajikMessageMail {
725
806
  `Current length: ${message.length}`);
726
807
  }
727
808
  }
809
+ static isValidAttachmentRef(a) {
810
+ return (a &&
811
+ typeof a === "object" &&
812
+ typeof a.fileId === "string" &&
813
+ typeof a.fileHash === "string" &&
814
+ typeof a.r2Key === "string" &&
815
+ typeof a.sizeOriginal === "number" &&
816
+ (a.originalName === null || typeof a.originalName === "string") &&
817
+ (a.mimeType === null || typeof a.mimeType === "string") &&
818
+ typeof a.context === "string");
819
+ }
728
820
  static isValidJSON(json) {
729
821
  return (json &&
730
822
  typeof json === "object" &&
@@ -1,4 +1,4 @@
1
- import type { FileContext, MajikFile, MajikFileJSON } from "@majikah/majik-file";
1
+ import type { FileContext, MajikFile, MajikFileJSON, TempFileDuration } from "@majikah/majik-file";
2
2
  export type ISODateString = string;
3
3
  export type MajikMessageAccountID = string;
4
4
  export type MajikMessagePublicKey = string;
@@ -95,6 +95,8 @@ export interface MajikKeyMetadata {
95
95
  export interface EncryptFileOptions {
96
96
  /** Raw binary content of the file to encrypt. */
97
97
  data: Uint8Array | ArrayBuffer;
98
+ /** UUID from auth.users — used for R2 key construction and ownership checks. */
99
+ userId?: string;
98
100
  /**
99
101
  * File context — drives storage routing, WebP conversion, and R2 key prefix.
100
102
  * "user_upload" → permanent storage, no WebP conversion
@@ -125,14 +127,16 @@ export interface EncryptFileOptions {
125
127
  * @default false
126
128
  */
127
129
  isTemporary?: boolean;
128
- /** ISO-8601 expiry timestamp. Required when isTemporary is true. */
129
- expiresAt?: string;
130
+ /** TempFileDuration in days. Required when isTemporary is true. */
131
+ expiresAt?: TempFileDuration;
130
132
  /** Bypass the 100 MB file size limit. @default false */
131
133
  bypassSizeLimit?: boolean;
132
134
  /** Foreign-key association with a chat message. */
133
135
  chatMessageId?: string;
134
136
  /** Foreign-key association with a thread message. */
135
137
  threadMessageId?: string;
138
+ /** Foreign-key association with a thread. */
139
+ threadId?: string;
136
140
  }
137
141
  /**
138
142
  * Returned by MajikMessage.encryptFile().
@@ -66,15 +66,6 @@ export declare class MajikMessage {
66
66
  * @param accountId Own account ID. Defaults to the active account.
67
67
  */
68
68
  private _resolveFileIdentity;
69
- /**
70
- * Resolve a list of contact IDs into MajikFileRecipient objects.
71
- *
72
- * Used for group file encryption — each recipient only needs their ML-KEM
73
- * public key. Secret keys never leave their respective devices.
74
- *
75
- * @param ids Contact IDs from the contact directory.
76
- */
77
- private _resolveFileRecipients;
78
69
  /**
79
70
  * Resolve a list of contact IDs into MajikFileRecipient objects.
80
71
  *
@@ -170,25 +161,7 @@ export declare class MajikMessage {
170
161
  decryptMajikMessageChat(encryptedPayload: string, recipientId?: string): Promise<string>;
171
162
  /**
172
163
  * Encrypt a binary file and return everything the caller needs to persist it.
173
- *
174
- * Flow:
175
- * 1. Resolve the active account's full MajikFileIdentity from MajikKeyStore.
176
- * 2. Resolve each recipientId into a MajikFileRecipient (public key only).
177
- * MajikFile.create() silently deduplicates and strips the sender's own ID
178
- * from the recipient list, so callers don't have to filter it out.
179
- * 3. Delegate entirely to MajikFile.create() — it handles:
180
- * • SHA-256 content hash (dedup)
181
- * • WebP conversion for chat_image / chat_attachment image contexts
182
- * • Zstd compression for compressible formats
183
- * • ML-KEM encapsulation (single or group)
184
- * • AES-256-GCM encryption
185
- * • .mjkb binary encoding
186
- * 4. Return the MajikFile instance, Supabase-ready metadata, and R2-ready Blob.
187
- *
188
- * The caller is responsible for:
189
- * • Uploading `result.binary` to R2 at `result.metadata.r2_key`
190
- * • Inserting `result.metadata` into the majik_files Supabase table
191
- *
164
+
192
165
  * @throws Error if no active account, account has no ML-KEM keys, or a
193
166
  * recipient cannot be resolved from the contact directory.
194
167
  * @throws MajikFileError on validation failures or crypto errors (re-thrown
@@ -139,36 +139,12 @@ export class MajikMessage {
139
139
  `Re-import via importAccountFromMnemonicBackup() to upgrade.`);
140
140
  }
141
141
  return {
142
- userId: key.id,
142
+ publicKey: key.publicKeyBase64,
143
143
  fingerprint: key.fingerprint,
144
144
  mlKemPublicKey: key.mlKemPublicKey,
145
145
  mlKemSecretKey: key.getMlKemSecretKey(),
146
146
  };
147
147
  }
148
- /**
149
- * Resolve a list of contact IDs into MajikFileRecipient objects.
150
- *
151
- * Used for group file encryption — each recipient only needs their ML-KEM
152
- * public key. Secret keys never leave their respective devices.
153
- *
154
- * @param ids Contact IDs from the contact directory.
155
- */
156
- async _resolveFileRecipients(ids) {
157
- return Promise.all(ids.map(async (id) => {
158
- const contact = this.contactDirectory.getContact(id);
159
- if (!contact)
160
- throw new Error(`No contact found for id "${id}"`);
161
- const mlPubKey = base64ToUint8Array(contact.mlKey);
162
- if (!mlPubKey || mlPubKey.length === 0) {
163
- throw new Error(`Contact "${id}" has no ML-KEM public key. ` +
164
- `They may need to upgrade their account via importFromMnemonicBackup().`);
165
- }
166
- return {
167
- fingerprint: contact.fingerprint,
168
- mlKemPublicKey: mlPubKey,
169
- };
170
- }));
171
- }
172
148
  /**
173
149
  * Resolve a list of contact IDs into MajikFileRecipient objects.
174
150
  *
@@ -190,6 +166,7 @@ export class MajikMessage {
190
166
  return {
191
167
  fingerprint: contact.fingerprint,
192
168
  mlKemPublicKey: mlPubKey,
169
+ publicKey: pkey,
193
170
  };
194
171
  }));
195
172
  }
@@ -651,25 +628,7 @@ export class MajikMessage {
651
628
  // ── File Encryption / Decryption ──────────────────────────────────────────
652
629
  /**
653
630
  * Encrypt a binary file and return everything the caller needs to persist it.
654
- *
655
- * Flow:
656
- * 1. Resolve the active account's full MajikFileIdentity from MajikKeyStore.
657
- * 2. Resolve each recipientId into a MajikFileRecipient (public key only).
658
- * MajikFile.create() silently deduplicates and strips the sender's own ID
659
- * from the recipient list, so callers don't have to filter it out.
660
- * 3. Delegate entirely to MajikFile.create() — it handles:
661
- * • SHA-256 content hash (dedup)
662
- * • WebP conversion for chat_image / chat_attachment image contexts
663
- * • Zstd compression for compressible formats
664
- * • ML-KEM encapsulation (single or group)
665
- * • AES-256-GCM encryption
666
- * • .mjkb binary encoding
667
- * 4. Return the MajikFile instance, Supabase-ready metadata, and R2-ready Blob.
668
- *
669
- * The caller is responsible for:
670
- * • Uploading `result.binary` to R2 at `result.metadata.r2_key`
671
- * • Inserting `result.metadata` into the majik_files Supabase table
672
- *
631
+
673
632
  * @throws Error if no active account, account has no ML-KEM keys, or a
674
633
  * recipient cannot be resolved from the contact directory.
675
634
  * @throws MajikFileError on validation failures or crypto errors (re-thrown
@@ -700,10 +659,11 @@ export class MajikMessage {
700
659
  * ```
701
660
  */
702
661
  async encryptFile(options) {
703
- const { data, context, originalName, mimeType, recipients = [], conversationId, isTemporary = false, expiresAt, bypassSizeLimit = false, chatMessageId, threadMessageId, } = options;
662
+ const { data, context, originalName, mimeType, recipients = [], conversationId, isTemporary = false, expiresAt, bypassSizeLimit = false, chatMessageId, threadMessageId, threadId, userId, } = options;
704
663
  // ── 1. Resolve sender identity ──────────────────────────────────────────
705
664
  // Builds MajikFileIdentity with both public + secret keys from keystore.
706
665
  const identity = await this._resolveFileIdentity();
666
+ const finalUserID = userId ?? identity.publicKey;
707
667
  // ── 2. Resolve additional recipients ───────────────────────────────────
708
668
  // MajikFile.create() will silently drop the sender's own fingerprint if
709
669
  // it appears in this list, and will deduplicate any repeated entries.
@@ -725,6 +685,8 @@ export class MajikMessage {
725
685
  bypassSizeLimit,
726
686
  chatMessageId,
727
687
  threadMessageId,
688
+ userId: finalUserID,
689
+ threadId: threadId,
728
690
  });
729
691
  // ── 4. Package the result ───────────────────────────────────────────────
730
692
  return {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@majikah/majik-message",
3
3
  "type": "module",
4
4
  "description": "Post-quantum end-to-end encryption with ML-KEM-768. Seed phrase–based accounts. Auto-expiring messages. Offline-ready. Exportable encrypted messages. Tamper-proof threads with blockchain-like integrity. Quantum-resistant messaging.",
5
- "version": "0.1.20",
5
+ "version": "0.2.0",
6
6
  "license": "Apache-2.0",
7
7
  "author": "Zelijah",
8
8
  "main": "./dist/index.js",
@@ -81,8 +81,8 @@
81
81
  "dependencies": {
82
82
  "@bokuweb/zstd-wasm": "^0.0.27",
83
83
  "@majikah/majik-envelope": "^0.0.1",
84
- "@majikah/majik-file": "^0.0.6",
85
- "@majikah/majik-key": "^0.1.8",
84
+ "@majikah/majik-file": "^0.0.13",
85
+ "@majikah/majik-key": "^0.1.9",
86
86
  "@noble/hashes": "^2.0.1",
87
87
  "@noble/post-quantum": "^0.5.4",
88
88
  "@scure/bip39": "^1.6.0",