Google Home & Typescript

Hi @46deva,

I don’t have full knowledge on the subject to help you, but the tutorial helped me a lot: Authenticating smart home Actions for the Google Assistant with Auth0 by Nick Felker and the original code: https://github.com/actions-on-google/smart-home-nodejs.

Below is my code that works:

index.ts


// Express imports
import * as express from 'express'
import * as bodyParser from 'body-parser'
import * as cors from 'cors'
import * as morgan from 'morgan'
import * as ngrok from 'ngrok'
import { AddressInfo } from 'net'

// Smart home imports
import {
  smarthome,
  SmartHomeV1ExecuteResponseCommands,
  Headers,
} from 'actions-on-google'

// Local imports
import * as Firestore from './firestore'
import * as Auth from './auth-provider'
import * as Config from './config-provider'

const expressApp = express()
expressApp.use(cors())
expressApp.use(morgan('dev'))
expressApp.use(bodyParser.json())
expressApp.use(bodyParser.urlencoded({extended: true}))
expressApp.set('trust proxy', 1)

Auth.registerAuthEndpoints(expressApp)

let jwt
try {
  jwt = require('./smart-home-key.json')
} catch (e) {
  console.warn('Service account key is not found')
  console.warn('Report state and Request sync will be unavailable')
}

const app = smarthome({
  jwt,
  debug: true,
})

// Array could be of any type
// tslint:disable-next-line
async function asyncForEach(array: any[], callback: Function) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array)
  }
}

async function getUserIdOrThrow(headers: Headers): Promise {
  var userId = await Auth.getUser(headers)
  if (!userId) {
    console.log('>>>> getUserIdOrThrow deu Ruim ')
    userId = await Auth.getUser(headers)
  }
  console.log('>>>> getUserIdOrThrow : ', userId)
  const userExists = await Firestore.userExists(userId)
  if (!userExists) {
    throw new Error(`User ${userId} has not created an account, so there are no devices`)
  }
  return userId
}

app.onSync(async (body, headers) => {
  const userId = await getUserIdOrThrow(headers)
  await Firestore.setHomegraphEnable(userId, true)

  const devices = await Firestore.getDevices(userId)
  return {
    requestId: body.requestId,
    payload: {
      agentUserId: userId,
      devices,
    },
  }
})

interface DeviceStatesMap {
  // tslint:disable-next-line
  [key: string]: any
}
app.onQuery(async (body, headers) => {
  const userId = await getUserIdOrThrow(headers)
  const deviceStates: DeviceStatesMap = {}
  const {devices} = body.inputs[0].payload
  await asyncForEach(devices, async (device: {id: string}) => {
    const states = await Firestore.getState(userId, device.id)
    deviceStates[device.id] = states
  })


  return {
    requestId: body.requestId,
    payload: {
      devices: deviceStates,
    },
  }
})

app.onExecute(async (body, headers) => {
  const userId = await getUserIdOrThrow(headers)
  const commands: SmartHomeV1ExecuteResponseCommands[] = [{
    ids: [],
    status: 'SUCCESS',
    states: {},
  }]
  //console.log('>>>> userId <<< {
    try {
      const states = await Firestore.execute(userId, device.id, execution[0])
      commands[0].ids.push(device.id)
      commands[0].states = states

     
      await app.reportState({
        agentUserId: userId,
        //requestId: Math.random().toString(),
        requestId: body.requestId,
        payload: {
          devices: {
            states: {
              [device.id]: states,
            },
          },
        },
      })
     
  return {
    requestId: body.requestId,
    payload: {
      commands,
    },
  }
})

app.onDisconnect(async (body, headers) => {
  const userId = await getUserIdOrThrow(headers)
  await Firestore.disconnect(userId)
})

expressApp.post('/smarthome', app)

expressApp.post('/smarthome/update', async (req, res) => {
  console.log(req.body)
  const {userId, deviceId, name, nickname, states} = req.body
  try {
    await Firestore.updateDevice(userId, deviceId, name, nickname, states)
    const reportStateResponse = await app.reportState({
      agentUserId: userId,
      requestId: Math.random().toString(),
      payload: {
        devices: {
          states: {
            [deviceId]: states,
          },
        },
      },
    })

    res.status(200).send('OK')
  } catch(e) {
    console.error(e)
    res.status(400).send(`Error reporting state: ${e}`)
  }
})

expressApp.post('/smarthome/create', async (req, res) => {
  console.log(req.body)
  const {userId, data} = req.body
  try {
    await Firestore.addDevice(userId, data)
    await app.requestSync(userId)
  } catch(e) {
    console.error(e)
  } finally {
    res.status(200).send('OK')
  }
})

expressApp.post('/smarthome/delete', async (req, res) => {
  console.log(req.body)
  const {userId, deviceId} = req.body
  try {
    await Firestore.deleteDevice(userId, deviceId)
    await app.requestSync(userId)
  } catch(e) {
    console.error(e)
  } finally {
    res.status(200).send('OK')
  }
})

const appPort = process.env.PORT || Config.expressPort

const expressServer = expressApp.listen(appPort, () => {
  const server = expressServer.address() as AddressInfo
  const {address, port} = server

  console.log(`Smart home server listening at ${address}:${port}`)

  if (Config.useNgrok) {
    ngrok.connect(Config.expressPort, (err, url) => {
      if (err) {
        console.error('Ngrok was unable to start')
        console.error(err)
        process.exit()
      }

      console.log('')
      console.log('COPY & PASTE NGROK URL BELOW')
      console.log(url)
      console.log('')
      console.log('=====')
      console.log('Visit the Actions on Google console at http://console.actions.google.com')
      console.log('Replace the webhook URL in the Actions section with:')
      console.log('    ' + url + '/smarthome')

      console.log('')
      console.log('In the console, set the Authorization URL to:')
      console.log('    ' + url + '/fakeauth')

      console.log('')
      console.log('Then set the Token URL to:')
      console.log('    ' + url + '/faketoken')
      console.log('')

      console.log('Finally press the \'TEST DRAFT\' button')
    })
  }
})

auth-provider.ts


import * as express from 'express'
import * as util from 'util'
import { Headers } from 'actions-on-google'
import * as Firestore from './firestore'
import { AuthenticationClient } from 'auth0'

const auth0 = new AuthenticationClient({
  domain: my-domain,
  clientId: my-clientID,
  clientSecret: my-clienteSecret
});

export async function getUser(headers: Headers): Promise {
  const authorization = headers.authorization
  const accessToken = (authorization as string).substr(7)
  const {email} = await auth0.getProfile(accessToken)
  
  const userExists = await Firestore.userExists(email)
  if (!userExists) {
    await Firestore.addUser(email, email)
  }
  return await Firestore.getUserIdByEmail(email)
}