mobbdev 1.2.35 → 1.2.38

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.
@@ -6700,9 +6700,19 @@ function isAuthError(error) {
6700
6700
  }
6701
6701
  return false;
6702
6702
  }
6703
- function isNetworkError(error) {
6703
+ function isTransientError(error) {
6704
6704
  const errorString = error?.toString() ?? "";
6705
- return errorString.includes("FetchError") || errorString.includes("TypeError") || errorString.includes("ECONNREFUSED") || errorString.includes("ENOTFOUND") || errorString.includes("ETIMEDOUT") || errorString.includes("UND_ERR");
6705
+ if (errorString.includes("FetchError") || errorString.includes("TypeError") || errorString.includes("ECONNREFUSED") || errorString.includes("ENOTFOUND") || errorString.includes("ETIMEDOUT") || errorString.includes("UND_ERR")) {
6706
+ return true;
6707
+ }
6708
+ if (error instanceof ClientError) {
6709
+ const status = error.response?.status;
6710
+ if (status && status >= 502 && status <= 504) return true;
6711
+ }
6712
+ if (errorString.includes("Gateway Time-out") || errorString.includes("Bad Gateway") || errorString.includes("Service Unavailable")) {
6713
+ return true;
6714
+ }
6715
+ return false;
6706
6716
  }
6707
6717
  var API_KEY_HEADER_NAME = "x-mobb-key";
6708
6718
  var REPORT_STATE_CHECK_DELAY = 5 * 1e3;
@@ -6763,8 +6773,8 @@ var GQLClient = class {
6763
6773
  try {
6764
6774
  await this.getUserInfo();
6765
6775
  } catch (e) {
6766
- if (isNetworkError(e)) {
6767
- debug7("verify connection failed (network error) %o", e);
6776
+ if (isTransientError(e)) {
6777
+ debug7("verify connection failed (transient error) %o", e);
6768
6778
  return false;
6769
6779
  }
6770
6780
  debug7("verify connection: endpoint reachable but request failed %o", e);
@@ -6785,11 +6795,7 @@ var GQLClient = class {
6785
6795
  debug7("verify token failed - auth error %o", e);
6786
6796
  return false;
6787
6797
  }
6788
- if (isNetworkError(e)) {
6789
- debug7("verify token failed - network error, rethrowing %o", e);
6790
- throw e;
6791
- }
6792
- debug7("verify token failed - unexpected error, rethrowing %o", e);
6798
+ debug7("verify token failed - transient/unknown error, rethrowing %o", e);
6793
6799
  throw e;
6794
6800
  }
6795
6801
  }
@@ -6846,11 +6852,7 @@ var GQLClient = class {
6846
6852
  return res?.cli_login_by_pk?.encryptedApiToken || null;
6847
6853
  }
6848
6854
  async createCommunityUser() {
6849
- try {
6850
- await this._clientSdk.CreateCommunityUser();
6851
- } catch (e) {
6852
- debug7("create community user failed %o", e);
6853
- }
6855
+ await this._clientSdk.CreateCommunityUser();
6854
6856
  }
6855
6857
  async updateScmToken(args) {
6856
6858
  const { scmType, url, token, org, refreshToken } = args;
@@ -7406,8 +7408,8 @@ var configStore = getConfigStore();
7406
7408
 
7407
7409
  // src/commands/AuthManager.ts
7408
7410
  var debug10 = Debug9("mobbdev:auth");
7409
- var LOGIN_MAX_WAIT = 10 * 60 * 1e3;
7410
- var LOGIN_CHECK_DELAY = 5 * 1e3;
7411
+ var LOGIN_MAX_WAIT = 2 * 60 * 1e3;
7412
+ var LOGIN_CHECK_DELAY = 2 * 1e3;
7411
7413
  var _AuthManager = class _AuthManager {
7412
7414
  constructor(webAppUrl, apiUrl) {
7413
7415
  __publicField(this, "publicKey");
@@ -7421,22 +7423,33 @@ var _AuthManager = class _AuthManager {
7421
7423
  this.resolvedWebAppUrl = webAppUrl || WEB_APP_URL;
7422
7424
  this.resolvedApiUrl = apiUrl || API_URL;
7423
7425
  }
7426
+ /** Set the minimum interval between browser opens (MCP sets this to 24h) */
7427
+ static setBrowserCooldown(ms) {
7428
+ _AuthManager.browserCooldownMs = ms;
7429
+ }
7430
+ /** Reset cooldown state. Used by tests to ensure isolation. */
7431
+ static resetCooldown() {
7432
+ _AuthManager.lastBrowserOpenTime = 0;
7433
+ _AuthManager.browserCooldownMs = 0;
7434
+ }
7424
7435
  openUrlInBrowser() {
7425
- if (this.currentBrowserUrl) {
7426
- open(this.currentBrowserUrl);
7427
- return true;
7436
+ if (!this.currentBrowserUrl) {
7437
+ return false;
7428
7438
  }
7429
- return false;
7439
+ if (_AuthManager.browserCooldownMs > 0 && Date.now() - _AuthManager.lastBrowserOpenTime < _AuthManager.browserCooldownMs) {
7440
+ debug10("browser cooldown active, skipping open");
7441
+ return false;
7442
+ }
7443
+ open(this.currentBrowserUrl);
7444
+ _AuthManager.lastBrowserOpenTime = Date.now();
7445
+ return true;
7430
7446
  }
7447
+ /**
7448
+ * Polls for the encrypted API token, decrypts it, validates it, and stores it.
7449
+ * Returns true if login succeeded.
7450
+ */
7431
7451
  async waitForAuthentication() {
7432
- let newApiToken = null;
7433
- for (let i = 0; i < _AuthManager.loginMaxWait / LOGIN_CHECK_DELAY; i++) {
7434
- newApiToken = await this.getApiToken();
7435
- if (newApiToken) {
7436
- break;
7437
- }
7438
- await sleep(LOGIN_CHECK_DELAY);
7439
- }
7452
+ const newApiToken = await this.waitForApiToken();
7440
7453
  if (!newApiToken) {
7441
7454
  return false;
7442
7455
  }
@@ -7454,52 +7467,75 @@ var _AuthManager = class _AuthManager {
7454
7467
  return false;
7455
7468
  }
7456
7469
  /**
7457
- * Checks if the user is already authenticated
7470
+ * Polls for the encrypted API token and decrypts it.
7471
+ * Does NOT validate or store the token — use this when the caller
7472
+ * needs to validate on a specific client type (e.g. McpGQLClient).
7473
+ */
7474
+ async waitForApiToken() {
7475
+ for (let i = 0; i < _AuthManager.loginMaxWait / LOGIN_CHECK_DELAY; i++) {
7476
+ const token = await this.getApiToken();
7477
+ if (token) {
7478
+ return token;
7479
+ }
7480
+ await sleep(LOGIN_CHECK_DELAY);
7481
+ }
7482
+ return null;
7483
+ }
7484
+ /**
7485
+ * Checks if the user is already authenticated.
7486
+ * Returns the full AuthResult so callers can distinguish 'invalid' from 'unknown'.
7458
7487
  */
7459
7488
  async isAuthenticated() {
7460
7489
  if (this.authenticated === null) {
7461
7490
  const result = await this.checkAuthentication();
7462
7491
  this.authenticated = result.isAuthenticated;
7463
7492
  if (!result.isAuthenticated) {
7464
- debug10("isAuthenticated: false \u2014 %s", result.message);
7493
+ debug10("isAuthenticated: false \u2014 %s (%s)", result.message, result.reason);
7465
7494
  }
7466
7495
  }
7467
7496
  return this.authenticated;
7468
7497
  }
7469
7498
  /**
7470
- * Private function to check if the user is authenticated with the server
7499
+ * Full 3-state auth check returning an {@link AuthResult}.
7500
+ *
7501
+ * - `isAuthenticated: true` — token is valid.
7502
+ * - `reason: 'invalid'` — definitive auth failure (access-denied). Caller should trigger login.
7503
+ * - `reason: 'unknown'` — transient/network error. Caller should NOT trigger login.
7471
7504
  */
7472
7505
  async checkAuthentication(apiKey) {
7506
+ if (!this.gqlClient) {
7507
+ this.gqlClient = this.getGQLClient(apiKey);
7508
+ }
7509
+ const isConnected = await this.gqlClient.verifyApiConnection();
7510
+ if (!isConnected) {
7511
+ return {
7512
+ isAuthenticated: false,
7513
+ reason: "unknown",
7514
+ message: "Failed to connect to Mobb server"
7515
+ };
7516
+ }
7473
7517
  try {
7474
- if (!this.gqlClient) {
7475
- this.gqlClient = this.getGQLClient(apiKey);
7476
- }
7477
- const isConnected = await this.gqlClient.verifyApiConnection();
7478
- if (!isConnected) {
7479
- return {
7480
- isAuthenticated: false,
7481
- message: "Failed to connect to Mobb server"
7482
- };
7483
- }
7484
7518
  const userVerify = await this.gqlClient.validateUserToken();
7485
7519
  if (!userVerify) {
7486
7520
  return {
7487
7521
  isAuthenticated: false,
7522
+ reason: "invalid",
7488
7523
  message: "User token validation failed"
7489
7524
  };
7490
7525
  }
7491
7526
  } catch (error) {
7492
7527
  return {
7493
7528
  isAuthenticated: false,
7529
+ reason: "unknown",
7494
7530
  message: error instanceof Error ? error.message : "Unknown authentication error"
7495
7531
  };
7496
7532
  }
7497
- return { isAuthenticated: true, message: "Successfully authenticated" };
7533
+ return { isAuthenticated: true };
7498
7534
  }
7499
7535
  /**
7500
7536
  * Generates a login URL for manual authentication
7501
7537
  */
7502
- async generateLoginUrl(loginContext) {
7538
+ async generateLoginUrl(loginPath, loginContext) {
7503
7539
  try {
7504
7540
  if (!this.gqlClient) {
7505
7541
  this.gqlClient = this.getGQLClient();
@@ -7512,7 +7548,7 @@ var _AuthManager = class _AuthManager {
7512
7548
  this.loginId = await this.gqlClient.createCliLogin({
7513
7549
  publicKey: this.publicKey.export({ format: "pem", type: "pkcs1" }).toString()
7514
7550
  });
7515
- const webLoginUrl = `${this.resolvedWebAppUrl}/cli-login`;
7551
+ const webLoginUrl = `${this.resolvedWebAppUrl}${loginPath || "/cli-login"}`;
7516
7552
  const browserUrl = loginContext ? buildLoginUrl(webLoginUrl, this.loginId, os.hostname(), loginContext) : `${webLoginUrl}/${this.loginId}?hostname=${os.hostname()}`;
7517
7553
  this.currentBrowserUrl = browserUrl;
7518
7554
  return browserUrl;
@@ -7522,22 +7558,32 @@ var _AuthManager = class _AuthManager {
7522
7558
  }
7523
7559
  }
7524
7560
  /**
7525
- * Retrieves and decrypts the API token after authentication
7561
+ * Retrieves and decrypts the API token after authentication.
7562
+ * Returns null if the token is not yet available or on transient errors.
7526
7563
  */
7527
7564
  async getApiToken() {
7528
7565
  if (!this.gqlClient || !this.loginId || !this.privateKey) {
7529
7566
  return null;
7530
7567
  }
7531
- const encryptedApiToken = await this.gqlClient.getEncryptedApiToken({
7532
- loginId: this.loginId
7533
- });
7534
- if (encryptedApiToken) {
7535
- return crypto.privateDecrypt(
7536
- this.privateKey,
7537
- Buffer.from(encryptedApiToken, "base64")
7538
- ).toString("utf-8");
7568
+ try {
7569
+ const encryptedApiToken = await this.gqlClient.getEncryptedApiToken({
7570
+ loginId: this.loginId
7571
+ });
7572
+ if (encryptedApiToken) {
7573
+ return crypto.privateDecrypt(
7574
+ this.privateKey,
7575
+ Buffer.from(encryptedApiToken, "base64")
7576
+ ).toString("utf-8");
7577
+ }
7578
+ return null;
7579
+ } catch (error) {
7580
+ if (isTransientError(error)) {
7581
+ debug10("getApiToken: transient error, will retry");
7582
+ } else {
7583
+ debug10("getApiToken: unexpected error: %O", error);
7584
+ }
7585
+ return null;
7539
7586
  }
7540
- return null;
7541
7587
  }
7542
7588
  /**
7543
7589
  * Returns true if a non-empty API token is stored in the configStore.
@@ -7581,12 +7627,13 @@ var _AuthManager = class _AuthManager {
7581
7627
  };
7582
7628
  /** Maximum time (ms) to wait for login authentication. Override in tests for faster failures. */
7583
7629
  __publicField(_AuthManager, "loginMaxWait", LOGIN_MAX_WAIT);
7630
+ /** Browser cooldown: minimum ms between browser opens (0 = no cooldown) */
7631
+ __publicField(_AuthManager, "browserCooldownMs", 0);
7632
+ __publicField(_AuthManager, "lastBrowserOpenTime", 0);
7584
7633
  var AuthManager = _AuthManager;
7585
7634
 
7586
7635
  // src/commands/handleMobbLogin.ts
7587
7636
  var debug11 = Debug10("mobbdev:commands");
7588
- var LOGIN_MAX_WAIT2 = 10 * 60 * 1e3;
7589
- var LOGIN_CHECK_DELAY2 = 5 * 1e3;
7590
7637
  var MOBB_LOGIN_REQUIRED_MSG = `\u{1F513} Login to Mobb is Required, you will be redirected to our login page, once the authorization is complete return to this prompt, ${chalk2.bgBlue(
7591
7638
  "press any key to continue"
7592
7639
  )};`;
@@ -7617,7 +7664,8 @@ async function handleMobbLogin({
7617
7664
  skipPrompts,
7618
7665
  apiUrl,
7619
7666
  webAppUrl,
7620
- loginContext
7667
+ loginContext,
7668
+ loginPath
7621
7669
  }) {
7622
7670
  debug11(
7623
7671
  "handleMobbLogin: resolved URLs - apiUrl=%s (from param: %s), webAppUrl=%s (from param: %s)",
@@ -7629,16 +7677,16 @@ async function handleMobbLogin({
7629
7677
  const { createSpinner } = Spinner({ ci: skipPrompts });
7630
7678
  const authManager = new AuthManager(webAppUrl, apiUrl);
7631
7679
  authManager.setGQLClient(inGqlClient);
7632
- try {
7633
- const isAuthenticated = await authManager.isAuthenticated();
7634
- if (isAuthenticated) {
7635
- createSpinner().start().success({
7636
- text: `\u{1F513} Login to Mobb succeeded. Already authenticated`
7637
- });
7638
- return authManager.getGQLClient();
7639
- }
7640
- } catch (error) {
7641
- debug11("Authentication check failed:", error);
7680
+ const authResult = await authManager.checkAuthentication();
7681
+ if (authResult.isAuthenticated) {
7682
+ createSpinner().start().success({
7683
+ text: `\u{1F513} Login to Mobb succeeded. Already authenticated`
7684
+ });
7685
+ return authManager.getGQLClient();
7686
+ }
7687
+ if (authResult.reason === "unknown") {
7688
+ debug11("Auth check returned unknown: %s", authResult.message);
7689
+ throw new CliError(`Cannot verify authentication: ${authResult.message}`);
7642
7690
  }
7643
7691
  if (apiKey) {
7644
7692
  createSpinner().start().error({
@@ -7657,7 +7705,7 @@ async function handleMobbLogin({
7657
7705
  text: "\u{1F513} Waiting for Mobb login..."
7658
7706
  });
7659
7707
  try {
7660
- const loginUrl = await authManager.generateLoginUrl(loginContext);
7708
+ const loginUrl = await authManager.generateLoginUrl(loginPath, loginContext);
7661
7709
  if (!loginUrl) {
7662
7710
  loginSpinner.error({
7663
7711
  text: "Failed to generate login URL"