Forge API Content Security Policy error

Hello,

I am creating a dashboard gadget app. I was able to install and run with static text. However, I am getting Content Security Policy error when trying to make call to Jira API.

Below is the sample code:

import api, { route } from "@forge/api";

const View = () => {
  var jql = 'filter = "Filter by Type"';
  var payload = `{ jql: ${jql}, startAt: 0, maxResults: 3000 }`
    
  const response = await api.asApp().requestJira(route`/rest/api/2/search`, {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: payload
  });
  
  const json = await response.json();
  
  console.log(`Response: ${response.status} ${response.statusText}`);
  console.log(json);
  
  return json;
}

In the menifest have below permission:

permissions:
  scopes:
    - read:jira-work

index.jsx:82 Refused to connect to ‘https://jira/rest/api/2/search’ because it violates the following Content Security Policy directive: “connect-src ‘self’ https://api.atlassian.com/metal/ingest”.

The app is running under https://mydomain.atlassian.net/jira/dashboards/10001/edit

The ‘route’ should be ‘https://mydomain.atlassian.net’ and not ‘https://jira’.

Thanks for you help!

Hi @AhmedPan, the @forge/api package is meant to be used in Forge backend resolvers and the deprecated old version of UI Kit (which makes a call to the Forge backend resolver by default).

If you want to make an API request to Jira from your front end, then you are probably want to use requestJira in the @forge/bridge package.

How do I return a promise from the resolver and use that in the front end? In the resolver I can’t await a call.

resolver.define('getText', (req) => {
  console.log(req);

  var jql = 'filter = "Filter by Type"';
  var payload = `{ "jql": ${jql}, startAt: 0, maxResults: 3000 }`

  var p = {
    "jql": jql,
    "startAt": 0,
    "maxResults": 3000
  }

  const response = api.asApp().requestJira(route`/rest/api/2/search`, {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(p)
  });

  return response;
});

Code in the front-end:

 useEffect(() => {

   invoke('getText', { example: 'my-invoke-variable' }).then( async (rr) => {
    console.log(rr);
    const result = await rr.json();
     setData(result);
   });
 }, []);

Thanks @BoZhang for you input. It help me find the solution. I got it working both front-end and back-end. Below are both solution for reference.

Forge version 10.5.0
Module: jira:dashboardGadget

Front-end solution (indext.jsx):

const View = () => {
  const [data, setData] = useState(null);
  const context = useProductContext();

  const fetchFilter = async () => {
    var jql = 'filter = "Filter by Type"';
    var p = {
      "jql": jql,
      "startAt": 0,
      "maxResults": 3000
    }

    const response = await requestJira('/rest/api/2/search', {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(p)
    });

    const result = await response.text();
    console.log(result);

    return result;
  };

  fetchFilter().then((r) => {
    setData(r);
  })

  return (
    <>
      <Text>data: {data}</Text>
    </>
  );
};

requestJira: rest call is made as the current user.
Note: the “fetchFilter” method is returning text and not JSON.

Back-end solution:
indext.js: the resolver

resolver.define('getText', async (req) => {
  console.log(req);

  var jql = 'filter = "Filter by Type"';
  var p = {
    "jql": jql,
    "startAt": 0,
    "maxResults": 3000
  }

  const response = await api.asApp().requestJira(route`/rest/api/2/search`, {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(p)
  });

  const result = await response.json();
  return result;
});

Indext.jsx calling resolver:

const View = () => {
  const [data, setData] = useState(null);
  const context = useProductContext();
  
  useEffect(() => {
    invoke('getText', { example: 'my-invoke-variable' }).then((rr) => {
      const id = rr.issues[0].id;
      const key = rr.issues[0].key;
      setData(`id: ${id}, key: ${key}`);
    });
  }, []);

  return (
    <>
      <Text>data: {data}</Text>
    </>
  );
};
1 Like