Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalid FileMaker Data API token when using multiple clients #114

Open
jeremydrichardson opened this issue Apr 28, 2021 · 1 comment
Open

Comments

@jeremydrichardson
Copy link

Describe the bug
When creating 2 separate fms-api-client clients for different files, and making a single async calls with each client, the second call will fail.

Expected behavior
It's unexpected that completely separate clients would affect each other. It seems as though there is some sort of collision between the clients that is causing the wrong token to be used.

Code Examples

const { Filemaker } = require("fms-api-client");
const { connect } = require("marpat");
require("dotenv").config({ path: "./.env" });

connect("nedb://memory").then(() => {

  const fmsApiClient1 = Filemaker.create({
    name: `client-database1`,
    database: "database1",
    concurrency: 30,
    server: `${process.env.URL}`,
    user: `${process.env.USERNAME}`,
    password: `${process.env.PASSWORD}`,
    usage: true,
    timeout: 60000,
  });
  const client1 = fmsApiClient1.save();

  const fmsApiClient2 = Filemaker.create({
    name: `client-database2`,
    database: "database2",
    concurrency: 30,
    server: `${process.env.URL}`,
    user: `${process.env.USERNAME}`,
    password: `${process.env.PASSWORD}`,
    usage: true,
    timeout: 60000,
  });
  const client2 = fmsApiClient2.save();

  Promise.all([client1, client2])
    .then(([db1Client, db2Client]) => {
      db1Client.layout("REST_Value_Lists")
        .then((result) => {
          console.log("Result of db1Client");
        })
        .catch((err) => {
          console.log("Error of db1Client");
        });

      db2Client.layout("REST_Value_Lists")
        .then((result) => {
          console.log("Result of db2Client");
        })
        .catch((err) => {
          console.log("Error of db2Client");
        });
    })
});

Additional context
I'm not sure really where to look on this one. Any help knowing where to look would be great. Have VS Code debugger hooked up but still not being able to really track what it going on.

How does fms-api-client decide when to look for a new token. Seems to be in the agent.model file but can't track it down.

Thanks

@jeremydrichardson
Copy link
Author

Finally figured this one out!!

The issue is that in the agent model, the axios instance is being shared by every client. There is some race condition where another request is made before the previous request promise has resolved.

The fix is to create an instance that is local to the agent class and call that instead of the global axios instance.

Here is our code that extends the Client model and adds the localized axios instance to the agent and replaces the agent request method with one that uses this.instance instead of just instance.

I'll see if I can make a pull request that would fix it in the agent model. The tricky part is making sure it can accomodate an external agent.

class newFilemaker extends Filemaker {
  constructor() {
    super();
  }

  preInit(data) {
    super.preInit(data);

    this.agent.instance = axios.create();
    axiosCookieJarSupport(this.agent.instance);

    this.agent.request = modifiedRequest;
  }
}

function modifiedRequest(data, parameters = {}) {
  console.log("inside modified request");
  const id = uuidv4();
  const interceptor = this.instance.interceptors.request.use(
    ({ httpAgent, httpsAgent, ...request }) => {
      this.instance.interceptors.request.eject(interceptor);
      return new Promise((resolve, reject) =>
        this.push({
          request: this.handleRequest(request, id),
          resolve,
          reject,
        })
      );
    }
  );

  const response = this.instance.interceptors.response.use(
    (response) => {
      this.instance.interceptors.response.eject(response);
      return this.handleResponse(response, id);
    },
    (error) => {
      this.instance.interceptors.response.eject(response);
      return this.handleError(error, id);
    }
  );

  return this.instance(
    Object.assign(
      data,
      this.timeout ? { timeout: this.timeout } : {},
      _.isEmpty(this.proxy) ? {} : { proxy: this.proxy },
      _.isEmpty(this.agent) ? {} : this.localize(),
      parameters.request || {}
    )
  );
} 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant