Create and return a Meteor method promise


#1

I have a server side method for authenticating against a Microsoft Active Directory server using LDAP. This works nicely, but I can’t get the result back to the client - apparently the bind call is async and hence I am returning before the response is back. So as I understand Meteor methods, I should simply return a Promise and that will then be resolved by the Meteor call. How to do that?

if (Meteor.isServer) {
  Meteor.methods({
    /**
     * LDAP authentication method.
     *
     * @param  {String}  url LDAP server url.
     * @param  {String}  dn  distinguished name.
     * @param  {String}  email LDAP binddn.
     * @param  {String}  password LDAP password.
     */
    'ldap.authenticate': (url, dn, email, password) => {
      const client = ldap.createClient({ url });

      client.bind(email, password, (err, res) => {
        if (err) {
          console.log(err);
        }

        console.log(res);

        return [err, res];
      });
    },
  });
}

#2

Hi @maasha,

You can try something like this

if (Meteor.isServer) {
  Meteor.methods({
    /**
     * LDAP authentication method.
     *
     * @param  {String}  url LDAP server url.
     * @param  {String}  dn  distinguished name.
     * @param  {String}  email LDAP binddn.
     * @param  {String}  password LDAP password.
     */
    'ldap.authenticate': async (url, dn, email, password) => {
      return await clientBind(url, email, password);
    },
  });
}

async function clientBind(url, email, password) {
  const client = ldap.createClient({ url });

  return new Promise((resolve, reject) => {
    client.bind(email, password, (error, res) => {
      if (error) {
        reject(error);
      } else {
        resolve(res);
      }
    });
  });
}

If that doesnt work you can use return Promise.await(clientBind(url, email, password))


#3

So this both suggestions yield:

W20181207-15:47:36.517(1)? (STDERR) (node:28423) UnhandledPromiseRejectionWarning: RangeError: Maximum call stack size exceeded
W20181207-15:47:36.569(1)? (STDERR)     at Object.keys.forEach.key (packages/ejson/ejson.js:594:27)
W20181207-15:47:36.569(1)? (STDERR)     at Array.forEach (<anonymous>)
W20181207-15:47:36.569(1)? (STDERR)     at Object.EJSON.clone.v [as clone] (packages/ejson/ejson.js:594:18)
W20181207-15:47:36.570(1)? (STDERR)     at Object.keys.forEach.key (packages/ejson/ejson.js:595:22)
W20181207-15:47:36.570(1)? (STDERR)     at Array.forEach (<anonymous>)
W20181207-15:47:36.570(1)? (STDERR)     at Object.EJSON.clone.v [as clone] (packages/ejson/ejson.js:594:18)
W20181207-15:47:36.570(1)? (STDERR)     at Object.keys.forEach.key (packages/ejson/ejson.js:595:22)
W20181207-15:47:36.570(1)? (STDERR)     at Array.forEach (<anonymous>)
W20181207-15:47:36.570(1)? (STDERR)     at Object.EJSON.clone.v [as clone] (packages/ejson/ejson.js:594:18)
W20181207-15:47:36.570(1)? (STDERR)     at Object.keys.forEach.key (packages/ejson/ejson.js:595:22)
W20181207-15:47:36.570(1)? (STDERR)     at Array.forEach (<anonymous>)
W20181207-15:47:36.571(1)? (STDERR)     at Object.EJSON.clone.v [as clone] (packages/ejson/ejson.js:594:18)
W20181207-15:47:36.571(1)? (STDERR)     at Object.keys.forEach.key (packages/ejson/ejson.js:595:22)
W20181207-15:47:36.571(1)? (STDERR)     at Array.forEach (<anonymous>)
W20181207-15:47:36.571(1)? (STDERR)     at Object.EJSON.clone.v [as clone] (packages/ejson/ejson.js:594:18)
W20181207-15:47:36.571(1)? (STDERR)     at Object.keys.forEach.key (packages/ejson/ejson.js:595:22)
W20181207-15:47:36.571(1)? (STDERR) (node:28423) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
W20181207-15:47:36.571(1)? (STDERR) (node:28423) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

#4

You don’t need to wrap this in a Promise and use async/await.

You can just use Meteor.wrapAsync:

Meteor.methods({
  /**
   * LDAP authentication method.
   *
   * @param  {String}  url LDAP server url.
   * @param  {String}  dn  distinguished name.
   * @param  {String}  email LDAP binddn.
   * @param  {String}  password LDAP password.
   */
  'ldap.authenticate'(url, dn, email, password) {
    const client = ldap.createClient({ url });
    const clientBind = Meteor.wrapAsync(client.bind, client);
    try {
      const res = clientBind(email, password);
      console.log(res);
      return res;
    } catch (err) {
      throw new Meteor.Error('xxx', 'yyy');
    }
  }
});

#5

OK, now I get the BindResponse printed to console server side, but then I get:

W20181207-16:21:21.565(1)? (STDERR) (node:28726) UnhandledPromiseRejectionWarning: RangeError: Maximum call stack size exceeded
W20181207-16:21:21.565(1)? (STDERR)     at Object.keys.forEach.key (packages/ejson/ejson.js:594:27)
W20181207-16:21:21.565(1)? (STDERR)     at Array.forEach (<anonymous>)
W20181207-16:21:21.565(1)? (STDERR)     at Object.EJSON.clone.v [as clone] (packages/ejson/ejson.js:594:18)
W20181207-16:21:21.565(1)? (STDERR)     at Object.keys.forEach.key (packages/ejson/ejson.js:595:22)
W20181207-16:21:21.566(1)? (STDERR)     at Array.forEach (<anonymous>)
W20181207-16:21:21.566(1)? (STDERR)     at Object.EJSON.clone.v [as clone] (packages/ejson/ejson.js:594:18)
W20181207-16:21:21.566(1)? (STDERR)     at Object.keys.forEach.key (packages/ejson/ejson.js:595:22)
W20181207-16:21:21.566(1)? (STDERR)     at Array.forEach (<anonymous>)
W20181207-16:21:21.566(1)? (STDERR)     at Object.EJSON.clone.v [as clone] (packages/ejson/ejson.js:594:18)
W20181207-16:21:21.566(1)? (STDERR)     at Object.keys.forEach.key (packages/ejson/ejson.js:595:22)
W20181207-16:21:21.566(1)? (STDERR)     at Array.forEach (<anonymous>)
W20181207-16:21:21.566(1)? (STDERR)     at Object.EJSON.clone.v [as clone] (packages/ejson/ejson.js:594:18)
W20181207-16:21:21.567(1)? (STDERR)     at Object.keys.forEach.key (packages/ejson/ejson.js:595:22)
W20181207-16:21:21.567(1)? (STDERR)     at Array.forEach (<anonymous>)
W20181207-16:21:21.567(1)? (STDERR)     at Object.EJSON.clone.v [as clone] (packages/ejson/ejson.js:594:18)
W20181207-16:21:21.567(1)? (STDERR)     at Object.keys.forEach.key (packages/ejson/ejson.js:595:22)
W20181207-16:21:21.567(1)? (STDERR) (node:28726) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 5)

Client:

handleSubmit(event) {
    const that = this;
    event.preventDefault();
    const email = document.getElementById('login-email').value;
    const password = document.getElementById('login-password').value;

    Meteor.call(
      'ldap.authenticate',
      '<url>',
      '<dn>',
      email,
      password,
      (err, res) => {
        if (err) {
          that.setState({ error: err });
          that.setState({ authenticated: false });
          throw new Error('ldap.authenticate failed');
        }

        console.log('ldap.authenticate', res);

        that.setState({ authenticated: true });
      },
    );
}

Server:

if (Meteor.isServer) {
  Meteor.methods({
    /**
     * LDAP authentication method.
     *
     * @param  {String}  url LDAP server url.
     * @param  {String}  dn  distinguished name.
     * @param  {String}  email LDAP binddn.
     * @param  {String}  password LDAP password.
     */
    'ldap.authenticate': (url, dn, email, password) => {
      const client = ldap.createClient({ url });
      const clientBind = Meteor.wrapAsync(client.bind, client);
      try {
        const res = clientBind(email, password);
        console.log(res);
        return res;
      } catch (err) {
        throw new Meteor.Error('xxx', 'yyy');
      }
    },
  });
}

#6

You have something in an infinite loop.

Also, you should not use fat arrow syntax for Meteor methods (I don’t think that’s causing your problem, though).

Replace

'ldap.authenticate': (url, dn, email, password) => {

with

'ldap.authenticate'(url, dn, email, password) {

#7

OK, now using object shorthand instead of fat arrow syntax. If the credentials are bad I get the expected error. If the credentials are good, I get the pesky loop.