0

I'm trying to hit my Appsync GraphQL endpoint with a plain nodejs client that signs and sends the requests. There it is:

var https = require("https");
let AWS = require("aws-sdk");
let urlParse = require("url").URL;
require("dotenv").config();

const throwOnErrors = ({ query, variables, errors }) => {
  if (errors) {
    const errorMessage = `
        query: ${query.substr(0, 100)}

        variables: ${JSON.stringify(variables, null, 4)}

        error: ${JSON.stringify(errors, null, 4)}
        `;
    throw new Error(errorMessage);
  }
};

module.exports = async (
  url,
  credentials,
  query,
  operationName,
  variables = {}
) => {
  
  AWS.config.update({
    region: process.env.AWS_REGION,
    credentials: new AWS.Credentials(
      credentials.accessKeyId,
      credentials.secretAccessKey,
      credentials.sessionToken
    ),
  });

  let endpoint = new urlParse(url).hostname.toString();
  const item = {
    input: variables
  };
  let req = new AWS.HttpRequest(url, process.env.AWS_REGION);
  req.method = "POST";
  req.headers.host = endpoint;
  req.headers["Content-Type"] = "application/json";
  req.body = JSON.stringify({
    query: query,  
    operationName: operationName,
    variables: item
  });

  let signer = new AWS.Signers.V4(req, "appsync", true);
  signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate());

  const data = await new Promise((resolve, reject) => {
    const httpRequest = https.request({ ...req, host: endpoint }, (result) => {
      result.on("data", (data) => {
        
        resultdata = JSON.parse(data.toString());
        if (resultdata.errors != null) {
          throwOnErrors(query, variables, data);
          reject(resultdata);
        } else {
          resolve(resultdata);
        }
      });

      result.on("errors", (data) => {
        reject(() => {
          throwOnErrors(query, variables, data)
          return JSON.parse(data.toString())
        });
      });
    });
    
    httpRequest.write(req.body);
    httpRequest.end();
  });

  return data.data
};

Unfortunately, sometimes, the response does get rejected and the error is always something like this:

SyntaxError: Unexpected token , in JSON at position 0
        at JSON.parse (<anonymous>)

      56 |       result.on("data", (data) => {
      57 |         
    > 58 |         resultdata = JSON.parse(data.toString());
         |                           ^
      59 |         if (resultdata.errors != null) {
      60 |           throwOnErrors(query, variables, data);
      61 |           reject(resultdata);

      at IncomingMessage.<anonymous> (_tests/lib/graphql.js:58:27)

I really have no clue on how to solve this, because sometimes the promise gets resolved and sometimes not.

Do you guys have any tips for me?

Thanks in advance!

1 Answers1

0

Your 'data' callback can run many times, result is a stream, so

const httpRequest = https.request({ ...req, host: endpoint }, (result) => {
  result.on("data", (data) => {
    
    resultdata = JSON.parse(data.toString());
    if (resultdata.errors != null) {
      throwOnErrors(query, variables, data);
      reject(resultdata);
    } else {
      resolve(resultdata);
    }
  });

  result.on("errors", (data) => {
    reject(() => {
      throwOnErrors(query, variables, data)
      return JSON.parse(data.toString())
    });
  });
});

needs to be more like

const httpRequest = https.request({ ...req, host: endpoint }, (response) => {
  const chunks = [];
  response.on("data", (data) => { chunks.push(data); });
  response.on("end", () => {        
    resultdata = JSON.parse(Buffer.concat(chunks).toString());
    if (resultdata.errors != null) {
      throwOnErrors(query, variables, data);
      reject(resultdata);
    } else {
      resolve(resultdata);
    }
  });
  response.on("error", err => {
    reject(() => {
      throwOnErrors(query, variables, data)
      return JSON.parse(data.toString())
    });
  });
});
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251