Giter Club home page Giter Club logo

Comments (4)

monecchi avatar monecchi commented on June 16, 2024 1

For anyone coming around the issue, the following assumes you've already created an account on Tuya IoT Platform as also a Cloud Project in order to get the required credentials, aka Client ID & Client Secret, and the project API endpoint (Data center url address).

I was facing the same issue regarding CORS policy, but I've solved that by abstracting tuya-connector-nodejs in order to create an ordinary nodejs / express server. Also I've used cors package to configure the Access-Control-Allow-Origin CORS header... I'm sharing the simplified Custom API set up with its own routes which then call tuya-connector-nodejs functions.

On Tuya Iot Platform -> Cloud -> Development: I've left the Cloud Authorization IP Allowlist option disabled, as my intention was to run the server on localhost only.

Nodejs / Express:

.env

TUYA_DATA_CENTER_URL=https://openapi.tuyaus.com
TUYA_CLIENT_ID=
TUYA_CLIENT_SECRET=
TUYA_IMAGE_URL=https://images.tuyacn.com/

src/config.js

import dotenv from 'dotenv';
dotenv.config()

// Tuya Cloud - IoT Platform
const config = {
  baseUrl: `${process.env.TUYA_DATA_CENTER_URL}`, // Western America Data Center
  clientId: `${process.env.TUYA_CLIENT_ID}`, // Access ID / Client ID
  clientSecret: `${process.env.TUYA_CLIENT_SECRET}`, // Access Secret / Client Secret:
  apiImageUrl: `${process.env.TUYA_IMAGE_URL}` // https://images.tuyacn.com/
};

if (!Object.values(config).every(Boolean)) {
  throw new Error(
    'Please create .env from .env.example and specify all values',
  );
}

export default config;

src/index.js

import dotenv from 'dotenv'
import express from 'express'
import axios from 'axios'
import cors from 'cors'

// Custom Tuya Api Routes
import { routes } from './routes/index.js'

const router = express.Router()

dotenv.config()
const port = 3399
const app = express()

app.use(express.urlencoded({ extended: true }))
app.use(express.json({ limit: '32mb' }))

app.use(cors({ origin: '*' })) // This alone didn't work!
app.use(
  cors({
    origin: [
      'https://openapi.tuyeu.com',
      'https://openapi.tuyaus.com',
      'http://localhost:3000',
      'http://localhost:3399',
    ],
    methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE', 'OPTIONS'], 
    allowedHeaders: [
      'Origin',
      'X-Requested-With',
      'Content-Type',
      'Accept',
      'Signature-Headers',
      'client_id',
      'access_token',
      'sign',
      'sign_method',
      'stringToSign',
      't',
      'nonce',
    ], 
  })
)

app.use(routes)

app.listen(port, () => {
    console.log(
      `TuyaApi Started ✓ - Express listening on port : ${port} - press Ctrl-C to terminate.`
    )
})

src/routes/index.js

import express from 'express'
const router = express.Router()

// Main Api Routes
import { apiRoutes } from './api/index.js' // src/routes/api/index.js

const api = '/api/v1' 

router.use(api, apiRoutes)
router.use(api, (req, res) => res.status(404).json('No API route found'))

export { router as routes }

src/routes/api/index.js

import express from 'express'
const router = express.Router()

import { deviceRoutes } from './devices.js' // src/routes/api/device.js
import { userRoutes } from './user.js' // src/routes/api/user.js

// Device Routes
router.use('/device', deviceRoutes)

// User Routes
router.use('/users', userRoutes)

// Other Routes...

export { router as apiRoutes }

src/utils/redisConnect.js

// Redis (ioRedis) connection
import dotenv from 'dotenv'
dotenv.config()

export const redisConnect = {
  port: `${process.env.REDIS_PORT}`,
  host: `${process.env.REDIS_HOST}`,
  username: 'default',
  password: `${process.env.REDIS_PASS}`,
  db: 0
}

src/utils/tokenStore.js

//
// redis store
// @see: https://github.com/tuya/tuya-connector-nodejs
//
import Redis from 'ioredis'
import { redisConnect } from '../utils/redisConnect.js'

export class RedisTokenStore {
  client = new Redis(redisConnect)

  constructor (client, key = 'tuya::token') {
    this.client = client
    this.key = key
  }

  async setTokens (tokens) {
    const res = await this.client.set(this.key, JSON.stringify(tokens))
    return !!res
  }

  async getAccessToken () {
    const jsonStr = (await this.client.get(this.key)) || '{}'
    const tokens = JSON.parse(jsonStr)
    return tokens && tokens.access_token
  }

  async getRefreshToken () {
    const jsonStr = (await this.client.get(this.key)) || '{}'
    const tokens = JSON.parse(jsonStr)
    return tokens.refresh_token
  }
}

src/routes/api/device.js

Here tuya-connector-nodejs functions are wrapped within ordinary express router logic...

import express from 'express'
import axios from 'axios'
import Redis from 'ioredis'

import { TuyaContext } from '@tuya/tuya-connector-nodejs'
import { RedisTokenStore } from '../../utils/tokenStore.js'
import { redisConnect } from '../../utils/redisConnect.js'

import config from '../../config.js'
const { baseUrl, clientId, clientSecret, deviceId, uid } = config

const router = express.Router()

const redis = new Redis(redisConnect)

const tuya = new TuyaContext({
  baseUrl: baseUrl, // https://openapi.tuyaus.com',
  accessKey: clientId,
  secretKey: clientSecret,
  store: new RedisTokenStore(redis),
  rpc: axios
})

//
// Devices API Routes
// Tuya Cloud API Reference: https://developer.tuya.com/en/docs/iot/api-reference?id=Ka7qb7vhber64
// @see: https://github.com/tuya/tuya-connector-nodejs
//

//
// Post commands to device
//
router.post('/:id/commands', async (req, res) => {
  try {
    const deviceId = req.params.id
    const { commands } = req.body

    const result = await tuya.request({
      method: 'POST',
      path: `/v1.0/devices/${deviceId}/commands`,
      body: { commands }
    })

    res.status(200).json({
      ...result,
      message: 'Device updated successfully!'
    })
  } catch (error) {
    console.log('ERROR', error)
  }
})

//
//
// Update (modify) device details, such as name etc..
//
router.put('/:id/update', async (req, res) => {
  try {
    const deviceId = req.params.id
    const update = req.body

    if (Object.keys(update).length === 0) {
      return res.status(400).json({ error: 'Missing body params' })
    }

    const result = await tuya.request({
      method: 'PUT',
      path: `/v1.0/iot-03/devices/${deviceId}`,
      body: update
    })

    res.status(200).json({
      ...result,
      message: 'Device updated successfully!'
    })
  } catch (error) {
    console.log('ERROR', error)
  }
})

//
// Get all devices under a user's account by user ID
//
router.get('/', async (req, res) => {
  try {
    const response = await tuya.request({
      method: 'GET',
      path: `/v1.0/users/${uid}/devices`
      // body: {},
    })

    res.status(200).json({
      ...response.result
    })
  } catch (error) {
    console.log('ERROR', error)
  }
})

//
// Get device details by id
//
router.get('/:id', async (req, res) => {
  try {
    const deviceId = req.params.id

    const response = tuya.request({
      method: 'GET',
      path: `/v1.0/devices/${deviceId}`
      // body: {},
    })

    await response.then(result => {
      res.status(200).send(result)
    })
  } catch (error) {
    console.log('ERROR', error)
  }
})

//
// Get device available data points
//
router.get('/:id/status', async (req, res) => {
  try {
    const deviceId = req.params.id

    const response = tuya.request({
      method: 'GET',
      path: `/v1.0/devices/${deviceId}/status`
      // body: {},
    })

    await response.then(result => {
      res.status(200).json(result)
    })
  } catch (error) {
    console.log('ERROR', error)
  }
})

//
// Get device available functions
//
router.get('/:id/functions', async (req, res) => {
  try {
    const deviceId = req.params.id

    const response = tuya.request({
      method: 'GET',
      path: `/v1.0/iot-03/devices/${deviceId}/functions`
      // body: {},
    })

    await response.then(result => {
      res.status(200).send(result)
    })
  } catch (error) {
    console.log('ERROR', error)
  }
})

export { router as deviceRoutes }

After starting the server, if no errors, the API is ready to be consumed. So in the Frontend as usual, by making a GET request to http://localhost:3399/api/v1/devices/:id, where :id is the deviceId connected to your Tuya Cloud project, you should get the device details.

I had no issues with CORS with this set up!

Please leave your enhancement ideas, such as a tuya middleware function or any other type of set up that works for you!

from tuya-connector-nodejs.

JimRh avatar JimRh commented on June 16, 2024

For anyone coming around the issue, the following assumes you've already created an account on Tuya IoT Platform as also a Cloud Project in order to get the required credentials, aka Client ID & Client Secret, and the project API endpoint (Data center url address).

I was facing the same issue regarding CORS policy, but I've solved that by abstrating tuya-connector-nodejs in order to create an ordinary nodejs / express server. Also I've used cors package to configure the Access-Control-Allow-Origin CORS header... I'm sharing the simplified Custom API set up with its own routes which then call tuya-connector-nodejs functions.

On Tuya Iot Platform -> Cloud -> Development: I've left the Cloud Authorization IP Allowlist option disabled, as my intention was to run the server on localhost only.

Nodejs / Express:

.env

TUYA_DATA_CENTER_URL=https://openapi.tuyaus.com
TUYA_CLIENT_ID=
TUYA_CLIENT_SECRET=
TUYA_IMAGE_URL=https://images.tuyacn.com/

src/config.js

import dotenv from 'dotenv';
dotenv.config()

// Tuya Cloud - IoT Platform
const config = {
  baseUrl: `${process.env.TUYA_DATA_CENTER_URL}`, // Western America Data Center
  clientId: `${process.env.TUYA_CLIENT_ID}`, // Access ID / Client ID
  clientSecret: `${process.env.TUYA_CLIENT_SECRET}`, // Access Secret / Client Secret:
  apiImageUrl: `${process.env.TUYA_IMAGE_URL}` // https://images.tuyacn.com/
};

if (!Object.values(config).every(Boolean)) {
  throw new Error(
    'Please create .env from .env.example and specify all values',
  );
}

export default config;

src/index.js

import dotenv from 'dotenv'
import express from 'express'
import axios from 'axios'
import cors from 'cors'

// Custom Tuya Api Routes
import { routes } from './routes/index.js'

const router = express.Router()

dotenv.config()
const port = 3399
const app = express()

app.use(express.urlencoded({ extended: true }))
app.use(express.json({ limit: '32mb' }))

app.use(cors({ origin: '*' })) // This alone didn't work!
app.use(
  cors({
    origin: [
      'https://openapi.tuyeu.com',
      'https://openapi.tuyaus.com',
      'http://localhost:3000',
      'http://localhost:3399',
    ],
    methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE', 'OPTIONS'], 
    allowedHeaders: [
      'Origin',
      'X-Requested-With',
      'Content-Type',
      'Accept',
      'Signature-Headers',
      'client_id',
      'access_token',
      'sign',
      'sign_method',
      'stringToSign',
      't',
      'nonce',
    ], 
  })
)

app.use(routes)

app.listen(port, () => {
    console.log(
      `TuyaApi Started ✓ - Express listening on port : ${port} - press Ctrl-C to terminate.`
    )
})

src/routes/index.js

import express from 'express'
const router = express.Router()

// Main Api Routes
import { apiRoutes } from './api/index.js' // src/routes/api/index.js

const api = '/api/v1' 

router.use(api, apiRoutes)
router.use(api, (req, res) => res.status(404).json('No API route found'))

export { router as routes }

src/routes/api/index.js

import express from 'express'
const router = express.Router()

import { deviceRoutes } from './devices.js' // src/routes/api/device.js
import { userRoutes } from './user.js' // src/routes/api/user.js

// Device Routes
router.use('/device', deviceRoutes)

// User Routes
router.use('/users', userRoutes)

// Other Routes...

export { router as apiRoutes }

src/routes/api/device.js

Here tuya-connector-nodejs functions are wrapped within ordinary express router logic...

import express from 'express'
const router = express.Router()
import axios from 'axios'

import { TuyaContext } from '@tuya/tuya-connector-nodejs';
import { RedisTokenStore } from '../../utils/tokenStore.ts';
import Redis from 'ioredis';
const redis = new Redis();

import config from '../../config.js';
const { baseUrl, clientId, clientSecret } = config;

const tuya = new TuyaContext({
  baseUrl: baseUrl, //'https://openapi.tuyaus.com',
  accessKey: clientId,
  secretKey: clientSecret,
  store: new RedisTokenStore(redis),
  rpc: axios
});

//
// Get device by id
//

router.get('/:id', async (req, res) => {
  try {
    const deviceId = req.params.id

    const response = tuya.request({
      method: 'GET',
      path: `/v1.0/devices/${deviceId}`,
      body: {},
    })

    await response.then(result => {
      res.status(200).send(result)
    })

  } catch (error) {
    console.log('ERROR', error);
  }
})

// ... rest of routes

// router.get()

// router.post()

...

export { router as deviceRoutes }

After starting the server, if no errors, the API is ready to be consumed. So in the Frontend as usual, by making a GET request to http://localhost:3399/api/v1/devices/:id, where :id is the deviceId connected to your Tuya Cloud project, you should get the device details.

I had no issues with CORS with this set up!

Please leave your enhancement ideas, such as a tuya middleware function or any other type of set up that works for you!

Hi did you run the tuya connector node sdk and this node server together,or you just took the functions from their code and that redis token they have in their github,I was facing sign invalid issue.

from tuya-connector-nodejs.

JimRh avatar JimRh commented on June 16, 2024

@monecchi Can you share your tokenStore.ts file code

from tuya-connector-nodejs.

monecchi avatar monecchi commented on June 16, 2024

Hey @JimRh! You're right, I use their suggested tokenStore solution based on redis. The project I've set up does not use typescript ... I've updated the answer code blocks to include an abstraction of the way I'm connectng to the redis server (remote or local).

from tuya-connector-nodejs.

Related Issues (12)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.