@thezelijah/majik-message 1.0.4 → 1.0.6

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,13 +1,14 @@
1
1
  type SupportedInput = string | object | ArrayBuffer | Uint8Array;
2
2
  export type MajikCompressorType = "str" | "json" | "blob";
3
+ export type CompressionMode = "binary" | "plaintext";
3
4
  export declare class MajikCompressor {
4
5
  private static PREFIX;
5
6
  private static initialized;
6
7
  private static ensureInit;
7
8
  private static encodeInput;
8
9
  private static decodeOutput;
9
- static compress(input: SupportedInput, level?: number): Promise<string>;
10
- static decompress(compressedStr: string): Promise<string | Record<string, any> | Uint8Array>;
10
+ static compress(mode: CompressionMode, input: SupportedInput, level?: number): Promise<string>;
11
+ static decompress(mode: CompressionMode, compressedStr: string): Promise<string | Record<string, any> | Uint8Array>;
11
12
  static decompressJSON(compressedStr: string): Promise<Record<string, any>>;
12
13
  static decompressString(compressedStr: string): Promise<string>;
13
14
  static decompressBlob(compressedStr: string): Promise<Uint8Array>;
@@ -1,10 +1,11 @@
1
1
  import { init, compress as zstdCompress, decompress as zstdDecompress, } from "@bokuweb/zstd-wasm";
2
+ import { gzipSync, gunzipSync } from "fflate";
2
3
  export class MajikCompressor {
3
4
  static PREFIX = "mjkcmp";
4
5
  static initialized = false;
5
6
  static async ensureInit() {
6
7
  if (!this.initialized) {
7
- await init();
8
+ await init(); // only init Zstd for binary mode
8
9
  this.initialized = true;
9
10
  }
10
11
  }
@@ -32,37 +33,50 @@ export class MajikCompressor {
32
33
  throw new Error(`Unsupported type for decoding: ${type}`);
33
34
  }
34
35
  // --- Compress input and return string ---
35
- static async compress(input, level = 9) {
36
- await this.ensureInit();
36
+ static async compress(mode, input, level = 9) {
37
37
  const { type, data } = this.encodeInput(input);
38
- const compressed = zstdCompress(data, level); // synchronous
38
+ let compressed;
39
+ if (mode === "binary") {
40
+ await this.ensureInit();
41
+ compressed = zstdCompress(data, level);
42
+ }
43
+ else {
44
+ // plaintext mode → fflate gzip
45
+ compressed = gzipSync(data);
46
+ }
39
47
  const b64 = this.uint8ArrayToBase64(compressed);
40
48
  return `${this.PREFIX}:${type}:${b64}`;
41
49
  }
42
50
  // --- Decompress string with prefix ---
43
- static async decompress(compressedStr) {
44
- await this.ensureInit();
51
+ static async decompress(mode, compressedStr) {
45
52
  if (!compressedStr.startsWith(`${this.PREFIX}:`))
46
53
  throw new Error("Invalid MajikCompressor string format");
47
54
  const [, type, b64] = compressedStr.split(":", 3);
48
55
  const compressedData = this.base64ToUint8Array(b64);
49
- const decompressed = zstdDecompress(compressedData); // synchronous
56
+ let decompressed;
57
+ if (mode === "binary") {
58
+ await this.ensureInit();
59
+ decompressed = zstdDecompress(compressedData);
60
+ }
61
+ else {
62
+ decompressed = gunzipSync(compressedData);
63
+ }
50
64
  return this.decodeOutput(type, decompressed);
51
65
  }
52
66
  static async decompressJSON(compressedStr) {
53
- const result = await this.decompress(compressedStr);
67
+ const result = await this.decompress("binary", compressedStr);
54
68
  if (typeof result === "object" && !(result instanceof Uint8Array))
55
69
  return result;
56
70
  throw new Error("Decompressed data is not JSON");
57
71
  }
58
72
  static async decompressString(compressedStr) {
59
- const result = await this.decompress(compressedStr);
73
+ const result = await this.decompress("binary", compressedStr);
60
74
  if (typeof result === "string")
61
75
  return result;
62
76
  throw new Error("Decompressed data is not a string");
63
77
  }
64
78
  static async decompressBlob(compressedStr) {
65
- const result = await this.decompress(compressedStr);
79
+ const result = await this.decompress("binary", compressedStr);
66
80
  if (result instanceof Uint8Array)
67
81
  return result;
68
82
  throw new Error("Decompressed data is not a blob");
@@ -1,3 +1,4 @@
1
+ import { MajikMessageIdentityJSON } from "../database/system/identity";
1
2
  import { ISODateString } from "../types";
2
3
  export type SerializedMajikContact = Omit<MajikContactData, "publicKey"> & {
3
4
  publicKeyBase64: string;
@@ -48,6 +49,18 @@ export declare class MajikContact {
48
49
  setBlocked(blocked: boolean): this;
49
50
  block(): this;
50
51
  unblock(): this;
52
+ /**
53
+ * Support both CryptoKey and raw-key wrappers (fallbacks when WebCrypto X25519 unsupported)
54
+ */
55
+ getPublicKeyBase64(): Promise<string>;
51
56
  toJSON(): Promise<SerializedMajikContact>;
57
+ /**
58
+ * Reconstruct a MajikContact from its serialized form
59
+ */
60
+ static fromJSON(serialized: SerializedMajikContact): MajikContact;
61
+ /**
62
+ * Create a new MajikContact from a MajikMessageIdentityJSON
63
+ */
64
+ static fromIdentityJSON(identityJSON: MajikMessageIdentityJSON): Promise<MajikContact>;
52
65
  static isBlocked(contact: MajikContact): boolean;
53
66
  }
@@ -1,4 +1,4 @@
1
- import { arrayBufferToBase64 } from "../utils/utilities";
1
+ import { arrayBufferToBase64, base64ToArrayBuffer } from "../utils/utilities";
2
2
  /* -------------------------------
3
3
  * Errors
4
4
  * ------------------------------- */
@@ -103,32 +103,72 @@ export class MajikContact {
103
103
  this.setBlocked(false);
104
104
  return this;
105
105
  }
106
- async toJSON() {
107
- // Support both CryptoKey and raw-key wrappers (fallbacks when WebCrypto X25519 unsupported)
106
+ /**
107
+ * Support both CryptoKey and raw-key wrappers (fallbacks when WebCrypto X25519 unsupported)
108
+ */
109
+ async getPublicKeyBase64() {
108
110
  try {
109
111
  // If it's a CryptoKey, export with SubtleCrypto
110
112
  const raw = await crypto.subtle.exportKey("raw", this.publicKey);
111
- return {
112
- id: this.id,
113
- fingerprint: this.fingerprint,
114
- meta: { ...this.meta },
115
- publicKeyBase64: arrayBufferToBase64(raw),
116
- };
113
+ return arrayBufferToBase64(raw);
117
114
  }
118
115
  catch (e) {
119
116
  // Fallback: publicKey may be a wrapper with `raw` Uint8Array
120
117
  const maybe = this.publicKey;
121
118
  if (maybe && maybe.raw instanceof Uint8Array) {
122
- return {
123
- id: this.id,
124
- fingerprint: this.fingerprint,
125
- meta: { ...this.meta },
126
- publicKeyBase64: arrayBufferToBase64(maybe.raw.buffer),
127
- };
119
+ return arrayBufferToBase64(maybe.raw.buffer);
128
120
  }
129
121
  throw e;
130
122
  }
131
123
  }
124
+ async toJSON() {
125
+ return {
126
+ id: this.id,
127
+ fingerprint: this.fingerprint,
128
+ meta: { ...this.meta },
129
+ publicKeyBase64: await this.getPublicKeyBase64(),
130
+ };
131
+ }
132
+ /**
133
+ * Reconstruct a MajikContact from its serialized form
134
+ */
135
+ static fromJSON(serialized) {
136
+ try {
137
+ const publicKeyRaw = new Uint8Array(base64ToArrayBuffer(serialized.publicKeyBase64));
138
+ return new MajikContact({
139
+ id: serialized.id,
140
+ fingerprint: serialized.fingerprint,
141
+ meta: serialized.meta,
142
+ publicKey: { raw: publicKeyRaw },
143
+ });
144
+ }
145
+ catch (err) {
146
+ throw new MajikContactError("Failed to deserialize MajikContact", err);
147
+ }
148
+ }
149
+ /**
150
+ * Create a new MajikContact from a MajikMessageIdentityJSON
151
+ */
152
+ static async fromIdentityJSON(identityJSON) {
153
+ try {
154
+ const publicKeyRaw = new Uint8Array(base64ToArrayBuffer(identityJSON.public_key));
155
+ const contactData = {
156
+ id: identityJSON.id,
157
+ publicKey: { raw: publicKeyRaw },
158
+ fingerprint: identityJSON.id,
159
+ meta: {
160
+ label: identityJSON.label,
161
+ createdAt: identityJSON.timestamp,
162
+ updatedAt: identityJSON.timestamp,
163
+ blocked: identityJSON.restricted,
164
+ },
165
+ };
166
+ return new MajikContact(contactData);
167
+ }
168
+ catch (err) {
169
+ throw new MajikContactError("Failed to create MajikContact from MajikMessageIdentityJSON", err);
170
+ }
171
+ }
132
172
  static isBlocked(contact) {
133
173
  return !!contact.meta.blocked;
134
174
  }
@@ -142,7 +142,7 @@ export class MajikMessageChat {
142
142
  // Compress the message before storing
143
143
  let compressedMessage;
144
144
  try {
145
- compressedMessage = await MajikCompressor.compress(message.trim());
145
+ compressedMessage = await MajikCompressor.compress("plaintext", message.trim());
146
146
  }
147
147
  catch (error) {
148
148
  throw new Error(`Failed to compress message: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -70,10 +70,11 @@ export class MajikMessageIdentity {
70
70
  const generatedID = autogenerateID();
71
71
  const timestamp = new Date().toISOString();
72
72
  const phash = sha256(`${user.id}:${account.id}:${generatedID}`);
73
+ const publicKey = account.publicKeyBase64;
73
74
  return new MajikMessageIdentity({
74
75
  id: generatedID,
75
76
  userId: user.id,
76
- publicKey: account.id,
77
+ publicKey: publicKey,
77
78
  phash,
78
79
  label,
79
80
  timestamp,
package/dist/index.d.ts CHANGED
@@ -13,3 +13,5 @@ export * from "./core/utils/APITranscoder";
13
13
  export * from "./core/utils/utilities";
14
14
  export * from "./core/database/chat/majik-message-chat";
15
15
  export type * from "./core/database/chat/types";
16
+ export * from "./core/database/system/identity";
17
+ export * from "./core/compressor/majik-compressor";
package/dist/index.js CHANGED
@@ -11,3 +11,5 @@ export * from "./core/scanner/scanner-engine";
11
11
  export * from "./core/utils/APITranscoder";
12
12
  export * from "./core/utils/utilities";
13
13
  export * from "./core/database/chat/majik-message-chat";
14
+ export * from "./core/database/system/identity";
15
+ export * from "./core/compressor/majik-compressor";
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.4",
5
+ "version": "1.0.6",
6
6
  "license": "Apache-2.0",
7
7
  "author": "Zelijah",
8
8
  "main": "./dist/index.js",
@@ -86,6 +86,7 @@
86
86
  "@stablelib/x25519": "^2.0.1",
87
87
  "ed2curve": "^0.3.0",
88
88
  "fernet": "^0.3.3",
89
+ "fflate": "^0.8.2",
89
90
  "idb": "^8.0.3",
90
91
  "nanoid": "^5.1.6"
91
92
  },