ngrok / ngrok-javascript

Embed ngrok secure ingress into your Node.js apps with a single line of code.
https://ngrok.com
Apache License 2.0
86 stars 17 forks source link

SessionBuilder listener not close session #118

Closed twocolors closed 4 months ago

twocolors commented 4 months ago
const ngrok = require('@ngrok/ngrok');

(async function () {
  const session = await new ngrok.SessionBuilder()
    .authtoken('***')
    .serverAddr('connect.eu.ngrok-agent.com:443')
    .connect();

  const listener = await session.httpEndpoint().listenAndForward('http://localhost:8080');

  console.log(`Ingress established at: ${listener.url()}`);
  console.log(`close`);
  await listener.close();
  await session.close();
  // default heartbeat 10s
  console.log(`timeout 5s`);
  await timeout(5 * 1000);
  console.log(`new session 5s`);
  const session_1 = await new ngrok.SessionBuilder()
    .authtoken('***')
    .serverAddr('connect.eu.ngrok-agent.com:443')
    .connect();
})();

function timeout(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

and error

Ingress established at: https://aa5a-***-***-***-246.ngrok-free.app
close
timeout 5s
new session 5s
node:internal/process/promises:288
            triggerUncaughtException(err, true /* fromPromise */);
            ^

[Error: failed to connect session: Your account is limited to 1 simultaneous ngrok agent session.
You can run multiple tunnels on a single agent session using a configuration file.
To learn more, see https://ngrok.com/docs/secure-tunnels/ngrok-agent/reference/config/
Active ngrok agent sessions in region 'eu':
  - ts_2c46Kcqe1Gxr8F7oQqFgHLTxzfS (********) error_code: ERR_NGROK_108] {
  code: 'GenericFailure',
  errorCode: 'ERR_NGROK_108'
}

Node.js v18.7.0
CK-Ward commented 4 months ago

Hi @twocolors, thanks for your submission. It looks like you're on a free account. Are you able to verify in the dashboard that you have no other agents running?

twocolors commented 4 months ago

I have no other agents runners , this example work normal

(async function () {
  const listener = await ngrok.forward({ addr: 8080, authtoken: '***' });
  console.log(`Ingress established at: ${listener.url()}`);
  console.log(`close`);
  await listener.close();
  console.log(`timeout 5s`);
  await timeout(5 * 1000);
  const listener_1 = await ngrok.forward({ addr: 8080, authtoken: '***' });
  console.log(`Ingress established at: ${listener_1.url()}`);
  console.log(`close1`);
  await listener_1.close();
})();

function timeout(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

out

Ingress established at: https://8b25-***-***-49-246.ngrok-free.app
close
timeout 5s
Ingress established at: https://08c0-***-***-49-246.ngrok-free.app
close1
twocolors commented 4 months ago

and if for SessionBuilder and handleStopCommand this handle not call when send close on listener or session

bobzilladev commented 4 months ago

Hello, thanks again for writing in. The session close can take a bit longer, since that is a request to have the server side close the connection. You are right that the associated callback, handleDisconnection in this case, does not get called since the session has already been requested to close, that's something we can look into.

The following code relies on logging, so would be a bit brittle for production, but illustrates the session close will asynchronously complete:

const ngrok = require('@ngrok/ngrok');

var connected = false;
ngrok.loggingCallback(function (level, target, message) {
  console.log(`${level} ${target} - ${message}`);
  if (message.includes("reconnect failed")) {
    // the read side of the connection is now closed
    connected = false;
  }
}, "DEBUG");

(async function () {
  var session = await new ngrok.SessionBuilder().authtokenFromEnv()
    .serverAddr('connect.eu.ngrok-agent.com:443').connect();
  connected = true;

  const listener = await session.httpEndpoint().listenAndForward('http://localhost:8080');

  console.log(`Ingress established at: ${listener.url()}`);

  console.log(`timeout 2s`);
  await timeout(2 * 1000);
  await listener.close();
  await session.close();

  while (connected) {
    console.log(`timeout 5s`);
    await timeout(5 * 1000);
  }

  console.log(`new session`); 
  const session_1 = await new ngrok.SessionBuilder().authtokenFromEnv()
    .serverAddr('connect.eu.ngrok-agent.com:443').connect();

  await timeout(300 * 1000);
})();

function timeout(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
twocolors commented 4 months ago

why then ngrok.forward close session(connect) in monent ? and if set heartbeatInterval in 5 sec , session on SessionBuilder wil close faster. i think in your example close session in timeout(heartbeatInterval)

bobzilladev commented 4 months ago

Sure, let me answer each of those:

why then ngrok.forward close session(connect) in monent ?

In the code above that uses ngrok.forward, it is closing just the listener (await listener.close()), but the session is still open. The second call to ngrok.forward re-uses the still established session to create the new listener on. Multiple listeners on the same session is a common pattern.

if set heartbeatInterval in 5 sec , session on SessionBuilder wil close faster

The smallest heartbeatInterval can be set to is 10 seconds, that doesn't appear to affect session close time though. heartbeatTolerance also does not change the timing.

twocolors commented 4 months ago

yep, you right. thx what ~time need to close session on server?

bobzilladev commented 4 months ago

No problem! I was seeing around 25 seconds when adding timing output to that illustration code.

twocolors commented 4 months ago

thx for help