@thezelijah/majik-message 1.0.0 → 1.0.2

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,61 @@
1
+ import { MajikUser } from "./majik-user/majik-user";
2
+ import { SerializedMajikContact } from "../../contacts/majik-contact";
3
+ export interface MajikMessageIdentityJSON {
4
+ id: string;
5
+ user_id: string;
6
+ public_key: string;
7
+ phash: string;
8
+ label: string;
9
+ timestamp: string;
10
+ restricted: boolean;
11
+ }
12
+ /**
13
+ * MajikMessageIdentity
14
+ * Immutable identity container with integrity verification
15
+ */
16
+ export declare class MajikMessageIdentity {
17
+ private readonly _id;
18
+ private readonly _userId;
19
+ private readonly _publicKey;
20
+ private readonly _phash;
21
+ private _label;
22
+ private readonly _timestamp;
23
+ private readonly _restricted;
24
+ /**
25
+ * Constructor is private to enforce controlled creation
26
+ */
27
+ private constructor();
28
+ /**
29
+ * Create a new immutable identity from MajikUser
30
+ */
31
+ static create(user: MajikUser, account: SerializedMajikContact, options?: {
32
+ label?: string;
33
+ restricted?: boolean;
34
+ }): MajikMessageIdentity;
35
+ get id(): string;
36
+ get userID(): string;
37
+ get publicKey(): string;
38
+ get phash(): string;
39
+ get label(): string;
40
+ get timestamp(): string;
41
+ get restricted(): boolean;
42
+ /**
43
+ * Only mutable field
44
+ */
45
+ set label(label: string);
46
+ /**
47
+ * Returns true if identity is restricted
48
+ */
49
+ isRestricted(): boolean;
50
+ /**
51
+ * Verify identity integrity
52
+ * Detects tampering of id/public_key
53
+ */
54
+ validateIntegrity(): boolean;
55
+ /**
56
+ * Explicit verification helper
57
+ */
58
+ matches(userId: string, publicKey: string): boolean;
59
+ toJSON(): MajikMessageIdentityJSON;
60
+ static fromJSON(json: string | MajikMessageIdentityJSON): MajikMessageIdentity;
61
+ }
@@ -0,0 +1,170 @@
1
+ import crypto from "crypto";
2
+ import { autogenerateID } from "./utils";
3
+ /**
4
+ * Utility assertions
5
+ */
6
+ function assert(condition, message) {
7
+ if (!condition)
8
+ throw new Error(message);
9
+ }
10
+ function assertString(value, field) {
11
+ assert(typeof value === "string" && value.trim().length > 0, `${field} must be a non-empty string`);
12
+ }
13
+ function assertISODate(value, field) {
14
+ const date = new Date(value);
15
+ assert(!isNaN(date.getTime()), `${field} must be a valid ISO timestamp`);
16
+ }
17
+ function sha256(input) {
18
+ return crypto.createHash("sha256").update(input).digest("hex");
19
+ }
20
+ /**
21
+ * MajikMessageIdentity
22
+ * Immutable identity container with integrity verification
23
+ */
24
+ export class MajikMessageIdentity {
25
+ // 🔒 Private backing fields
26
+ _id;
27
+ _userId;
28
+ _publicKey;
29
+ _phash;
30
+ _label;
31
+ _timestamp;
32
+ _restricted;
33
+ /**
34
+ * Constructor is private to enforce controlled creation
35
+ */
36
+ constructor(params) {
37
+ assertString(params.id, "id");
38
+ assertString(params.userId, "user_id");
39
+ assertString(params.publicKey, "public_key");
40
+ assertString(params.phash, "phash");
41
+ assertString(params.label, "label");
42
+ assertISODate(params.timestamp, "timestamp");
43
+ assert(typeof params.restricted === "boolean", "restricted must be boolean");
44
+ this._id = params.id;
45
+ this._userId = params.userId;
46
+ this._publicKey = params.publicKey;
47
+ this._phash = params.phash;
48
+ this._label = params.label;
49
+ this._timestamp = params.timestamp;
50
+ this._restricted = params.restricted;
51
+ // Final integrity check at construction
52
+ assert(this.validateIntegrity(), "Identity integrity validation failed");
53
+ }
54
+ // ─────────────────────────────
55
+ // Static factory
56
+ // ─────────────────────────────
57
+ /**
58
+ * Create a new immutable identity from MajikUser
59
+ */
60
+ static create(user, account, options) {
61
+ assert(user, "MajikUser is required");
62
+ const userValidResult = user.validate();
63
+ if (!userValidResult.isValid) {
64
+ throw new Error(`Invalid MajikUser: ${userValidResult.errors.join(", ")}`);
65
+ }
66
+ const label = options?.label || account?.meta?.label || user.displayName;
67
+ assertString(label, "label");
68
+ const generatedID = autogenerateID();
69
+ const timestamp = new Date().toISOString();
70
+ const phash = sha256(`${user.id}:${account.id}:${generatedID}`);
71
+ return new MajikMessageIdentity({
72
+ id: generatedID,
73
+ userId: user.id,
74
+ publicKey: account.id,
75
+ phash,
76
+ label,
77
+ timestamp,
78
+ restricted: options?.restricted ?? false,
79
+ });
80
+ }
81
+ // ─────────────────────────────
82
+ // Getters (safe, read-only)
83
+ // ─────────────────────────────
84
+ get id() {
85
+ return this._id;
86
+ }
87
+ get userID() {
88
+ return this._userId;
89
+ }
90
+ get publicKey() {
91
+ return this._publicKey;
92
+ }
93
+ get phash() {
94
+ return this._phash;
95
+ }
96
+ get label() {
97
+ return this._label;
98
+ }
99
+ get timestamp() {
100
+ return this._timestamp;
101
+ }
102
+ get restricted() {
103
+ return this._restricted;
104
+ }
105
+ // ─────────────────────────────
106
+ // Mutators (restricted)
107
+ // ─────────────────────────────
108
+ /**
109
+ * Only mutable field
110
+ */
111
+ set label(label) {
112
+ assertString(label, "label");
113
+ this._label = label;
114
+ }
115
+ // ─────────────────────────────
116
+ // Identity checks
117
+ // ─────────────────────────────
118
+ /**
119
+ * Returns true if identity is restricted
120
+ */
121
+ isRestricted() {
122
+ return this._restricted === true;
123
+ }
124
+ /**
125
+ * Verify identity integrity
126
+ * Detects tampering of id/public_key
127
+ */
128
+ validateIntegrity() {
129
+ const expected = sha256(`${this._userId}:${this._publicKey}:${this._id}`);
130
+ return expected === this._phash;
131
+ }
132
+ /**
133
+ * Explicit verification helper
134
+ */
135
+ matches(userId, publicKey) {
136
+ assertString(userId, "userId");
137
+ assertString(publicKey, "publicKey");
138
+ const hash = sha256(`${userId}:${publicKey}:${this._id}`);
139
+ return hash === this._phash;
140
+ }
141
+ // ─────────────────────────────
142
+ // Serialization
143
+ // ─────────────────────────────
144
+ toJSON() {
145
+ return {
146
+ id: this._id,
147
+ user_id: this._userId,
148
+ public_key: this._publicKey,
149
+ phash: this._phash,
150
+ label: this._label,
151
+ timestamp: this._timestamp,
152
+ restricted: this._restricted,
153
+ };
154
+ }
155
+ static fromJSON(json) {
156
+ const obj = typeof json === "string" ? JSON.parse(json) : json;
157
+ assert(typeof obj === "object" && obj !== null, "Invalid JSON object");
158
+ const identity = new MajikMessageIdentity({
159
+ id: obj.id,
160
+ userId: obj.user_id,
161
+ publicKey: obj.public_key,
162
+ phash: obj.phash,
163
+ label: obj.label,
164
+ timestamp: obj.timestamp,
165
+ restricted: obj.restricted,
166
+ });
167
+ assert(identity.validateIntegrity(), "Invalid phash in JSON");
168
+ return identity;
169
+ }
170
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Target gender brackets for the audience.
3
+ */
4
+ export declare const UserGenderOptions: {
5
+ readonly MALE: "Male";
6
+ readonly FEMALE: "Female";
7
+ readonly OTHER: "Other";
8
+ };
9
+ export type UserGenderOptions = (typeof UserGenderOptions)[keyof typeof UserGenderOptions];
10
+ /**
11
+ * Enum representing different types of social media platforms.
12
+ */
13
+ export declare const SocialLinkType: {
14
+ readonly FACEBOOK: "Facebook";
15
+ readonly X: "X";
16
+ readonly TIKTOK: "Tik-Tok";
17
+ readonly THREADS: "Threads";
18
+ readonly INSTAGRAM: "Instagram";
19
+ readonly YOUTUBE: "Youtube";
20
+ readonly SPOTIFY: "Spotify";
21
+ readonly APPLE_MUSIC: "Apple Music";
22
+ readonly LINKEDIN: "LinkedIn";
23
+ readonly WEBSITE: "Website URL";
24
+ };
25
+ export type SocialLinkType = (typeof SocialLinkType)[keyof typeof SocialLinkType];
26
+ /** Payment methods accepted for the invoice */
27
+ export declare const PaymentMethod: {
28
+ /** Payment is made in cash */
29
+ readonly CASH: "Cash";
30
+ /** Payment is made via bank transfer or deposit */
31
+ readonly BANK: "Bank";
32
+ /** Payment is made via e-wallet transfer */
33
+ readonly EWALLET: "E-Wallet";
34
+ /** Payment is made using a check */
35
+ readonly CHECK: "Check";
36
+ };
37
+ export type PaymentMethod = (typeof PaymentMethod)[keyof typeof PaymentMethod];
38
+ export declare const Visibility: {
39
+ readonly PRIVATE: "Private";
40
+ readonly PUBLIC: "Public";
41
+ readonly LIMITED: "Limited";
42
+ readonly UNLISTED: "Unlisted";
43
+ };
44
+ export type Visibility = (typeof Visibility)[keyof typeof Visibility];
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Target gender brackets for the audience.
3
+ */
4
+ export const UserGenderOptions = {
5
+ MALE: 'Male',
6
+ FEMALE: 'Female',
7
+ OTHER: 'Other'
8
+ };
9
+ /**
10
+ * Enum representing different types of social media platforms.
11
+ */
12
+ export const SocialLinkType = {
13
+ FACEBOOK: 'Facebook',
14
+ X: 'X',
15
+ TIKTOK: 'Tik-Tok',
16
+ THREADS: 'Threads',
17
+ INSTAGRAM: 'Instagram',
18
+ YOUTUBE: 'Youtube',
19
+ SPOTIFY: 'Spotify',
20
+ APPLE_MUSIC: 'Apple Music',
21
+ LINKEDIN: 'LinkedIn',
22
+ WEBSITE: 'Website URL'
23
+ };
24
+ /** Payment methods accepted for the invoice */
25
+ export const PaymentMethod = {
26
+ /** Payment is made in cash */
27
+ CASH: 'Cash',
28
+ /** Payment is made via bank transfer or deposit */
29
+ BANK: 'Bank',
30
+ /** Payment is made via e-wallet transfer */
31
+ EWALLET: 'E-Wallet',
32
+ /** Payment is made using a check */
33
+ CHECK: 'Check'
34
+ };
35
+ export const Visibility = {
36
+ PRIVATE: 'Private',
37
+ PUBLIC: 'Public',
38
+ LIMITED: 'Limited',
39
+ UNLISTED: 'Unlisted'
40
+ };
@@ -0,0 +1,257 @@
1
+ import type { UserBasicInformation, FullName, Address, UserSettings, MajikUserJSON, SupabaseUser, YYYYMMDD } from './types';
2
+ import type { UserGenderOptions } from './enums';
3
+ export interface MajikUserData<TMetadata extends UserBasicInformation = UserBasicInformation> {
4
+ id: string;
5
+ email: string;
6
+ displayName: string;
7
+ hash: string;
8
+ metadata: TMetadata;
9
+ settings: UserSettings;
10
+ createdAt: Date;
11
+ lastUpdate: Date;
12
+ }
13
+ /**
14
+ * Base user class for database persistence
15
+ * Designed to be extended by subclasses with additional metadata
16
+ */
17
+ export declare class MajikUser<TMetadata extends UserBasicInformation = UserBasicInformation> {
18
+ readonly id: string;
19
+ protected _email: string;
20
+ protected _displayName: string;
21
+ protected _hash: string;
22
+ protected _metadata: TMetadata;
23
+ protected _settings: UserSettings;
24
+ readonly createdAt: Date;
25
+ protected _lastUpdate: Date;
26
+ constructor(data: MajikUserData<TMetadata>);
27
+ /**
28
+ * Initialize a new user with email and display name
29
+ * Generates a UUID for the id if unset and sets timestamps
30
+ */
31
+ static initialize<T extends MajikUser>(this: new (data: MajikUserData<any>) => T, email: string, displayName: string, id?: string): T;
32
+ /**
33
+ * Deserialize user from JSON object or JSON string
34
+ */
35
+ static fromJSON<T extends MajikUser>(this: new (data: MajikUserData<any>) => T, json: MajikUserJSON<any> | string): T;
36
+ /**
37
+ * Create MajikUser from Supabase User object
38
+ * Maps Supabase user fields to MajikUser structure
39
+ */
40
+ static fromSupabase<T extends MajikUser>(this: new (data: MajikUserData<any>) => T, supabaseUser: SupabaseUser): T;
41
+ get email(): string;
42
+ get displayName(): string;
43
+ get hash(): string;
44
+ get metadata(): Readonly<TMetadata>;
45
+ get settings(): Readonly<UserSettings>;
46
+ get lastUpdate(): Date;
47
+ /**
48
+ * Get user's full name if available
49
+ */
50
+ get fullName(): string | null;
51
+ get fullNameObject(): FullName | null;
52
+ set fullNameObject(name: FullName);
53
+ /**
54
+ * Get user's formatted name (first + last)
55
+ */
56
+ get formattedName(): string;
57
+ /**
58
+ * Get user's first name if available
59
+ */
60
+ get firstName(): string | null;
61
+ /**
62
+ * Get user's last name if available
63
+ */
64
+ get lastName(): string | null;
65
+ /**
66
+ * Get user's gender
67
+ */
68
+ get gender(): string;
69
+ /**
70
+ * Calculate user's age from birthdate
71
+ */
72
+ get age(): number | null;
73
+ /**
74
+ * Get user's first name if available
75
+ */
76
+ get birthday(): YYYYMMDD | null;
77
+ /**
78
+ * Check if email is verified
79
+ */
80
+ get isEmailVerified(): boolean;
81
+ /**
82
+ * Check if phone is verified
83
+ */
84
+ get isPhoneVerified(): boolean;
85
+ /**
86
+ * Check if identity is verified
87
+ */
88
+ get isIdentityVerified(): boolean;
89
+ /**
90
+ * Check if all verification steps are complete
91
+ */
92
+ get isFullyVerified(): boolean;
93
+ /**
94
+ * Get user's initials from name or display name
95
+ */
96
+ get initials(): string;
97
+ set email(value: string);
98
+ set displayName(value: string);
99
+ set hash(value: string);
100
+ /**
101
+ * Update user's full name
102
+ */
103
+ setName(name: FullName): void;
104
+ /**
105
+ * Update user's profile picture
106
+ */
107
+ setPicture(url: string): void;
108
+ /**
109
+ * Update user's phone number
110
+ */
111
+ setPhone(phone: string): void;
112
+ /**
113
+ * Update user's address
114
+ */
115
+ setAddress(address: Address): void;
116
+ /**
117
+ * Update user's birthdate
118
+ * Accepts either YYYY-MM-DD string or Date object
119
+ */
120
+ setBirthdate(birthdate: YYYYMMDD | Date): void;
121
+ /**
122
+ * Update user's address
123
+ */
124
+ setGender(gender: UserGenderOptions): void;
125
+ /**
126
+ * Update user's bio
127
+ */
128
+ setBio(bio: string): void;
129
+ /**
130
+ * Update user's language preference
131
+ */
132
+ setLanguage(language: string): void;
133
+ /**
134
+ * Update user's timezone
135
+ */
136
+ setTimezone(timezone: string): void;
137
+ /**
138
+ * Add or update a social link
139
+ */
140
+ setSocialLink(platform: string, url: string): void;
141
+ /**
142
+ * Remove a social link
143
+ */
144
+ removeSocialLink(platform: string): void;
145
+ /**
146
+ * Update a specific metadata field
147
+ */
148
+ setMetadata(key: keyof TMetadata, value: TMetadata[typeof key]): void;
149
+ /**
150
+ * Merge multiple metadata fields
151
+ */
152
+ updateMetadata(updates: Partial<TMetadata>): void;
153
+ /**
154
+ * Mark email as verified
155
+ */
156
+ verifyEmail(): void;
157
+ /**
158
+ * Mark email as unverified
159
+ */
160
+ unverifyEmail(): void;
161
+ /**
162
+ * Mark phone as verified
163
+ */
164
+ verifyPhone(): void;
165
+ /**
166
+ * Mark phone as unverified
167
+ */
168
+ unverifyPhone(): void;
169
+ /**
170
+ * Mark identity as verified (KYC)
171
+ */
172
+ verifyIdentity(): void;
173
+ /**
174
+ * Mark identity as unverified
175
+ */
176
+ unverifyIdentity(): void;
177
+ /**
178
+ * Update a specific setting
179
+ */
180
+ setSetting(key: string, value: unknown): void;
181
+ /**
182
+ * Merge multiple settings
183
+ */
184
+ updateSettings(updates: Partial<UserSettings>): void;
185
+ /**
186
+ * Enable notifications
187
+ */
188
+ enableNotifications(): void;
189
+ /**
190
+ * Disable notifications
191
+ */
192
+ disableNotifications(): void;
193
+ /**
194
+ * Check if user is currently restricted
195
+ */
196
+ isCurrentlyRestricted(): boolean;
197
+ /**
198
+ * Restrict user until a specific date (or indefinitely)
199
+ */
200
+ restrict(until?: Date): void;
201
+ /**
202
+ * Remove restriction from user
203
+ */
204
+ unrestrict(): void;
205
+ /**
206
+ * Check if this user has the same ID as another user
207
+ */
208
+ equals(other: MajikUser): boolean;
209
+ /**
210
+ * Check if user has complete profile information
211
+ */
212
+ hasCompleteProfile(): boolean;
213
+ /**
214
+ * Get profile completion percentage (0-100)
215
+ */
216
+ getProfileCompletionPercentage(): number;
217
+ validate(): {
218
+ isValid: boolean;
219
+ errors: string[];
220
+ };
221
+ /**
222
+ * Create a shallow clone of the user
223
+ */
224
+ clone(): MajikUser<TMetadata>;
225
+ /**
226
+ * Get a supabase ready version of user data (metadata)
227
+ */
228
+ toSupabaseJSON(): Record<string, unknown>;
229
+ /**
230
+ * Get a sanitized version of user data (removes sensitive info)
231
+ */
232
+ toPublicJSON(): Record<string, unknown>;
233
+ /**
234
+ * Serialize user to JSON-compatible object
235
+ */
236
+ toJSON(): MajikUserJSON<TMetadata>;
237
+ /**
238
+ * Updates the lastUpdate timestamp
239
+ */
240
+ protected updateTimestamp(): void;
241
+ /**
242
+ * Validates email format
243
+ */
244
+ protected validateEmail(email: string): void;
245
+ /**
246
+ * Generate a cryptographically secure unique identifier
247
+ */
248
+ protected static generateID(): string;
249
+ /**
250
+ * Validate ID format
251
+ */
252
+ protected static validateID(id: string): boolean;
253
+ /**
254
+ * Hash an ID using SHA-256
255
+ */
256
+ protected static hashID(id: string): string;
257
+ }