@thezelijah/majik-message 1.1.1 → 1.1.3

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,839 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { arrayBufferToBase64, arrayToBase64, dateToYYYYMMDD, stripUndefined, } from "./utils";
3
- import { generateMnemonic, mnemonicToSeedSync } from "@scure/bip39";
4
- import * as ed25519 from "@stablelib/ed25519";
5
- import ed2curve from "ed2curve";
6
- import { hash } from "@stablelib/sha256";
7
- import { wordlist } from "@scure/bip39/wordlists/english";
8
- /**
9
- * Base user class for database persistence
10
- * Designed to be extended by subclasses with additional metadata
11
- */
12
- export class MajikUser {
13
- id;
14
- _email;
15
- _displayName;
16
- _hash;
17
- _metadata;
18
- _settings;
19
- createdAt;
20
- _lastUpdate;
21
- constructor(data) {
22
- this.id = data.id;
23
- this._email = data.email;
24
- this._displayName = data.displayName;
25
- this._hash = data.hash;
26
- this._metadata = { ...data.metadata };
27
- this._settings = { ...data.settings };
28
- this.createdAt = new Date(data.createdAt);
29
- this._lastUpdate = new Date(data.lastUpdate);
30
- }
31
- // ==================== STATIC FACTORY METHODS ====================
32
- /**
33
- * Initialize a new user with email and display name
34
- * Generates a UUID for the id if unset and sets timestamps
35
- */
36
- static initialize(email, displayName, id) {
37
- if (!email) {
38
- throw new Error("Email cannot be empty");
39
- }
40
- if (!displayName) {
41
- throw new Error("Display name cannot be empty");
42
- }
43
- const userID = !id?.trim() ? MajikUser.generateID() : id;
44
- const instance = new this({
45
- id: userID,
46
- email,
47
- displayName,
48
- hash: MajikUser.hashID(userID),
49
- metadata: {
50
- verification: {
51
- email_verified: false,
52
- phone_verified: false,
53
- identity_verified: false,
54
- },
55
- },
56
- settings: {
57
- notifications: true,
58
- system: {
59
- isRestricted: false,
60
- },
61
- },
62
- createdAt: new Date(),
63
- lastUpdate: new Date(),
64
- });
65
- instance.validateEmail(email);
66
- return instance;
67
- }
68
- /**
69
- * Deserialize user from JSON object or JSON string
70
- */
71
- static fromJSON(json) {
72
- // Parse string to object if needed
73
- const data = typeof json === "string" ? JSON.parse(json) : json;
74
- if (!data.id || typeof data.id !== "string") {
75
- throw new Error("Invalid user data: missing or invalid id");
76
- }
77
- if (!data.email || typeof data.email !== "string") {
78
- throw new Error("Invalid user data: missing or invalid email");
79
- }
80
- if (!data.displayName || typeof data.displayName !== "string") {
81
- throw new Error("Invalid user data: missing or invalid displayName");
82
- }
83
- if (!data.hash || typeof data.hash !== "string") {
84
- throw new Error("Invalid user data: missing or invalid hash");
85
- }
86
- const userData = {
87
- id: data.id,
88
- email: data.email,
89
- displayName: data.displayName,
90
- hash: data.hash,
91
- metadata: data.metadata || {},
92
- settings: data.settings || {
93
- notifications: true,
94
- system: {
95
- isRestricted: false,
96
- },
97
- },
98
- createdAt: data.createdAt ? new Date(data.createdAt) : new Date(),
99
- lastUpdate: data.lastUpdate ? new Date(data.lastUpdate) : new Date(),
100
- };
101
- return new this(userData);
102
- }
103
- /**
104
- * Create MajikUser from Supabase User object
105
- * Maps Supabase user fields to MajikUser structure
106
- */
107
- static fromSupabase(supabaseUser) {
108
- if (!supabaseUser.id) {
109
- throw new Error("Invalid Supabase user: missing id");
110
- }
111
- if (!supabaseUser.email) {
112
- throw new Error("Invalid Supabase user: missing email");
113
- }
114
- // Extract display name from user_metadata or email
115
- const displayName = supabaseUser.user_metadata?.display_name ||
116
- supabaseUser.user_metadata?.full_name ||
117
- supabaseUser.user_metadata?.name ||
118
- supabaseUser.email.split("@")[0];
119
- // Map user_metadata to MajikUser metadata
120
- const metadata = {
121
- verification: {
122
- email_verified: !!supabaseUser.email_confirmed_at,
123
- phone_verified: !!supabaseUser.phone_confirmed_at,
124
- identity_verified: false,
125
- },
126
- };
127
- // Map optional fields from user_metadata if they exist
128
- if (supabaseUser.user_metadata) {
129
- const userMeta = supabaseUser.user_metadata;
130
- // Name mapping
131
- if (userMeta.first_name || userMeta.family_name) {
132
- metadata.name = {
133
- first_name: userMeta.first_name || "",
134
- last_name: userMeta.family_name || "",
135
- middle_name: userMeta.middle_name,
136
- suffix: userMeta.suffix,
137
- };
138
- }
139
- // Direct field mappings
140
- if (userMeta.picture || userMeta.avatar_url) {
141
- metadata.picture = userMeta.picture || userMeta.avatar_url;
142
- }
143
- if (userMeta.bio)
144
- metadata.bio = userMeta.bio;
145
- if (userMeta.phone)
146
- metadata.phone = userMeta.phone;
147
- if (userMeta.gender)
148
- metadata.gender = userMeta.gender;
149
- if (userMeta.birthdate)
150
- metadata.birthdate = userMeta.birthdate;
151
- if (userMeta.language)
152
- metadata.language = userMeta.language;
153
- if (userMeta.timezone)
154
- metadata.timezone = userMeta.timezone;
155
- if (userMeta.pronouns)
156
- metadata.pronouns = userMeta.pronouns;
157
- // Address mapping
158
- if (userMeta.address) {
159
- metadata.address = userMeta.address;
160
- }
161
- // Social links mapping
162
- if (userMeta.social_links) {
163
- metadata.social_links = userMeta.social_links;
164
- }
165
- // Company information
166
- if (userMeta.company) {
167
- metadata.company = userMeta.company;
168
- }
169
- }
170
- // Map app_metadata to settings
171
- const settings = {
172
- notifications: supabaseUser.app_metadata?.notifications ?? true,
173
- system: {
174
- isRestricted: supabaseUser.app_metadata?.is_restricted ?? false,
175
- restrictedUntil: supabaseUser.app_metadata?.restricted_until
176
- ? new Date(supabaseUser.app_metadata.restricted_until)
177
- : undefined,
178
- },
179
- };
180
- // Add any additional app_metadata to settings
181
- if (supabaseUser.app_metadata) {
182
- Object.keys(supabaseUser.app_metadata).forEach((key) => {
183
- if (!["notifications", "is_restricted", "restricted_until"].includes(key)) {
184
- settings[key] = supabaseUser.app_metadata[key];
185
- }
186
- });
187
- }
188
- const userData = {
189
- id: supabaseUser.id,
190
- email: supabaseUser.email,
191
- displayName,
192
- hash: MajikUser.hashID(supabaseUser.id),
193
- metadata,
194
- settings,
195
- createdAt: new Date(supabaseUser.created_at),
196
- lastUpdate: new Date(supabaseUser.updated_at || supabaseUser.created_at),
197
- };
198
- return new this(userData);
199
- }
200
- // ==================== GETTERS ====================
201
- get email() {
202
- return this._email;
203
- }
204
- get displayName() {
205
- return this._displayName;
206
- }
207
- get hash() {
208
- return this._hash;
209
- }
210
- get metadata() {
211
- return { ...this._metadata };
212
- }
213
- get settings() {
214
- return { ...this._settings };
215
- }
216
- get lastUpdate() {
217
- return new Date(this._lastUpdate);
218
- }
219
- /**
220
- * Get user's full name if available
221
- */
222
- get fullName() {
223
- if (!this._metadata.name)
224
- return null;
225
- const { first_name, middle_name, last_name, suffix } = this._metadata.name;
226
- const parts = [first_name, middle_name, last_name, suffix].filter(Boolean);
227
- return parts.join(" ");
228
- }
229
- get fullNameObject() {
230
- if (!this._metadata.name)
231
- return null;
232
- return this._metadata.name;
233
- }
234
- set fullNameObject(name) {
235
- if (!name || !name?.first_name?.trim() || !name?.last_name?.trim()) {
236
- throw new Error("Full name must contain first and last names");
237
- }
238
- this._metadata.name = name;
239
- this.updateTimestamp();
240
- }
241
- /**
242
- * Get user's formatted name (first + last)
243
- */
244
- get formattedName() {
245
- if (!this._metadata.name)
246
- return this._displayName;
247
- const { first_name, last_name } = this._metadata.name;
248
- if (first_name && last_name) {
249
- return `${first_name} ${last_name}`;
250
- }
251
- return this._displayName;
252
- }
253
- /**
254
- * Get user's first name if available
255
- */
256
- get firstName() {
257
- if (!this._metadata?.name?.first_name?.trim())
258
- return null;
259
- return this._metadata.name.first_name;
260
- }
261
- /**
262
- * Get user's last name if available
263
- */
264
- get lastName() {
265
- if (!this._metadata?.name?.last_name?.trim())
266
- return null;
267
- return this._metadata.name.last_name;
268
- }
269
- /**
270
- * Get user's gender
271
- */
272
- get gender() {
273
- return this._metadata.gender || "Unspecified";
274
- }
275
- /**
276
- * Calculate user's age from birthdate
277
- */
278
- get age() {
279
- const birthdate = this._metadata.birthdate;
280
- if (!birthdate)
281
- return null;
282
- const today = new Date();
283
- const birth = new Date(birthdate);
284
- let age = today.getFullYear() - birth.getFullYear();
285
- const monthDiff = today.getMonth() - birth.getMonth();
286
- if (monthDiff < 0 ||
287
- (monthDiff === 0 && today.getDate() < birth.getDate())) {
288
- age--;
289
- }
290
- return age;
291
- }
292
- /**
293
- * Get user's first name if available
294
- */
295
- get birthday() {
296
- if (!this._metadata?.birthdate?.trim())
297
- return null;
298
- return this._metadata.birthdate;
299
- }
300
- /**
301
- * Get user's full address if available
302
- */
303
- get address() {
304
- if (!this._metadata.address)
305
- return null;
306
- const { building, street, area, city, region, zip, country } = this._metadata.address;
307
- const parts = [building, street, area, city, region, zip, country].filter(Boolean);
308
- return parts.join(", ");
309
- }
310
- /**
311
- * Check if email is verified
312
- */
313
- get isEmailVerified() {
314
- return this._metadata.verification?.email_verified ?? false;
315
- }
316
- /**
317
- * Check if phone is verified
318
- */
319
- get isPhoneVerified() {
320
- return this._metadata.verification?.phone_verified ?? false;
321
- }
322
- /**
323
- * Check if identity is verified
324
- */
325
- get isIdentityVerified() {
326
- return this._metadata.verification?.identity_verified ?? false;
327
- }
328
- /**
329
- * Check if all verification steps are complete
330
- */
331
- get isFullyVerified() {
332
- return (this.isEmailVerified && this.isPhoneVerified && this.isIdentityVerified);
333
- }
334
- /**
335
- * Get user's initials from name or display name
336
- */
337
- get initials() {
338
- if (this._metadata.name) {
339
- const { first_name, last_name } = this._metadata.name;
340
- const firstInitial = first_name?.[0]?.toUpperCase() || "";
341
- const lastInitial = last_name?.[0]?.toUpperCase() || "";
342
- return (`${firstInitial}${lastInitial}`.trim() ||
343
- this._displayName[0].toUpperCase());
344
- }
345
- const names = this._displayName.split(" ");
346
- if (names.length >= 2) {
347
- return `${names[0][0]}${names[names.length - 1][0]}`.toUpperCase();
348
- }
349
- return this._displayName.slice(0, 2).toUpperCase();
350
- }
351
- // ==================== SETTERS ====================
352
- set email(value) {
353
- this.validateEmail(value);
354
- this._email = value;
355
- // Unverify email when changed
356
- if (this._metadata.verification) {
357
- this._metadata.verification.email_verified = false;
358
- }
359
- this.updateTimestamp();
360
- }
361
- set displayName(value) {
362
- if (!value || value.trim().length === 0) {
363
- throw new Error("Display name cannot be empty");
364
- }
365
- this._displayName = value;
366
- this.updateTimestamp();
367
- }
368
- set hash(value) {
369
- if (!value || value.length === 0) {
370
- throw new Error("Hash cannot be empty");
371
- }
372
- this._hash = value;
373
- this.updateTimestamp();
374
- }
375
- // ==================== METADATA METHODS ====================
376
- /**
377
- * Update user's full name
378
- */
379
- setName(name) {
380
- this.updateMetadata({ name });
381
- }
382
- /**
383
- * Update user's profile picture
384
- */
385
- setPicture(url) {
386
- this.updateMetadata({ picture: url });
387
- }
388
- /**
389
- * Update user's phone number
390
- */
391
- setPhone(phone) {
392
- this.updateMetadata({ phone });
393
- // Unverify phone when changed
394
- if (this._metadata.verification) {
395
- this._metadata.verification.phone_verified = false;
396
- }
397
- }
398
- /**
399
- * Update user's address
400
- */
401
- setAddress(address) {
402
- this.updateMetadata({ address });
403
- }
404
- /**
405
- * Update user's birthdate
406
- * Accepts either YYYY-MM-DD string or Date object
407
- */
408
- setBirthdate(birthdate) {
409
- let formatted;
410
- if (birthdate instanceof Date) {
411
- if (Number.isNaN(birthdate.getTime())) {
412
- throw new Error("Invalid Date object");
413
- }
414
- // Format to YYYY-MM-DD (UTC-safe)
415
- formatted = dateToYYYYMMDD(birthdate);
416
- }
417
- else {
418
- // Validate ISO date format YYYY-MM-DD
419
- if (!/^\d{4}-\d{2}-\d{2}$/.test(birthdate)) {
420
- throw new Error("Invalid birthdate format. Use YYYY-MM-DD");
421
- }
422
- formatted = birthdate;
423
- }
424
- this.updateMetadata({ birthdate: formatted });
425
- }
426
- /**
427
- * Update user's address
428
- */
429
- setGender(gender) {
430
- this.updateMetadata({ gender });
431
- }
432
- /**
433
- * Update user's bio
434
- */
435
- setBio(bio) {
436
- this.updateMetadata({ bio });
437
- }
438
- /**
439
- * Update user's language preference
440
- */
441
- setLanguage(language) {
442
- this.updateMetadata({ language });
443
- }
444
- /**
445
- * Update user's timezone
446
- */
447
- setTimezone(timezone) {
448
- this.updateMetadata({ timezone });
449
- }
450
- /**
451
- * Add or update a social link
452
- */
453
- setSocialLink(platform, url) {
454
- const socialLinks = { ...this._metadata.social_links, [platform]: url };
455
- this.updateMetadata({ social_links: socialLinks });
456
- }
457
- /**
458
- * Remove a social link
459
- */
460
- removeSocialLink(platform) {
461
- if (!this._metadata.social_links)
462
- return;
463
- const socialLinks = { ...this._metadata.social_links };
464
- delete socialLinks[platform];
465
- this.updateMetadata({ social_links: socialLinks });
466
- }
467
- /**
468
- * Update a specific metadata field
469
- */
470
- setMetadata(key, value) {
471
- this._metadata[key] = value;
472
- this.updateTimestamp();
473
- }
474
- /**
475
- * Merge multiple metadata fields
476
- */
477
- updateMetadata(updates) {
478
- this._metadata = { ...this._metadata, ...updates };
479
- this._lastUpdate = new Date();
480
- }
481
- // ==================== VERIFICATION METHODS ====================
482
- /**
483
- * Mark email as verified
484
- */
485
- verifyEmail() {
486
- const currentVerification = this._metadata.verification || {
487
- email_verified: false,
488
- phone_verified: false,
489
- identity_verified: false,
490
- };
491
- this.updateMetadata({
492
- verification: {
493
- ...currentVerification,
494
- email_verified: true,
495
- },
496
- });
497
- }
498
- /**
499
- * Mark email as unverified
500
- */
501
- unverifyEmail() {
502
- const currentVerification = this._metadata.verification || {
503
- email_verified: false,
504
- phone_verified: false,
505
- identity_verified: false,
506
- };
507
- this.updateMetadata({
508
- verification: {
509
- ...currentVerification,
510
- email_verified: false,
511
- },
512
- });
513
- }
514
- /**
515
- * Mark phone as verified
516
- */
517
- verifyPhone() {
518
- const currentVerification = this._metadata.verification || {
519
- email_verified: false,
520
- phone_verified: false,
521
- identity_verified: false,
522
- };
523
- this.updateMetadata({
524
- verification: {
525
- ...currentVerification,
526
- phone_verified: true,
527
- },
528
- });
529
- }
530
- /**
531
- * Mark phone as unverified
532
- */
533
- unverifyPhone() {
534
- const currentVerification = this._metadata.verification || {
535
- email_verified: false,
536
- phone_verified: false,
537
- identity_verified: false,
538
- };
539
- this.updateMetadata({
540
- verification: {
541
- ...currentVerification,
542
- phone_verified: false,
543
- },
544
- });
545
- }
546
- /**
547
- * Mark identity as verified (KYC)
548
- */
549
- verifyIdentity() {
550
- const currentVerification = this._metadata.verification || {
551
- email_verified: false,
552
- phone_verified: false,
553
- identity_verified: false,
554
- };
555
- this.updateMetadata({
556
- verification: {
557
- ...currentVerification,
558
- identity_verified: true,
559
- },
560
- });
561
- }
562
- /**
563
- * Mark identity as unverified
564
- */
565
- unverifyIdentity() {
566
- const currentVerification = this._metadata.verification || {
567
- email_verified: false,
568
- phone_verified: false,
569
- identity_verified: false,
570
- };
571
- this.updateMetadata({
572
- verification: {
573
- ...currentVerification,
574
- identity_verified: false,
575
- },
576
- });
577
- }
578
- // ==================== SETTINGS METHODS ====================
579
- /**
580
- * Update a specific setting
581
- */
582
- setSetting(key, value) {
583
- this._settings[key] = value;
584
- this.updateTimestamp();
585
- }
586
- /**
587
- * Merge multiple settings
588
- */
589
- updateSettings(updates) {
590
- this._settings = {
591
- ...this._settings,
592
- ...updates,
593
- system: {
594
- ...this._settings.system,
595
- ...(updates.system || {}),
596
- },
597
- };
598
- this.updateTimestamp();
599
- }
600
- /**
601
- * Enable notifications
602
- */
603
- enableNotifications() {
604
- this.updateSettings({ notifications: true });
605
- }
606
- /**
607
- * Disable notifications
608
- */
609
- disableNotifications() {
610
- this.updateSettings({ notifications: false });
611
- }
612
- // ==================== RESTRICTION METHODS ====================
613
- /**
614
- * Check if user is currently restricted
615
- */
616
- isCurrentlyRestricted() {
617
- if (!this._settings.system.isRestricted)
618
- return false;
619
- if (!this._settings.system.restrictedUntil)
620
- return true;
621
- return new Date() < this._settings.system.restrictedUntil;
622
- }
623
- /**
624
- * Restrict user until a specific date (or indefinitely)
625
- */
626
- restrict(until) {
627
- this.updateSettings({
628
- system: {
629
- isRestricted: true,
630
- restrictedUntil: until,
631
- },
632
- });
633
- }
634
- /**
635
- * Remove restriction from user
636
- */
637
- unrestrict() {
638
- this.updateSettings({
639
- system: {
640
- isRestricted: false,
641
- restrictedUntil: undefined,
642
- },
643
- });
644
- }
645
- // ==================== COMPARISON & UTILITY METHODS ====================
646
- /**
647
- * Check if this user has the same ID as another user
648
- */
649
- equals(other) {
650
- return this.id === other.id;
651
- }
652
- /**
653
- * Check if user has complete profile information
654
- */
655
- hasCompleteProfile() {
656
- return !!(this._metadata.name &&
657
- this._metadata.phone &&
658
- this._metadata.birthdate &&
659
- this._metadata.address &&
660
- this._metadata.gender);
661
- }
662
- /**
663
- * Get profile completion percentage (0-100)
664
- */
665
- getProfileCompletionPercentage() {
666
- const fields = [
667
- "name",
668
- "picture",
669
- "phone",
670
- "gender",
671
- "birthdate",
672
- "address",
673
- "bio",
674
- ];
675
- const completedFields = fields.filter((field) => {
676
- const value = this._metadata[field];
677
- if (typeof value === "object" && value !== null) {
678
- return Object.keys(value).length > 0;
679
- }
680
- return !!value;
681
- }).length;
682
- return Math.round((completedFields / fields.length) * 100);
683
- }
684
- // Add detailed validation with error collection
685
- validate() {
686
- const errors = [];
687
- // Required fields
688
- if (!this.id)
689
- errors.push("ID is required");
690
- if (!this._email)
691
- errors.push("Email is required");
692
- if (!this._displayName)
693
- errors.push("Display name is required");
694
- if (!this._hash)
695
- errors.push("Hash is required");
696
- // Format validation
697
- try {
698
- this.validateEmail(this._email);
699
- }
700
- catch (e) {
701
- errors.push(`Invalid email format: ${e}`);
702
- }
703
- // Date validation
704
- if (!(this.createdAt instanceof Date) || isNaN(this.createdAt.getTime())) {
705
- errors.push("Invalid createdAt date");
706
- }
707
- if (!(this._lastUpdate instanceof Date) ||
708
- isNaN(this._lastUpdate.getTime())) {
709
- errors.push("Invalid lastUpdate date");
710
- }
711
- // Metadata validation
712
- if (this._metadata.phone) {
713
- if (!/^\+?[1-9]\d{1,14}$/.test(this._metadata.phone)) {
714
- errors.push("Invalid phone number format");
715
- }
716
- }
717
- if (this._metadata.birthdate) {
718
- if (!/^\d{4}-\d{2}-\d{2}$/.test(this._metadata.birthdate)) {
719
- errors.push("Invalid birthdate format");
720
- }
721
- }
722
- return {
723
- isValid: errors.length === 0,
724
- errors,
725
- };
726
- }
727
- /**
728
- * Create a shallow clone of the user
729
- */
730
- clone() {
731
- return new this.constructor({
732
- id: this.id,
733
- email: this._email,
734
- displayName: this._displayName,
735
- hash: this._hash,
736
- metadata: { ...this._metadata },
737
- settings: { ...this._settings },
738
- createdAt: this.createdAt,
739
- lastUpdate: this._lastUpdate,
740
- });
741
- }
742
- /**
743
- * Get a supabase ready version of user data (metadata)
744
- */
745
- toSupabaseJSON() {
746
- return stripUndefined({
747
- age: this.age,
748
- name: this.fullName,
749
- gender: this.metadata.gender,
750
- address: this.metadata.address,
751
- picture: this.metadata.picture,
752
- birthdate: this.metadata.birthdate,
753
- full_name: this.fullName,
754
- bio: this.metadata.bio,
755
- first_name: this.metadata.name?.first_name,
756
- family_name: this.metadata.name?.last_name,
757
- display_name: this.displayName,
758
- });
759
- }
760
- /**
761
- * Get a sanitized version of user data (removes sensitive info)
762
- */
763
- toPublicJSON() {
764
- return {
765
- id: this.id,
766
- displayName: this._displayName,
767
- picture: this._metadata.picture,
768
- bio: this._metadata.bio,
769
- createdAt: this.createdAt.toISOString(),
770
- };
771
- }
772
- /**
773
- * Serialize user to JSON-compatible object
774
- */
775
- toJSON() {
776
- return {
777
- id: this.id,
778
- email: this._email,
779
- displayName: this._displayName,
780
- hash: this._hash,
781
- metadata: { ...this._metadata },
782
- settings: { ...this._settings },
783
- createdAt: this.createdAt.toISOString(),
784
- lastUpdate: this._lastUpdate.toISOString(),
785
- };
786
- }
787
- // ==================== PROTECTED HELPER METHODS ====================
788
- /**
789
- * Updates the lastUpdate timestamp
790
- */
791
- updateTimestamp() {
792
- this._lastUpdate = new Date();
793
- }
794
- /**
795
- * Validates email format
796
- */
797
- validateEmail(email) {
798
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
799
- if (!emailRegex.test(email)) {
800
- throw new Error("Invalid email format");
801
- }
802
- }
803
- // ==================== STATIC CRYPTOGRAPHIC METHODS ====================
804
- /**
805
- * Generate a cryptographically secure unique identifier
806
- */
807
- static generateID() {
808
- try {
809
- const mnemonic = generateMnemonic(wordlist, 128);
810
- const seed = mnemonicToSeedSync(mnemonic);
811
- const seed32 = new Uint8Array(seed.slice(0, 32));
812
- const ed = ed25519.generateKeyPairFromSeed(seed32);
813
- const skCurve = ed2curve.convertSecretKey(ed.secretKey);
814
- const pkCurve = ed2curve.convertPublicKey(ed.publicKey);
815
- if (!skCurve || !pkCurve) {
816
- throw new Error("Failed to convert derived Ed25519 keys to Curve25519");
817
- }
818
- const pkCurveBytes = new Uint8Array(pkCurve);
819
- const publicKey = arrayBufferToBase64(pkCurveBytes.buffer);
820
- return publicKey;
821
- }
822
- catch (error) {
823
- throw new Error(`Failed to generate user ID: ${error}`);
824
- }
825
- }
826
- /**
827
- * Validate ID format
828
- */
829
- static validateID(id) {
830
- return /^[A-Za-z0-9+/]+=*$/.test(id) && id.length > 0;
831
- }
832
- /**
833
- * Hash an ID using SHA-256
834
- */
835
- static hashID(id) {
836
- const hashedID = hash(new TextEncoder().encode(id));
837
- return arrayToBase64(hashedID);
838
- }
839
- }