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 user = this.gun.get(createResult.pub).put({
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
- try {
378
- const loginResult = await this.login(username, password);
379
- if (!loginResult.success) {
380
- (0, logger_1.logError)(`Login after registration failed: ${loginResult.error}`);
381
- return {
382
- success: false,
383
- error: `Registration completed but login failed: ${loginResult.error}`,
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 pair = this.gun.user()?._?.sea;
486
- if (pair && typeof localStorage !== "undefined") {
487
- localStorage.setItem("pair", JSON.stringify(pair));
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
- console.error("Error saving auth pair:", error);
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
- (0, logger_1.log)("Logout completed");
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
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shogun-core",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "description": "SHOGUN SDK - Core library for Shogun SDK",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",