@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.
- package/dist/core/compressor/majik-compressor.d.ts +3 -2
- package/dist/core/compressor/majik-compressor.js +24 -10
- package/dist/core/contacts/majik-contact.d.ts +13 -0
- package/dist/core/contacts/majik-contact.js +55 -15
- package/dist/core/database/chat/majik-message-chat.js +1 -1
- package/dist/core/database/system/identity.js +2 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +2 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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:
|
|
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.
|
|
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
|
},
|