@thezelijah/majik-message 1.0.0 → 1.0.1
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/README.md +4 -0
- package/dist/core/compressor/majik-compressor.d.ts +17 -0
- package/dist/core/compressor/majik-compressor.js +86 -0
- package/dist/core/crypto/keystore.js +12 -0
- package/dist/core/database/chat/majik-message-chat.d.ts +94 -0
- package/dist/core/database/chat/majik-message-chat.js +432 -0
- package/dist/core/database/chat/types.d.ts +11 -0
- package/dist/core/database/chat/types.js +1 -0
- package/dist/core/database/system/identity.d.ts +61 -0
- package/dist/core/database/system/identity.js +170 -0
- package/dist/core/database/system/majik-user/enums.d.ts +44 -0
- package/dist/core/database/system/majik-user/enums.js +40 -0
- package/dist/core/database/system/majik-user/majik-user.d.ts +257 -0
- package/dist/core/database/system/majik-user/majik-user.js +812 -0
- package/dist/core/database/system/majik-user/types.d.ts +186 -0
- package/dist/core/database/system/majik-user/types.js +1 -0
- package/dist/core/database/system/majik-user/utils.d.ts +32 -0
- package/dist/core/database/system/majik-user/utils.js +110 -0
- package/dist/core/database/system/utils.d.ts +1 -0
- package/dist/core/database/system/utils.js +8 -0
- package/dist/core/types.d.ts +3 -0
- package/dist/core/utils/idb-majik-system.d.ts +5 -5
- package/dist/core/utils/utilities.d.ts +7 -0
- package/dist/core/utils/utilities.js +14 -0
- package/dist/majik-message.d.ts +18 -0
- package/dist/majik-message.js +101 -0
- package/package.json +2 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { UserGenderOptions } from './enums';
|
|
2
|
+
export type ISODateString = string;
|
|
3
|
+
/**
|
|
4
|
+
* ISO Date string in YYYY-MM-DD format
|
|
5
|
+
*/
|
|
6
|
+
export type YYYYMMDD = `${number}-${number}-${number}`;
|
|
7
|
+
export type CountryCode = string;
|
|
8
|
+
export type LanguageCode = string;
|
|
9
|
+
export interface Address {
|
|
10
|
+
/** Country name */
|
|
11
|
+
country?: string;
|
|
12
|
+
/** City name */
|
|
13
|
+
city?: string;
|
|
14
|
+
/** State or province name */
|
|
15
|
+
region?: string;
|
|
16
|
+
/** Barangay or local area */
|
|
17
|
+
area?: string;
|
|
18
|
+
/** Street address */
|
|
19
|
+
street?: string;
|
|
20
|
+
/** Optional building or suite information */
|
|
21
|
+
building?: string;
|
|
22
|
+
/** Optional postal/zip code */
|
|
23
|
+
zip?: string;
|
|
24
|
+
/** ISO 3166-1 alpha-2 country code (e.g., 'US', 'PH') */
|
|
25
|
+
country_code?: CountryCode;
|
|
26
|
+
/** Geographic coordinates */
|
|
27
|
+
coordinates?: {
|
|
28
|
+
/** Latitude in decimal degrees (-90 to 90) */
|
|
29
|
+
latitude: number;
|
|
30
|
+
/** Longitude in decimal degrees (-180 to 180) */
|
|
31
|
+
longitude: number;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Stores the user's personal and contact details, including demographics and device information.
|
|
36
|
+
*/
|
|
37
|
+
export interface UserBasicInformation {
|
|
38
|
+
/** Raw name of the customer */
|
|
39
|
+
name?: FullName;
|
|
40
|
+
/** Profile photo URL or path for the User */
|
|
41
|
+
picture?: string;
|
|
42
|
+
/** User's phone number for contact or verification */
|
|
43
|
+
phone?: string;
|
|
44
|
+
/** User's gender (e.g., 'male', 'female', 'non-binary', etc.) */
|
|
45
|
+
gender?: UserGenderOptions;
|
|
46
|
+
/** User's birthdate in ISO 8601 format (YYYY-MM-DD) */
|
|
47
|
+
birthdate?: YYYYMMDD;
|
|
48
|
+
/** Default address information */
|
|
49
|
+
address?: Address;
|
|
50
|
+
/** User's preferred pronouns */
|
|
51
|
+
pronouns?: string;
|
|
52
|
+
/** Short bio or description */
|
|
53
|
+
bio?: string;
|
|
54
|
+
/** Preferred language code (ISO 639-1, e.g., 'en', 'es') */
|
|
55
|
+
language?: string;
|
|
56
|
+
/** Timezone identifier (e.g., 'Asia/Manila', 'America/New_York') */
|
|
57
|
+
timezone?: string;
|
|
58
|
+
/** Verification status flags */
|
|
59
|
+
verification?: {
|
|
60
|
+
email_verified: boolean;
|
|
61
|
+
phone_verified: boolean;
|
|
62
|
+
identity_verified: boolean;
|
|
63
|
+
};
|
|
64
|
+
/** Social media or external profile links */
|
|
65
|
+
social_links?: Record<string, string>;
|
|
66
|
+
/** Company/organization affiliation */
|
|
67
|
+
company?: CompanyInformation;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Represents a person's full legal or professional name.
|
|
71
|
+
*/
|
|
72
|
+
export interface FullName {
|
|
73
|
+
/** First or given name of the person */
|
|
74
|
+
first_name: string;
|
|
75
|
+
/** Last or family name of the person */
|
|
76
|
+
last_name: string;
|
|
77
|
+
/** Optional middle name or initial */
|
|
78
|
+
middle_name?: string;
|
|
79
|
+
/** Optional suffix (e.g., Jr., Sr., III) */
|
|
80
|
+
suffix?: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Basic contact details for a person or organization.
|
|
84
|
+
*/
|
|
85
|
+
export interface ContactInformation {
|
|
86
|
+
/** Email address, if available */
|
|
87
|
+
email?: string;
|
|
88
|
+
/** Phone number, if available */
|
|
89
|
+
phone?: string;
|
|
90
|
+
/** Website URL, if applicable */
|
|
91
|
+
website?: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Information about the company associated with a client.
|
|
95
|
+
*/
|
|
96
|
+
export interface CompanyInformation {
|
|
97
|
+
/** Name of the company or organization */
|
|
98
|
+
name: string;
|
|
99
|
+
/** The role or position of the client within the company (e.g., CEO, Manager) */
|
|
100
|
+
role: string;
|
|
101
|
+
/** Contact details for the company or representative */
|
|
102
|
+
contact: ContactInformation;
|
|
103
|
+
/** Optional physical or mailing address of the company */
|
|
104
|
+
address?: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* JSON representation of a MajikUser for serialization/deserialization
|
|
108
|
+
*/
|
|
109
|
+
export interface MajikUserJSON<TMetadata extends UserBasicInformation = UserBasicInformation> {
|
|
110
|
+
id: string;
|
|
111
|
+
email: string;
|
|
112
|
+
displayName: string;
|
|
113
|
+
hash: string;
|
|
114
|
+
metadata: TMetadata;
|
|
115
|
+
settings: UserSettings;
|
|
116
|
+
createdAt: string;
|
|
117
|
+
lastUpdate: string;
|
|
118
|
+
}
|
|
119
|
+
export interface UserSettings {
|
|
120
|
+
notifications: boolean;
|
|
121
|
+
system: {
|
|
122
|
+
isRestricted: boolean;
|
|
123
|
+
restrictedUntil?: Date;
|
|
124
|
+
};
|
|
125
|
+
[key: string]: unknown;
|
|
126
|
+
}
|
|
127
|
+
interface SupabaseUserAppMetadata {
|
|
128
|
+
provider?: string;
|
|
129
|
+
[key: string]: any;
|
|
130
|
+
}
|
|
131
|
+
interface SupabaseUserMetadata {
|
|
132
|
+
[key: string]: any;
|
|
133
|
+
}
|
|
134
|
+
interface Factor {
|
|
135
|
+
/** ID of the factor. */
|
|
136
|
+
id: string;
|
|
137
|
+
/** Friendly name of the factor, useful to disambiguate between multiple factors. */
|
|
138
|
+
friendly_name?: string;
|
|
139
|
+
/**
|
|
140
|
+
* Type of factor. `totp` and `phone` supported with this version
|
|
141
|
+
*/
|
|
142
|
+
factor_type: 'totp' | 'phone' | (string & {});
|
|
143
|
+
/** Factor's status. */
|
|
144
|
+
status: 'verified' | 'unverified';
|
|
145
|
+
created_at: string;
|
|
146
|
+
updated_at: string;
|
|
147
|
+
}
|
|
148
|
+
interface SupabaseUserIdentity {
|
|
149
|
+
id: string;
|
|
150
|
+
user_id: string;
|
|
151
|
+
identity_data?: {
|
|
152
|
+
[key: string]: any;
|
|
153
|
+
};
|
|
154
|
+
identity_id: string;
|
|
155
|
+
provider: string;
|
|
156
|
+
created_at?: string;
|
|
157
|
+
last_sign_in_at?: string;
|
|
158
|
+
updated_at?: string;
|
|
159
|
+
}
|
|
160
|
+
export interface SupabaseUser {
|
|
161
|
+
id: string;
|
|
162
|
+
app_metadata: SupabaseUserAppMetadata;
|
|
163
|
+
user_metadata: SupabaseUserMetadata;
|
|
164
|
+
aud: string;
|
|
165
|
+
confirmation_sent_at?: string;
|
|
166
|
+
recovery_sent_at?: string;
|
|
167
|
+
email_change_sent_at?: string;
|
|
168
|
+
new_email?: string;
|
|
169
|
+
new_phone?: string;
|
|
170
|
+
invited_at?: string;
|
|
171
|
+
action_link?: string;
|
|
172
|
+
email?: string;
|
|
173
|
+
phone?: string;
|
|
174
|
+
created_at: string;
|
|
175
|
+
confirmed_at?: string;
|
|
176
|
+
email_confirmed_at?: string;
|
|
177
|
+
phone_confirmed_at?: string;
|
|
178
|
+
last_sign_in_at?: string;
|
|
179
|
+
role?: string;
|
|
180
|
+
updated_at?: string;
|
|
181
|
+
identities?: SupabaseUserIdentity[];
|
|
182
|
+
is_anonymous?: boolean;
|
|
183
|
+
is_sso_user?: boolean;
|
|
184
|
+
factors?: Factor[];
|
|
185
|
+
}
|
|
186
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { YYYYMMDD } from './types';
|
|
2
|
+
export declare function arrayToBase64(data: Uint8Array): string;
|
|
3
|
+
export declare function base64ToUint8Array(base64: string): Uint8Array;
|
|
4
|
+
export declare function arrayBufferToBase64(buffer: ArrayBuffer): string;
|
|
5
|
+
export declare function base64ToArrayBuffer(base64: string): ArrayBuffer;
|
|
6
|
+
export declare function base64ToUtf8(base64: string): string;
|
|
7
|
+
export declare function utf8ToBase64(str: string): string;
|
|
8
|
+
export declare function concatArrayBuffers(a: ArrayBuffer, b: ArrayBuffer): ArrayBuffer;
|
|
9
|
+
export declare function concatUint8Arrays(a: Uint8Array, b: Uint8Array): Uint8Array;
|
|
10
|
+
export interface MnemonicJSON {
|
|
11
|
+
seed: string[];
|
|
12
|
+
id: string;
|
|
13
|
+
phrase?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Converts a space-separated seed phrase string into MnemonicJSON
|
|
17
|
+
*/
|
|
18
|
+
export declare function seedToJSON(seed: string, id: string, phrase?: string): MnemonicJSON;
|
|
19
|
+
/**
|
|
20
|
+
* Converts MnemonicJSON into a single space-separated string
|
|
21
|
+
*/
|
|
22
|
+
export declare function jsonToSeed(json: MnemonicJSON): string;
|
|
23
|
+
export declare function seedStringToArray(seed: string): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Convert Date → YYYY-MM-DD (UTC-safe)
|
|
26
|
+
*/
|
|
27
|
+
export declare function dateToYYYYMMDD(date: Date): YYYYMMDD;
|
|
28
|
+
/**
|
|
29
|
+
* Convert YYYY-MM-DD → Date (UTC midnight)
|
|
30
|
+
*/
|
|
31
|
+
export declare function YYYYMMDDToDate(yyyymmdd: YYYYMMDD): Date;
|
|
32
|
+
export declare function stripUndefined<T extends Record<string, unknown>>(obj: T): Partial<T>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* ================================
|
|
2
|
+
* Utilities
|
|
3
|
+
* ================================ */
|
|
4
|
+
// utils/utilities.ts
|
|
5
|
+
export function arrayToBase64(data) {
|
|
6
|
+
let binary = '';
|
|
7
|
+
const bytes = data;
|
|
8
|
+
const len = bytes.byteLength;
|
|
9
|
+
for (let i = 0; i < len; i++) {
|
|
10
|
+
binary += String.fromCharCode(bytes[i]);
|
|
11
|
+
}
|
|
12
|
+
return btoa(binary);
|
|
13
|
+
}
|
|
14
|
+
export function base64ToUint8Array(base64) {
|
|
15
|
+
return new Uint8Array(base64ToArrayBuffer(base64));
|
|
16
|
+
}
|
|
17
|
+
export function arrayBufferToBase64(buffer) {
|
|
18
|
+
const bytes = new Uint8Array(buffer);
|
|
19
|
+
let binary = '';
|
|
20
|
+
const chunkSize = 0x8000;
|
|
21
|
+
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
22
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
|
|
23
|
+
}
|
|
24
|
+
return btoa(binary);
|
|
25
|
+
}
|
|
26
|
+
export function base64ToArrayBuffer(base64) {
|
|
27
|
+
const binary = atob(base64);
|
|
28
|
+
const bytes = new Uint8Array(binary.length);
|
|
29
|
+
for (let i = 0; i < binary.length; i++) {
|
|
30
|
+
bytes[i] = binary.charCodeAt(i);
|
|
31
|
+
}
|
|
32
|
+
return bytes.buffer;
|
|
33
|
+
}
|
|
34
|
+
export function base64ToUtf8(base64) {
|
|
35
|
+
const buf = base64ToArrayBuffer(base64);
|
|
36
|
+
return new TextDecoder().decode(new Uint8Array(buf));
|
|
37
|
+
}
|
|
38
|
+
export function utf8ToBase64(str) {
|
|
39
|
+
const bytes = new TextEncoder().encode(str);
|
|
40
|
+
return arrayToBase64(bytes);
|
|
41
|
+
}
|
|
42
|
+
export function concatArrayBuffers(a, b) {
|
|
43
|
+
const tmp = new Uint8Array(a.byteLength + b.byteLength);
|
|
44
|
+
tmp.set(new Uint8Array(a), 0);
|
|
45
|
+
tmp.set(new Uint8Array(b), a.byteLength);
|
|
46
|
+
return tmp.buffer;
|
|
47
|
+
}
|
|
48
|
+
export function concatUint8Arrays(a, b) {
|
|
49
|
+
const out = new Uint8Array(a.byteLength + b.byteLength);
|
|
50
|
+
out.set(a, 0);
|
|
51
|
+
out.set(b, a.byteLength);
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Converts a space-separated seed phrase string into MnemonicJSON
|
|
56
|
+
*/
|
|
57
|
+
export function seedToJSON(seed, id, phrase) {
|
|
58
|
+
return {
|
|
59
|
+
seed: seed
|
|
60
|
+
.trim()
|
|
61
|
+
.split(/\s+/)
|
|
62
|
+
.map((w) => w.toLowerCase())
|
|
63
|
+
.filter(Boolean),
|
|
64
|
+
id,
|
|
65
|
+
phrase,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Converts MnemonicJSON into a single space-separated string
|
|
70
|
+
*/
|
|
71
|
+
export function jsonToSeed(json) {
|
|
72
|
+
return json.seed.join(' ');
|
|
73
|
+
}
|
|
74
|
+
export function seedStringToArray(seed) {
|
|
75
|
+
return seed
|
|
76
|
+
.trim()
|
|
77
|
+
.split(/\s+/)
|
|
78
|
+
.map((w) => w.toLowerCase())
|
|
79
|
+
.filter(Boolean);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Convert Date → YYYY-MM-DD (UTC-safe)
|
|
83
|
+
*/
|
|
84
|
+
export function dateToYYYYMMDD(date) {
|
|
85
|
+
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
|
|
86
|
+
throw new Error('Invalid Date object');
|
|
87
|
+
}
|
|
88
|
+
const y = date.getFullYear();
|
|
89
|
+
const m = String(date.getMonth() + 1).padStart(2, '0');
|
|
90
|
+
const d = String(date.getDate()).padStart(2, '0');
|
|
91
|
+
return `${y}-${m}-${d}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Convert YYYY-MM-DD → Date (UTC midnight)
|
|
95
|
+
*/
|
|
96
|
+
export function YYYYMMDDToDate(yyyymmdd) {
|
|
97
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(yyyymmdd)) {
|
|
98
|
+
throw new Error('Invalid ISO date format. Expected YYYY-MM-DD');
|
|
99
|
+
}
|
|
100
|
+
const [year, month, day] = yyyymmdd.split('-').map(Number);
|
|
101
|
+
const date = new Date(year, month - 1, day); // ✅ LOCAL midnight
|
|
102
|
+
// Validation
|
|
103
|
+
if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
|
|
104
|
+
throw new Error('Invalid calendar date');
|
|
105
|
+
}
|
|
106
|
+
return date;
|
|
107
|
+
}
|
|
108
|
+
export function stripUndefined(obj) {
|
|
109
|
+
return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined));
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function autogenerateID(): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { customAlphabet } from "nanoid";
|
|
2
|
+
export function autogenerateID() {
|
|
3
|
+
// Create the generator function ONCE with your custom alphabet and length
|
|
4
|
+
const generateID = customAlphabet("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 16);
|
|
5
|
+
// Call the generator function to produce the actual ID string
|
|
6
|
+
const genUID = generateID(); // Example output: 'G7K2aZp9'
|
|
7
|
+
return genUID;
|
|
8
|
+
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { type IDBPDatabase } from "idb";
|
|
2
|
-
export interface
|
|
2
|
+
export interface MajikIDBSaveData {
|
|
3
3
|
id: string;
|
|
4
4
|
data: Blob;
|
|
5
5
|
savedAt: number;
|
|
6
6
|
}
|
|
7
|
-
interface
|
|
8
|
-
majikdata:
|
|
7
|
+
interface MajikAutosaveSchema {
|
|
8
|
+
majikdata: MajikIDBSaveData;
|
|
9
9
|
}
|
|
10
|
-
export declare function initDB(): Promise<IDBPDatabase<
|
|
10
|
+
export declare function initDB(): Promise<IDBPDatabase<MajikAutosaveSchema>>;
|
|
11
11
|
export declare function idbSaveBlob(id: string, data: Blob): Promise<void>;
|
|
12
|
-
export declare function idbLoadBlob(id: string): Promise<
|
|
12
|
+
export declare function idbLoadBlob(id: 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 {};
|
|
@@ -20,3 +20,10 @@ export declare function seedToJSON(seed: string, id: string, phrase?: string): M
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function jsonToSeed(json: MnemonicJSON): string;
|
|
22
22
|
export declare function seedStringToArray(seed: string): string[];
|
|
23
|
+
/**
|
|
24
|
+
* Generates a unique, URL-safe ID for an item based on its name and current timestamp.
|
|
25
|
+
*
|
|
26
|
+
* @param prefix - The prefix string name to add.
|
|
27
|
+
* @returns A unique ID string prefixed.
|
|
28
|
+
*/
|
|
29
|
+
export declare function autogenerateID(prefix?: string): string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* ================================
|
|
2
2
|
* Utilities
|
|
3
3
|
* ================================ */
|
|
4
|
+
import { customAlphabet } from "nanoid";
|
|
4
5
|
// utils/utilities.ts
|
|
5
6
|
export function arrayToBase64(data) {
|
|
6
7
|
let binary = "";
|
|
@@ -78,3 +79,16 @@ export function seedStringToArray(seed) {
|
|
|
78
79
|
.map((w) => w.toLowerCase())
|
|
79
80
|
.filter(Boolean);
|
|
80
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Generates a unique, URL-safe ID for an item based on its name and current timestamp.
|
|
84
|
+
*
|
|
85
|
+
* @param prefix - The prefix string name to add.
|
|
86
|
+
* @returns A unique ID string prefixed.
|
|
87
|
+
*/
|
|
88
|
+
export function autogenerateID(prefix = "mjkmsg") {
|
|
89
|
+
// Create the generator function ONCE with your custom alphabet and length
|
|
90
|
+
const generateID = customAlphabet("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 8);
|
|
91
|
+
// Call the generator function to produce the actual ID string
|
|
92
|
+
const genUID = generateID(); // Example output: 'G7K2aZp9'
|
|
93
|
+
return `${prefix}-${genUID}`;
|
|
94
|
+
}
|
package/dist/majik-message.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { EnvelopeCache, type EnvelopeCacheItem, type EnvelopeCacheJSON } from ".
|
|
|
4
4
|
import { KeyStore } from "./core/crypto/keystore";
|
|
5
5
|
import { MajikContactDirectory, type MajikContactDirectoryData } from "./core/contacts/majik-contact-directory";
|
|
6
6
|
import type { MAJIK_API_RESPONSE } from "./core/types";
|
|
7
|
+
import { MajikMessageChat } from "./core/database/chat/majik-message-chat";
|
|
8
|
+
import { MajikMessageIdentity } from "./core/database/system/identity";
|
|
7
9
|
type MajikMessageEvents = "message" | "envelope" | "untrusted" | "error";
|
|
8
10
|
export interface MajikMessageConfig {
|
|
9
11
|
keyStore: KeyStore;
|
|
@@ -142,6 +144,22 @@ export declare class MajikMessage {
|
|
|
142
144
|
*/
|
|
143
145
|
encryptGroupMessage(recipientIds: string[], plaintext: string, cache?: boolean): Promise<MessageEnvelope>;
|
|
144
146
|
sendMessage(recipients: string[], plaintext: string): Promise<MessageEnvelope>;
|
|
147
|
+
/**
|
|
148
|
+
* Create a new MajikMessageChat with compression, then encrypt it.
|
|
149
|
+
* Returns the scanner-ready string containing the encrypted compressed message.
|
|
150
|
+
*
|
|
151
|
+
* Flow: Plaintext → Compress (MajikMessageChat) → Encrypt (EncryptionEngine) → Scanner String
|
|
152
|
+
*/
|
|
153
|
+
createEncryptedMajikMessageChat(account: MajikMessageIdentity, recipients: string[], plaintext: string, expiresInMs?: number): Promise<{
|
|
154
|
+
messageChat: MajikMessageChat;
|
|
155
|
+
scannerString: string;
|
|
156
|
+
}>;
|
|
157
|
+
/**
|
|
158
|
+
* Decrypt and decompress a MajikMessageChat message.
|
|
159
|
+
*
|
|
160
|
+
* Flow: Encrypted Payload → Decrypt (EncryptionEngine) → Decompress (MajikCompressor) → Plaintext
|
|
161
|
+
*/
|
|
162
|
+
decryptMajikMessageChat(encryptedPayload: string, recipientId?: string): Promise<string>;
|
|
145
163
|
/**
|
|
146
164
|
* Encrypts currently selected text in the browser DOM for given recipients.
|
|
147
165
|
* If `recipients` is empty, defaults to the first own account.
|
package/dist/majik-message.js
CHANGED
|
@@ -11,6 +11,8 @@ import { arrayBufferToBase64, arrayToBase64, base64ToArrayBuffer, base64ToUtf8,
|
|
|
11
11
|
import { autoSaveMajikFileData, loadSavedMajikFileData, } from "./core/utils/majik-file-utils";
|
|
12
12
|
import { randomBytes } from "@stablelib/random";
|
|
13
13
|
import { idbLoadBlob, idbSaveBlob } from "./core/utils/idb-majik-system";
|
|
14
|
+
import { MajikMessageChat } from "./core/database/chat/majik-message-chat";
|
|
15
|
+
import { MajikCompressor } from "./core/compressor/majik-compressor";
|
|
14
16
|
export class MajikMessage {
|
|
15
17
|
// Optional PIN protection (hashed). If set, UI should prompt for PIN to unlock.
|
|
16
18
|
pinHash = null;
|
|
@@ -517,6 +519,105 @@ export class MajikMessage {
|
|
|
517
519
|
/* ================================
|
|
518
520
|
* High-Level DOM Wrapper
|
|
519
521
|
* ================================ */
|
|
522
|
+
/**
|
|
523
|
+
* Create a new MajikMessageChat with compression, then encrypt it.
|
|
524
|
+
* Returns the scanner-ready string containing the encrypted compressed message.
|
|
525
|
+
*
|
|
526
|
+
* Flow: Plaintext → Compress (MajikMessageChat) → Encrypt (EncryptionEngine) → Scanner String
|
|
527
|
+
*/
|
|
528
|
+
async createEncryptedMajikMessageChat(account, recipients, plaintext, expiresInMs) {
|
|
529
|
+
if (!plaintext?.trim()) {
|
|
530
|
+
throw new Error("No text provided to encrypt.");
|
|
531
|
+
}
|
|
532
|
+
if (!recipients || recipients.length === 0) {
|
|
533
|
+
const firstOwn = this.listOwnAccounts()[0];
|
|
534
|
+
if (!firstOwn) {
|
|
535
|
+
throw new Error("No own account available for encryption.");
|
|
536
|
+
}
|
|
537
|
+
recipients = [firstOwn.id];
|
|
538
|
+
}
|
|
539
|
+
if (!account) {
|
|
540
|
+
throw new Error("No active account available to send message");
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
// Step 1: Create MajikMessageChat (compresses plaintext)
|
|
544
|
+
const messageChat = await MajikMessageChat.create(account, plaintext, recipients, expiresInMs);
|
|
545
|
+
// Step 2: Get compressed message for encryption
|
|
546
|
+
const compressedMessage = messageChat.getCompressedMessage();
|
|
547
|
+
// Step 3: Encrypt the compressed message using EncryptionEngine
|
|
548
|
+
let encryptedPayload;
|
|
549
|
+
if (recipients.length === 1) {
|
|
550
|
+
// Solo encryption
|
|
551
|
+
const contact = this.contactDirectory.getContact(recipients[0]);
|
|
552
|
+
if (!contact) {
|
|
553
|
+
throw new Error(`No contact found for recipient: ${recipients[0]}`);
|
|
554
|
+
}
|
|
555
|
+
encryptedPayload = await EncryptionEngine.encryptSoloMessage(compressedMessage, contact.publicKey);
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
// Group encryption
|
|
559
|
+
const recipientData = recipients.map((id) => {
|
|
560
|
+
const contact = this.contactDirectory.getContact(id);
|
|
561
|
+
if (!contact) {
|
|
562
|
+
throw new Error(`No contact found for recipient: ${id}`);
|
|
563
|
+
}
|
|
564
|
+
return {
|
|
565
|
+
id: contact.id,
|
|
566
|
+
publicKey: contact.publicKey,
|
|
567
|
+
};
|
|
568
|
+
});
|
|
569
|
+
encryptedPayload = await EncryptionEngine.encryptGroupMessage(compressedMessage, recipientData);
|
|
570
|
+
}
|
|
571
|
+
// Step 4: Convert encrypted payload to base64 string
|
|
572
|
+
const payloadJSON = JSON.stringify(encryptedPayload);
|
|
573
|
+
const payloadBase64 = utf8ToBase64(payloadJSON);
|
|
574
|
+
// Step 5: Create scanner string with MAJIK prefix
|
|
575
|
+
const scannerString = `${MessageEnvelope.PREFIX}:${payloadBase64}`;
|
|
576
|
+
// Step 6: Update the messageChat with encrypted payload for storage
|
|
577
|
+
messageChat.setMessage(payloadJSON); // Store encrypted version
|
|
578
|
+
return { messageChat, scannerString };
|
|
579
|
+
}
|
|
580
|
+
catch (err) {
|
|
581
|
+
this.emit("error", err, { context: "createEncryptedMajikMessageChat" });
|
|
582
|
+
throw err;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Decrypt and decompress a MajikMessageChat message.
|
|
587
|
+
*
|
|
588
|
+
* Flow: Encrypted Payload → Decrypt (EncryptionEngine) → Decompress (MajikCompressor) → Plaintext
|
|
589
|
+
*/
|
|
590
|
+
async decryptMajikMessageChat(encryptedPayload, recipientId) {
|
|
591
|
+
const recipient = recipientId
|
|
592
|
+
? this.getOwnAccountById(recipientId)
|
|
593
|
+
: this.getActiveAccount();
|
|
594
|
+
if (!recipient) {
|
|
595
|
+
throw new Error("No recipient account found for decryption");
|
|
596
|
+
}
|
|
597
|
+
try {
|
|
598
|
+
// Step 1: Ensure identity is unlocked
|
|
599
|
+
const privateKey = await this.ensureIdentityUnlocked(recipient.id);
|
|
600
|
+
// Step 2: Parse the encrypted payload
|
|
601
|
+
const payload = JSON.parse(encryptedPayload);
|
|
602
|
+
// Step 3: Decrypt using EncryptionEngine
|
|
603
|
+
let decryptedCompressed;
|
|
604
|
+
if (payload.keys) {
|
|
605
|
+
// Group message
|
|
606
|
+
decryptedCompressed = await EncryptionEngine.decryptGroupMessage(payload, privateKey, recipient.fingerprint);
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
// Solo message
|
|
610
|
+
decryptedCompressed = await EncryptionEngine.decryptSoloMessage(payload, privateKey);
|
|
611
|
+
}
|
|
612
|
+
// Step 4: Decompress the message
|
|
613
|
+
const plaintext = await MajikCompressor.decompressString(decryptedCompressed);
|
|
614
|
+
return plaintext;
|
|
615
|
+
}
|
|
616
|
+
catch (err) {
|
|
617
|
+
this.emit("error", err, { context: "decryptMajikMessageChat" });
|
|
618
|
+
throw err;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
520
621
|
/**
|
|
521
622
|
* Encrypts currently selected text in the browser DOM for given recipients.
|
|
522
623
|
* If `recipients` is empty, defaults to the first own account.
|
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.1",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Zelijah",
|
|
8
8
|
"main": "./dist/index.js",
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"prepublishOnly": "npm run build"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
+
"@bokuweb/zstd-wasm": "^0.0.27",
|
|
78
79
|
"@scure/bip39": "^1.6.0",
|
|
79
80
|
"@stablelib/aes": "^2.0.1",
|
|
80
81
|
"@stablelib/ed25519": "^2.0.2",
|