0

I am using a Javascript library called ATS (Authenticated Traffic Solution). I am not sure if this is the right title for my issue but I am getting an unexpected value from a method returning either a promise or a concrete value if a callback is defined.

The method is the following:

ats.retrieveEnvelope(callback);

and from its docs

Fetch the envelope from configured storage; the callback function is optional. If the function is called without a callback, a promise will be returned. If the function is called with a callback, an envelope value will be returned.

Also from the docs I know that the returned envelope should be of the form:

{
   "envelope:"sdfasdfasdfa"
}

This is how I am using it in my code then


updateSomething(): Promise<void> {
    return new Promise<void>((resolve, reject) => {
       this.loadEnvelope()
          .then(updatedOrNewEnvelopeWrapper => {
            this.sendSync(updatedOrNewEnvelopeWrapper);
            resolve();
          })
          .catch(error => {
            console.error(`Error while retrieval ${error}`);
            reject(error);
          });
    });
  }
 private loadEnvelope(): Promise<string> {
    return retryPromise<string>(
      () =>
        new Promise<string>((resolve, reject) => {
          if (this.ATSOnPage()) {
            try {
              resolve(this.getFromATS());
            } catch (error) {
              console.error('Some error');
              reject(error);
            }
          } else {
            reject(new Error('Some other error'));
          }
        }),
      this.maxATSRetrievalRetries,
      this.retryDelay
    );
  }

  private getFromATS(): string {
    return this._window.ats.retrieveEnvelope(function (envelope: string) {
      return JSON.parse(envelope).envelope;
    });
  }

The value of updatedOrNewEnvelopeWrapper is always

{
   "envelope": "fasdfasdf"
}

while I would expect for the JSON to have been successfully parsed under getFromATS, so updatedOrNewEnvelopeWrapper would have been just the string fasdfasdf.

I changed the getFromATS like so:

  private getFromATS(): string {
    return this._window.ats.retrieveEnvelope(function (envelope: string) {
      console.log('Located ATS.js');
      var parsed = JSON.parse(envelope);
      while ('envelope' in parsed && typeof parsed.envelope == 'object') {
        parsed = parsed.envelope;
      }

      if ('envelope' in parsed && typeof parsed.envelope == 'string') {
        if (parsed.envelope.includes('envelope')) {
          console.log(`JSON string is ${parsed.envelope}`);
          const jsonString = JSON.parse(parsed.envelope);
          return jsonString.envelope;
        } else return parsed.envelope;
      } else throw new Error('Malformed ATS response');
    });
  }
}

so that it can check arbitrarily deep in the json struct to find the final envelope value, since I thought that maybe the return value would be

{
  "envelope": {
    "envelope":{
      "envelope":"asdfasdfasdf"
    }
  }
}

I also though that the envelope would be a json string itself like:`

{
   "envelope": '{"envelope":"asdfasdfa"}'
}

Nevertheless, I am still getting the value of updatedOrNewEnvelopeWrapper as {"envelope":"asdfasdfa"}

Can you spot the bug?

Niko
  • 616
  • 4
  • 20

1 Answers1

2

There's a usage example on the API reference page to which you provided a link:

window.addEventListener("fbEnvelopePresent", async () => {
   const fbEnvelope = await ats.retrieveEnvelope(undefined, '_lr_fb_env');
   console.log("fbEnvelopePresent: ", JSON.parse(fbEnvelope).envelope);
});

The return type of your getFromATS method is a string:

function getFromATS(): string {
  return this._window.ats.retrieveEnvelope(function (envelope: string) {
    return JSON.parse(envelope).envelope;
  });
}

The documentation doesn't clarify the return type of the ats.retrieveEnvelope function when the callback overload is used, but I presume it to be void if it follows conventional patterns. Based on the contextual information, it must be something that takes time to produce a value, therefore it follows that it must be asynchronous, else it would block the event loop.

The actual content of the documentation is this:

ats.retrieveEnvelope(callback);

Fetch the envelope from configured storage; the callback function is optional. If the function is called without a callback, a promise will be returned. If the function is called with a callback, an envelope value will be returned.

I am suspicious about the last sentence:

If the function is called with a callback, an envelope value will be returned.

I think there's been a mistake in the wording of that description: the return type of the function in that case will likely be void, and the first argument provided to the callback function will be an envelope value.

You'll need to update your method to return a promise. Here's an example of a refactor:

async function getFromATS(): Promise<string> {
  const json = await this._window.ats.retrieveEnvelope();
  return JSON.parse(json).envelope;
}
jsejcksn
  • 27,667
  • 4
  • 38
  • 62
  • Unfortunately, this did not help. – Niko Jul 15 '23 at 10:29
  • My unit tests - unchanged - work with all different implementations. But I am mocking the `ats.retrieveEnvelope`. But when I deploy it, the parsing still does not work. It's frustrating that I cannot test it manually on a console, on a website since there are specific criteria that allow ATS to be loaded – Niko Jul 15 '23 at 11:18
  • [^](https://stackoverflow.com/questions/76690142/javascript-promise-not-resolved-as-expected/76690185?noredirect=1#comment135212891_76690185) @Niko I understand your frustration — they provide no testable (or reviewable?) code, and the documentation seems lacking, so you're left to interpret and experiment. Unless you or someone else provides access to the source code, we can only make inferences. – jsejcksn Jul 15 '23 at 20:49