SmartField and API typescript

Feature(s) impacted

We are currently experiencing an issue with integrating a PDF viewer and download functionality into Forest Admin with the TypeScript build. We are trying to implement a Smart Field that allows viewing and downloading PDF documents directly from our collection.

Despite several attempts, the viewer does not work as expected in our TypeScript environment. We have followed the examples provided in the documentation, but they seem to be mainly oriented towards JavaScript environments. The code we are using to get the PDF via an API works, but viewing the file in base64 in the Forest Admin interface fails.

collection('clientDocuments', {
  fields: [
    {
      field: 'pdfViewerUrl',
      type: 'File',
      isReadOnly: true,
      get: async (record) => {
        try {
          const response = await axios.get(`URL_TO_PDF`, { responseType: 'arraybuffer' });
          const base64 = Buffer.from(response.data).toString('base64');
          return `data:${response.headers['content-type']};base64,${base64}`;
        } catch (error) {
          return `Error loading PDF: ${error.message}`;
        }
      },
    },
  ],
});

How to integrate it?

We would like to know if you have any specific recommendations for implementing this feature in a TypeScript project, or if any adjustments are needed to make the viewer and download work properly.

Observed behavior

Expected behavior

Failure Logs

Context

  • Project name: …
  • Team name: …
  • Environment name: …
  • Agent technology: (nodejs, php, rails, python)
  • Agent (forest package) name & version: …
  • Database type: …
  • Recent changes made on your end if any: …

Hello @Vincent,

That’s an awesome use case. That’s normally totally possible. Depending on the resource (public or not), there are multiple ways of doing it.

In your case the URL looks public, doesn’t it? It might be easier to only send a PDF name or URL and let the front handle it for you. It is less costly to do it this way.

  • Did you configure something regarding the pdfViewerUrl field in the Frontend?

You need to configure the Display widget to a File viewer.


As a side note, we develop our new agent Node.js in TypeScript first you might be interested in moving to it some day.

Kind regards,
Morgan

thanks for your response
It is not public but protected with a key. For this I need to integrate an API to make the communication
Ideally to be able to create additional intelligent fields with the right data without touching my model schema
example:

const schema = new Mongoose.Schema({
'name': { type: String, required: true},
'type': { type: String, required: true },
'isVideo': { type: Boolean, default: false },
}

to be able to add the fields
urlPdf which would take the _id and the name dynamically
to be able to only create additional fields to map with other schemas
My questions:

  • How can I create a dynamic field without touching my basic schema
  • Integrate security with a private key without the endpoint being public
  • do mapping with other schemas
    thks in advance

Hello @Vincent,

If this is not public, then indeed, the best option is to keep your calls and private API on agent side and let it return the data to the frontend.

To answer your first point:

  • How can I create a dynamic field without touching my basic schema

Indeed, adding a smart field is the way to go. Make sure that you have set your field widget display to File Viewer, and you should get a preview in detail views. Please find below an example field that will display a very simple pdf.

    {
      field: 'File',
      type: 'String',
      get: (record) => {
        return `data:application/pdf;base64,JVBERi0xLjAKMSAwIG9iajw8L1BhZ2VzIDIgMCBSPj5lbmRvYmogMiAwIG9iajw8L0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iaiAzIDAgb2JqPDwvTWVkaWFCb3hbMCAwIDMgM10+PmVuZG9iagp0cmFpbGVyPDwvUm9vdCAxIDAgUj4+Cg==`
      },
    }

Did you encounter any issue with that part. Did you get any typescript errors ?

  • Integrate security with a private key without the endpoint being public

The api call with the key should be done on agent side, as you attempted in your first post.

  • do mapping with other schemas

I’m not sure that I understand this question.

collection.addField('imageBase64', {
    columnType: 'File', 
    isReadOnly: true,
    dependencies: ['_id'],
    getValues: async () => {
      try {
        // Utilisation d'une image depuis une API qui retourne une image JPEG
      const response = await axios.get('https://fastly.picsum.photos/id/560/200/200.jpg?hmac=Dqou6QpKCTK2srRsCRhlIxLQHvFL7zz6UocOb3UkpwI', {
        responseType: 'arraybuffer',
      });

      const base64Image = Buffer.from(response.data).toString('base64');

      return `data:image/jpeg;base64,${base64Image}`;
        
  
      } catch (error) {
        console.error('Error loading image:', error);
        return `Error loading image: ${error.message}`;
      }
    },
  });

return error

why and how to do it?

Hello @Vincent,

Your second example is confusing, as it seems to be using the syntax for package @forestadmin/agent, while your first post was using syntax for forest-express-sequelize or forest-express-mongoose.

Could you please take a minute to fill in the template in order for us to provide a more precise help :pray:

Observed behavior

Expected behavior

Failure Logs

Context

  • Project name: …
  • Team name: …
  • Environment name: …
  • Agent technology: (nodejs, php, rails, python)
  • Agent (forest package) name & version: …
  • Database type: …
  • Recent changes made on your end if any: …

As for your example you shared, it seems to indicate that the frontend received an unexpected value form your agent. Can you please share the payload of what you received on frontend side, by using developer tools in the browser ?

One last point: you are using try/catch in your smart field code. May I suggest that you remove those in while you are in development, so that errors will be displayed more clearly.

Thanks

We are using: @forestadmin/agent
We are able to display new fields (but only the first letter is displayed)
We are able to use the file but we still encounter a missing prefix to base64 data error (however, we have used public images and with postman it works fine.)

How can we solve these problems quickly now?

thks by advance

For the example you shared, it seems to indicate that the frontend received an unexpected value form your agent. Can you please share the payload of what you received on frontend side, by using developer tools in the browser ?

Testing the example that you posted above, there are multiple issues that prevent it from working:

  • columnType should be String and not File which does not exist
  • getValues return value is takes a records list in input, and returns a list (this corresponds to all the records in a page for instance)
  • There is probably an issue with your typescript setup, since all this should have raised warnings/errors. can you please share more details about your agent creation code and ts config ?

Please find below a working example:

    .addField('imageBase64', {
      columnType: 'String',
      dependencies: ['id'],
      getValues: async records => {
        // Utilisation d'une image depuis une API qui retourne une image JPEG
        const response = await axios.get(
          'https://fastly.picsum.photos/id/560/200/200.jpg?hmac=Dqou6QpKCTK2srRsCRhlIxLQHvFL7zz6UocOb3UkpwI',
          {
            responseType: 'arraybuffer',
          },
        );
        const base64Image = Buffer.from(response.data).toString('base64');
        return records.map(_ => `data:image/jpeg;base64,${base64Image}`);
      },
    })