shogun-core 1.2.4 → 1.2.5
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/gundb/gun.js
CHANGED
|
@@ -87,6 +87,21 @@ class GunDB {
|
|
|
87
87
|
this.crypto = crypto;
|
|
88
88
|
this.utils = utils;
|
|
89
89
|
this.node = this.gun.get(appScope);
|
|
90
|
+
// Attempt to restore session after initialization
|
|
91
|
+
setTimeout(async () => {
|
|
92
|
+
try {
|
|
93
|
+
const sessionResult = await this.restoreSession();
|
|
94
|
+
if (sessionResult.success) {
|
|
95
|
+
(0, logger_1.log)(`Session automatically restored for user: ${sessionResult.userPub}`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
(0, logger_1.log)(`No previous session to restore: ${sessionResult.error}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
(0, logger_1.logError)("Error during automatic session restoration:", error);
|
|
103
|
+
}
|
|
104
|
+
}, 500); // Give Gun time to initialize
|
|
90
105
|
}
|
|
91
106
|
subscribeToAuthEvents() {
|
|
92
107
|
this.gun.on("auth", (ack) => {
|
|
@@ -344,6 +359,16 @@ class GunDB {
|
|
|
344
359
|
(0, logger_1.log)(err);
|
|
345
360
|
return { success: false, error: err };
|
|
346
361
|
}
|
|
362
|
+
// Check if username already exists
|
|
363
|
+
(0, logger_1.log)(`Checking if username ${username} already exists...`);
|
|
364
|
+
const existingUser = await this.checkUsernameExists(username);
|
|
365
|
+
if (existingUser) {
|
|
366
|
+
(0, logger_1.log)(`Username ${username} already exists with pub: ${existingUser.pub}`);
|
|
367
|
+
return {
|
|
368
|
+
success: false,
|
|
369
|
+
error: `Username '${username}' already exists. Please try to login instead.`,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
347
372
|
// Create user directly with Gun
|
|
348
373
|
const createResult = await new Promise((resolve) => {
|
|
349
374
|
this.gun.user().create(username, password, (ack) => {
|
|
@@ -360,39 +385,105 @@ class GunDB {
|
|
|
360
385
|
if (!createResult.success) {
|
|
361
386
|
return createResult;
|
|
362
387
|
}
|
|
363
|
-
// Store user metadata with improved safety
|
|
388
|
+
// Store user metadata with improved safety and wait for confirmation
|
|
364
389
|
try {
|
|
365
|
-
const
|
|
390
|
+
const userNode = this.gun.get(createResult.pub);
|
|
391
|
+
const userMetadata = {
|
|
366
392
|
username: username,
|
|
367
393
|
pub: createResult.pub,
|
|
394
|
+
createdAt: Date.now(),
|
|
395
|
+
};
|
|
396
|
+
// Save user metadata
|
|
397
|
+
await new Promise((resolve, reject) => {
|
|
398
|
+
userNode.put(userMetadata, (ack) => {
|
|
399
|
+
if (ack.err) {
|
|
400
|
+
reject(new Error(`Failed to save user metadata: ${ack.err}`));
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
(0, logger_1.log)(`User metadata saved for: ${username}`);
|
|
404
|
+
resolve();
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
// Add to users collection and wait for confirmation
|
|
409
|
+
await new Promise((resolve, reject) => {
|
|
410
|
+
this.gun.get("users").set(userNode, (ack) => {
|
|
411
|
+
if (ack.err) {
|
|
412
|
+
reject(new Error(`Failed to add user to collection: ${ack.err}`));
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
(0, logger_1.log)(`User added to collection: ${username}`);
|
|
416
|
+
resolve();
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
// Create a username -> pub mapping for faster lookups
|
|
421
|
+
await new Promise((resolve, reject) => {
|
|
422
|
+
this.gun
|
|
423
|
+
.get("usernames")
|
|
424
|
+
.get(username)
|
|
425
|
+
.put(createResult.pub, (ack) => {
|
|
426
|
+
if (ack.err) {
|
|
427
|
+
(0, logger_1.logError)(`Warning: Could not create username mapping: ${ack.err}`);
|
|
428
|
+
resolve(); // Don't fail registration for this
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
(0, logger_1.log)(`Username mapping created: ${username} -> ${createResult.pub}`);
|
|
432
|
+
resolve();
|
|
433
|
+
}
|
|
434
|
+
});
|
|
368
435
|
});
|
|
369
|
-
this.gun.get("users").set(user);
|
|
370
436
|
}
|
|
371
437
|
catch (metadataError) {
|
|
372
438
|
(0, logger_1.logError)(`Warning: Could not store user metadata: ${metadataError}`);
|
|
373
439
|
// Continue with login attempt even if metadata storage fails
|
|
374
440
|
}
|
|
375
|
-
// Login after creation
|
|
441
|
+
// Login after creation with retry mechanism
|
|
376
442
|
(0, logger_1.log)(`Attempting login after registration for: ${username}`);
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
443
|
+
let loginAttempts = 0;
|
|
444
|
+
const maxAttempts = 3;
|
|
445
|
+
while (loginAttempts < maxAttempts) {
|
|
446
|
+
try {
|
|
447
|
+
const loginResult = await this.login(username, password);
|
|
448
|
+
if (loginResult.success) {
|
|
449
|
+
(0, logger_1.log)(`Login after registration successful for: ${username}`);
|
|
450
|
+
return {
|
|
451
|
+
success: true,
|
|
452
|
+
userPub: loginResult.userPub,
|
|
453
|
+
username: loginResult.username,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
loginAttempts++;
|
|
458
|
+
if (loginAttempts < maxAttempts) {
|
|
459
|
+
(0, logger_1.log)(`Login attempt ${loginAttempts} failed, retrying...`);
|
|
460
|
+
await new Promise((resolve) => setTimeout(resolve, 1000 * loginAttempts));
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
(0, logger_1.logError)(`Login after registration failed after ${maxAttempts} attempts: ${loginResult.error}`);
|
|
464
|
+
return {
|
|
465
|
+
success: false,
|
|
466
|
+
error: `Registration completed but login failed: ${loginResult.error}`,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
catch (loginError) {
|
|
472
|
+
loginAttempts++;
|
|
473
|
+
if (loginAttempts >= maxAttempts) {
|
|
474
|
+
(0, logger_1.logError)(`Exception during post-registration login: ${loginError}`);
|
|
475
|
+
return {
|
|
476
|
+
success: false,
|
|
477
|
+
error: "Exception during post-registration login",
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
await new Promise((resolve) => setTimeout(resolve, 1000 * loginAttempts));
|
|
385
481
|
}
|
|
386
|
-
(0, logger_1.log)(`Login after registration successful for: ${username}`);
|
|
387
|
-
return loginResult;
|
|
388
|
-
}
|
|
389
|
-
catch (loginError) {
|
|
390
|
-
(0, logger_1.logError)(`Exception during post-registration login: ${loginError}`);
|
|
391
|
-
return {
|
|
392
|
-
success: false,
|
|
393
|
-
error: "Exception during post-registration login",
|
|
394
|
-
};
|
|
395
482
|
}
|
|
483
|
+
return {
|
|
484
|
+
success: false,
|
|
485
|
+
error: "Failed to login after registration",
|
|
486
|
+
};
|
|
396
487
|
}
|
|
397
488
|
catch (error) {
|
|
398
489
|
(0, logger_1.logError)(`Unexpected error during registration flow: ${error}`);
|
|
@@ -402,6 +493,62 @@ class GunDB {
|
|
|
402
493
|
};
|
|
403
494
|
}
|
|
404
495
|
}
|
|
496
|
+
/**
|
|
497
|
+
* Check if a username already exists in the system
|
|
498
|
+
* @param username Username to check
|
|
499
|
+
* @returns Promise resolving to user data if exists, null otherwise
|
|
500
|
+
*/
|
|
501
|
+
async checkUsernameExists(username) {
|
|
502
|
+
try {
|
|
503
|
+
// First check the username mapping (faster)
|
|
504
|
+
const mappedPub = await new Promise((resolve) => {
|
|
505
|
+
this.gun
|
|
506
|
+
.get("usernames")
|
|
507
|
+
.get(username)
|
|
508
|
+
.once((pub) => {
|
|
509
|
+
resolve(pub || null);
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
if (mappedPub) {
|
|
513
|
+
// Get user data from the pub
|
|
514
|
+
const userData = await new Promise((resolve) => {
|
|
515
|
+
this.gun.get(mappedPub).once((data) => {
|
|
516
|
+
resolve(data);
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
return userData;
|
|
520
|
+
}
|
|
521
|
+
// Fallback: Search through all users collection (slower but more reliable)
|
|
522
|
+
const existingUser = await new Promise((resolve) => {
|
|
523
|
+
let found = false;
|
|
524
|
+
let timeoutId;
|
|
525
|
+
const checkComplete = () => {
|
|
526
|
+
if (timeoutId)
|
|
527
|
+
clearTimeout(timeoutId);
|
|
528
|
+
if (!found) {
|
|
529
|
+
resolve(null);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
this.gun
|
|
533
|
+
.get("users")
|
|
534
|
+
.map()
|
|
535
|
+
.once((userData, key) => {
|
|
536
|
+
if (!found && userData && userData.username === username) {
|
|
537
|
+
found = true;
|
|
538
|
+
clearTimeout(timeoutId);
|
|
539
|
+
resolve(userData);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
// Set a timeout to avoid hanging
|
|
543
|
+
timeoutId = setTimeout(checkComplete, 2000);
|
|
544
|
+
});
|
|
545
|
+
return existingUser;
|
|
546
|
+
}
|
|
547
|
+
catch (error) {
|
|
548
|
+
(0, logger_1.logError)(`Error checking username existence: ${error}`);
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
405
552
|
/**
|
|
406
553
|
* Logs in a user using direct Gun authentication
|
|
407
554
|
* @param username Username
|
|
@@ -412,6 +559,18 @@ class GunDB {
|
|
|
412
559
|
async login(username, password, callback) {
|
|
413
560
|
(0, logger_1.log)(`Attempting login for user: ${username}`);
|
|
414
561
|
try {
|
|
562
|
+
// First check if user exists in the system
|
|
563
|
+
const existingUser = await this.checkUsernameExists(username);
|
|
564
|
+
if (!existingUser) {
|
|
565
|
+
const result = {
|
|
566
|
+
success: false,
|
|
567
|
+
error: `User '${username}' not found. Please check your username or register first.`,
|
|
568
|
+
};
|
|
569
|
+
if (callback)
|
|
570
|
+
callback(result);
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
(0, logger_1.log)(`User ${username} found in system, attempting authentication...`);
|
|
415
574
|
// Authenticate with Gun directly
|
|
416
575
|
const authResult = await new Promise((resolve) => {
|
|
417
576
|
this.gun.user().auth(username, password, (ack) => {
|
|
@@ -432,6 +591,17 @@ class GunDB {
|
|
|
432
591
|
return result;
|
|
433
592
|
}
|
|
434
593
|
const userPub = this.gun.user().is?.pub;
|
|
594
|
+
// Verify that the logged-in user matches the expected user
|
|
595
|
+
if (userPub !== existingUser.pub) {
|
|
596
|
+
(0, logger_1.logError)(`Login pub mismatch: expected ${existingUser.pub}, got ${userPub}`);
|
|
597
|
+
const result = {
|
|
598
|
+
success: false,
|
|
599
|
+
error: "Authentication inconsistency detected. Please try again.",
|
|
600
|
+
};
|
|
601
|
+
if (callback)
|
|
602
|
+
callback(result);
|
|
603
|
+
return result;
|
|
604
|
+
}
|
|
435
605
|
// Update users collection if needed - improved null safety
|
|
436
606
|
try {
|
|
437
607
|
let userExists = false;
|
|
@@ -453,9 +623,14 @@ class GunDB {
|
|
|
453
623
|
const newUser = this.gun.get(userPub).put({
|
|
454
624
|
username: username,
|
|
455
625
|
pub: userPub,
|
|
626
|
+
lastLogin: Date.now(),
|
|
456
627
|
});
|
|
457
628
|
this.gun.get("users").set(newUser);
|
|
458
629
|
}
|
|
630
|
+
else if (userExists && userPub) {
|
|
631
|
+
// Update last login time
|
|
632
|
+
this.gun.get(userPub).get("lastLogin").put(Date.now());
|
|
633
|
+
}
|
|
459
634
|
}
|
|
460
635
|
catch (collectionError) {
|
|
461
636
|
// Log but don't fail the login for collection errors
|
|
@@ -482,13 +657,91 @@ class GunDB {
|
|
|
482
657
|
}
|
|
483
658
|
_savePair() {
|
|
484
659
|
try {
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
660
|
+
const user = this.gun.user();
|
|
661
|
+
const pair = user?._?.sea;
|
|
662
|
+
const userInfo = user?.is;
|
|
663
|
+
if (pair && userInfo && typeof localStorage !== "undefined") {
|
|
664
|
+
// Save the crypto pair
|
|
665
|
+
localStorage.setItem("gun/pair", JSON.stringify(pair));
|
|
666
|
+
// Save user session info
|
|
667
|
+
const sessionInfo = {
|
|
668
|
+
pub: userInfo.pub,
|
|
669
|
+
alias: userInfo.alias || "",
|
|
670
|
+
timestamp: Date.now(),
|
|
671
|
+
};
|
|
672
|
+
localStorage.setItem("gun/session", JSON.stringify(sessionInfo));
|
|
673
|
+
(0, logger_1.log)(`Session saved for user: ${userInfo.alias || userInfo.pub}`);
|
|
488
674
|
}
|
|
489
675
|
}
|
|
490
676
|
catch (error) {
|
|
491
|
-
|
|
677
|
+
(0, logger_1.logError)("Error saving auth pair and session:", error);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Attempts to restore user session from local storage
|
|
682
|
+
* @returns Promise resolving to session restoration result
|
|
683
|
+
*/
|
|
684
|
+
async restoreSession() {
|
|
685
|
+
try {
|
|
686
|
+
if (typeof localStorage === "undefined") {
|
|
687
|
+
return { success: false, error: "localStorage not available" };
|
|
688
|
+
}
|
|
689
|
+
const sessionInfo = localStorage.getItem("gun/session");
|
|
690
|
+
const pairInfo = localStorage.getItem("gun/pair");
|
|
691
|
+
if (!sessionInfo || !pairInfo) {
|
|
692
|
+
(0, logger_1.log)("No saved session found");
|
|
693
|
+
return { success: false, error: "No saved session" };
|
|
694
|
+
}
|
|
695
|
+
const session = JSON.parse(sessionInfo);
|
|
696
|
+
const pair = JSON.parse(pairInfo);
|
|
697
|
+
// Check if session is not too old (optional - you can adjust this)
|
|
698
|
+
const sessionAge = Date.now() - session.timestamp;
|
|
699
|
+
const maxSessionAge = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
700
|
+
if (sessionAge > maxSessionAge) {
|
|
701
|
+
(0, logger_1.log)("Session expired, clearing storage");
|
|
702
|
+
localStorage.removeItem("gun/session");
|
|
703
|
+
localStorage.removeItem("gun/pair");
|
|
704
|
+
return { success: false, error: "Session expired" };
|
|
705
|
+
}
|
|
706
|
+
(0, logger_1.log)(`Attempting to restore session for user: ${session.alias || session.pub}`);
|
|
707
|
+
// Try to restore the session with Gun
|
|
708
|
+
const user = this.gun.user();
|
|
709
|
+
// Set the pair directly
|
|
710
|
+
user._ = { sea: pair };
|
|
711
|
+
// Try to recall the session
|
|
712
|
+
const recallResult = await new Promise((resolve) => {
|
|
713
|
+
try {
|
|
714
|
+
user.recall({ sessionStorage: true }, (ack) => {
|
|
715
|
+
if (ack.err) {
|
|
716
|
+
(0, logger_1.logError)(`Session recall error: ${ack.err}`);
|
|
717
|
+
resolve(false);
|
|
718
|
+
}
|
|
719
|
+
else {
|
|
720
|
+
resolve(true);
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
catch (error) {
|
|
725
|
+
(0, logger_1.logError)(`Session recall exception: ${error}`);
|
|
726
|
+
resolve(false);
|
|
727
|
+
}
|
|
728
|
+
// Fallback timeout
|
|
729
|
+
setTimeout(() => resolve(false), 3000);
|
|
730
|
+
});
|
|
731
|
+
if (recallResult && user.is?.pub === session.pub) {
|
|
732
|
+
(0, logger_1.log)(`Session restored successfully for: ${session.alias || session.pub}`);
|
|
733
|
+
return { success: true, userPub: session.pub };
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
(0, logger_1.log)("Session restoration failed, clearing storage");
|
|
737
|
+
localStorage.removeItem("gun/session");
|
|
738
|
+
localStorage.removeItem("gun/pair");
|
|
739
|
+
return { success: false, error: "Session restoration failed" };
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
(0, logger_1.logError)(`Error restoring session: ${error}`);
|
|
744
|
+
return { success: false, error: String(error) };
|
|
492
745
|
}
|
|
493
746
|
}
|
|
494
747
|
/**
|
|
@@ -501,9 +754,26 @@ class GunDB {
|
|
|
501
754
|
(0, logger_1.log)("No user logged in, skipping logout");
|
|
502
755
|
return;
|
|
503
756
|
}
|
|
757
|
+
const currentUser = this.getCurrentUser();
|
|
758
|
+
(0, logger_1.log)(`Logging out user: ${currentUser?.pub || "unknown"}`);
|
|
504
759
|
// Direct logout using Gun
|
|
505
760
|
this.gun.user().leave();
|
|
506
|
-
|
|
761
|
+
// Clear local storage session data
|
|
762
|
+
if (typeof localStorage !== "undefined") {
|
|
763
|
+
localStorage.removeItem("gun/pair");
|
|
764
|
+
localStorage.removeItem("gun/session");
|
|
765
|
+
// Also clear old format for backward compatibility
|
|
766
|
+
localStorage.removeItem("pair");
|
|
767
|
+
(0, logger_1.log)("Local session data cleared");
|
|
768
|
+
}
|
|
769
|
+
// Clear sessionStorage as well
|
|
770
|
+
if (typeof sessionStorage !== "undefined") {
|
|
771
|
+
sessionStorage.removeItem("gun/");
|
|
772
|
+
sessionStorage.removeItem("gun/user");
|
|
773
|
+
sessionStorage.removeItem("gun/auth");
|
|
774
|
+
(0, logger_1.log)("Session storage cleared");
|
|
775
|
+
}
|
|
776
|
+
(0, logger_1.log)("Logout completed successfully");
|
|
507
777
|
}
|
|
508
778
|
catch (error) {
|
|
509
779
|
(0, logger_1.logError)("Error during logout:", error);
|
|
@@ -110,6 +110,12 @@ declare class GunDB {
|
|
|
110
110
|
* @returns Promise resolving to signup result
|
|
111
111
|
*/
|
|
112
112
|
signUp(username: string, password: string): Promise<any>;
|
|
113
|
+
/**
|
|
114
|
+
* Check if a username already exists in the system
|
|
115
|
+
* @param username Username to check
|
|
116
|
+
* @returns Promise resolving to user data if exists, null otherwise
|
|
117
|
+
*/
|
|
118
|
+
private checkUsernameExists;
|
|
113
119
|
/**
|
|
114
120
|
* Logs in a user using direct Gun authentication
|
|
115
121
|
* @param username Username
|
|
@@ -119,6 +125,15 @@ declare class GunDB {
|
|
|
119
125
|
*/
|
|
120
126
|
login(username: string, password: string, callback?: (result: any) => void): Promise<any>;
|
|
121
127
|
private _savePair;
|
|
128
|
+
/**
|
|
129
|
+
* Attempts to restore user session from local storage
|
|
130
|
+
* @returns Promise resolving to session restoration result
|
|
131
|
+
*/
|
|
132
|
+
restoreSession(): Promise<{
|
|
133
|
+
success: boolean;
|
|
134
|
+
userPub?: string;
|
|
135
|
+
error?: string;
|
|
136
|
+
}>;
|
|
122
137
|
/**
|
|
123
138
|
* Logs out the current user using direct Gun authentication
|
|
124
139
|
*/
|