firebase functions integration with xero-node

439 Views Asked by At

Attempting to create a firebase function that is triggered by some database or onRequest event that creates payments on Xero to better manage incoming invoices.

Not sure I understand how to run (if its possible) the xero-node library from firebase functions.

I have been messing around with the two and it doesn't seem to be as simple as initialising the object with the secret ID and calling functions like i would expect:

const xero = new XeroClient({
  clientId: '230823jff',
  clientSecret: 'sdfh39fhsdaf',
  redirectUris: [`http://localhost:3000/xero`],
  scopes: 'openid profile email accounting.transactions offline_access'.split(' '),
})

xero.accountingApi.getInvoices('tennetid093242')

It looks like I need to generate an Access key with the following (which expires in 12 minutes):

let consentUrl = await xero.buildConsentUrl()
res.redirect(consentUrl)

messing around with the api in postman and this flow feels like i'm making it more complicated than it should be.

Is there a way I can just initialise it with an admin scope or something and not have to worry about access keys... Or should i use xero-node on the client side... (surely not)

There are expressjs versions with xero-node so would i just do the equivilent with firebase onrequest functions by making the redirectUri the address of another firebase function to get the session key?


Im getting somewhere slowly..

my firebase functions look like this:

const xero = new XeroClient({
  clientId: '123213123123',
  clientSecret: '123123123123',
  redirectUris: ['http://localhost:5001/example/us-central1/xeroAuthCode'],
  scopes: ['openid', 'profile', 'email', 'accounting.transactions', 'accounting.settings', 'offline_access']
})

exports.xeroInit = functions.https.onRequest(async (req, res) => {
  let consentUrl = await xero.buildConsentUrl()
  res.redirect(consentUrl)
})

exports.xeroAuthCode = functions.https.onRequest(async (req, res) => {
  let tokenSet = await xero.apiCallback(req.url)
  console.log(tokenSet)
})

The idea is that I call xeroInit and the token gets sent to xeroAuth which seems to work because console logging the req.url gives:

/?code=c5105f6ce943....230a23fsadf&scope=openid%20profile%20email%20accounting.transactions%20accounting.settings&session_state=tFC6G9Go_zBguCjIpy8-9gl6-9SWLTlUmY5CXMq49es.3c42d9ca9e53285596193bf423f791f3

But i get this error when trying to set it apiCallback

TypeError: Cannot read property 'callbackParams' of undefined

Testing with a pure express app this works perfectly i expect this is a bug with the firebase functions emulator...

Because this worked with express i used express inside my firebase function and calling apiCallback with the request.url worked... Ill update the answer when i get it working fully for others who want to use firebase and xero-node together.

2

There are 2 best solutions below

0
On BEST ANSWER

Big thanks to SerKnight who helped me get this working on firebase functions for future reference here is the configuration I got working.

Some notes

  • Just calling xero.initialize() in a firebase function as a callback didn't work for what ever reason so I used express to handle initial setup.
  • Once I have the refresh token in the database my connect function does the magic so I don't need to buildConsentUrl every time i want to do call a function.
  • you could just keep using express to call your functions if you wanted to.

Ensure your firebase function url is put into the OAuth 2.0 redirect URIs on xeros myapps page in full as you see it in my example. localhost if using firebase emulators

const app = express()
app.use(cors())

let x_client_id = functions.config().xero.client_id
let x_client_sectet = functions.config().xero.client_secret

const xero = new XeroClient({
  clientId: x_client_id,
  clientSecret: x_client_sectet,
  redirectUris: ['http://localhost:5001/example/us-central1/xeroInit/callback'],
  scopes: ['openid', 'profile', 'email', 'accounting.transactions', 'accounting.settings', 'offline_access'],
  httpTimeout: 3000,
  state: 123
})

const connect = () =>
  new Promise(async (res, rej) => {
    let snapshot = await db.ref('xero-config').once('value')
    let tokenSet = snapshot.val()
    try {
      xero.initialize()
      const newTokenSet = await xero.refreshWithRefreshToken(x_client_id, x_client_sectet, tokenSet.refresh_token)
      db.ref('xero-config').set(newTokenSet)
      xero.setTokenSet(newTokenSet)
      res()
    } catch (error) {
      rej(error)
    }
  })

app.get('/connect', async (req, res) => {
  try {
    await connect()
    res.send('Connection established')
  } catch (error) {
    let consentUrl = await xero.buildConsentUrl()
    res.redirect(consentUrl)
  }
})
app.get('/callback', async (req, res) => {
  let TokenSet = await xero.apiCallback(req.url)
  db.ref('xero-config').set(TokenSet)
  res.send('Token updated ')
})
exports.xeroInit = functions.https.onRequest(app)

exports.xeroOrganisation = functions.https.onRequest(async (req, res) => {
  await connect() //Doesn't handle errors and will break if inactive for 60 days but you get the idea
  await xero.updateTenants()
  res.json(xero.tenants[0])
})
  1. Call your connect function with http://localhost:5001/example/us-central1/xeroInit/connect
  2. Ensure once redirected you got the token updated message
  3. call your xeroOrganisation function to ensure everything it working (:
3
On

EDIT: Ah I think what you need to do is just initialize the client the second time using:

await xero.initialize()

This needs to be called to setup relevant openid-client on the XeroClient

buildConsentUrl also calls await xero.initialize() under the hood so since you called it in the initial thread it setup the client correctly. Due to firebase functions being siloed i think you just need to call the initialize() fn.



you will need to have a user for the Xero org you want to communicate with via API go through a whole OAuth2.0 flow to generate the access_token.

There is a great example app I recommend checking out that shows the whole flow: https://github.com/XeroAPI/xero-node-oauth2-app/blob/master/src/app.ts#L172


Is your goal in facilitating many users to use your app to read write to their orgs? Or are you just trying to connect and automate stuff for a single org?

If you are trying to do the second, this video shows how you can just generate the initial access_token in REST client and plug that into your code flow to be continually refreshed. https://www.youtube.com/watch?v=Zcf_64yreVI