I tried controlling my GoSund smart socket using Tuya IoT Development Platform, but I'm stuck on this error response when trying to switch its state:
{"code":1004,"msg":"sign invalid","success":false,"t":1658384161392,"tid":"97e938e608bc11eda4f0322e56e3d437"}
The following code is basically slightly modified copy of develop code sample from official Tuya API site with my keys and deviceId pasted(https://developer.tuya.com/en/docs/iot/singnature?id=Ka43a5mtx1gsc)
When I tried to do the exact same thing using Tuya's site debug device option it just works. When I try to do it using their code sample in a web app, it fails with 1004. Except for the token that is new every time I call this, basically all the request headers are the same as when calling them from Tuya's site. Payload is the same too, but the response is very different.
same request on Tuya website device debugging & in a web app
Adding sign_version: '2.0' to request headers or using different url (const url = /v1.0/iot-03/devices/${deviceId}/commands;) doesn't seem to help.
const config = {
/* openapi host */
//host: 'https://openapi.tuyacn.com',
host: 'https://openapi.tuyaeu.com',
/* fetch from openapi platform */
accessKey: 'I pasted here my Access ID/Client ID from iot.tuya.com',
/* fetch from openapi platform */
secretKey: 'I pasted here my Access Secret/Client Secret from iot.tuya.com',
/* Interface example device_ID */
deviceId: 'I pasted here Device ID of my GoSund smart plug',
};
const httpClient = axios.create({
baseURL: config.host,
timeout: 5 * 1e3,
});
async main(switchValue: boolean) {
try{
await this.getToken();
const data = await this.getDeviceInfo(config.deviceId, switchValue);
console.log('fetch success: ', JSON.stringify(data));
}catch(error){
console.log(error);
}
}
/**
* fetch highway login token
*/
async getToken() {
const method = 'GET';
const timestamp = Date.now().toString();
const signUrl = '/v1.0/token?grant_type=1';
const contentHash = crypto.createHash('sha256').update('').digest('hex');
const stringToSign = [method, contentHash, '', signUrl].join('\n');
const signStr = config.accessKey + timestamp + stringToSign;
const headers = {
t: timestamp,
sign_method: 'HMAC-SHA256',
client_id: config.accessKey,
sign: await this.encryptStr(signStr, config.secretKey),
};
const { data: login } = await httpClient.get('/v1.0/token?grant_type=1', { headers });
if (!login || !login.success) {
throw Error(`fetch failed: ${login.msg}`);
}
this.setState({ token: login.result.access_token })
}
/**
* fetch highway business data
*/
async getDeviceInfo(deviceId: string, switchValue: boolean) {
const query = {};
const method = 'POST';
const url = `/v1.0/devices/${deviceId}/commands`;
const reqHeaders: { [k: string]: string } = await this.getRequestSign(url, method, {}, query);
const { data } = await httpClient.request({
method,
data: {commands: [{code: "countdown_1", value: 0}, {code: "switch", value: switchValue}]},
params: {},
headers: reqHeaders,
url: reqHeaders.path,
});
if (!data || !data.success) {
throw Error(`request api failed: ${data.msg}`);
}
}
/**
* HMAC-SHA256 crypto function
*/
async encryptStr(str: string, secret: string): Promise<string> {
return crypto.createHmac('sha256', secret).update(str, 'utf8').digest('hex').toUpperCase();
}
/**
* request sign, save headers
* @param path
* @param method
* @param headers
* @param query
* @param body
*/
async getRequestSign(
path: string,
method: string,
headers: { [k: string]: string } = {},
query: { [k: string]: any } = {},
body: { [k: string]: any } = {},
) {
const t = Date.now().toString();
const [uri, pathQuery] = path.split('?');
const queryMerged = Object.assign(query, qs.parse(pathQuery));
const sortedQuery: { [k: string]: string } = {};
Object.keys(queryMerged)
.sort()
.forEach((i) => (sortedQuery[i] = query[i]));
const querystring = decodeURIComponent(qs.stringify(sortedQuery));
const url = querystring ? `${uri}?${querystring}` : uri;
const contentHash = crypto.createHash('sha256').update(JSON.stringify(body)).digest('hex');
const client_id = config.accessKey
const access_token = this.state.token
const stringToSign = [method, contentHash, '', url].join('\n');
const signStr = client_id + access_token + t + stringToSign;
return {
t,
path: url,
client_id: config.accessKey,
sign: await this.encryptStr(signStr, config.secretKey),
sign_method: 'HMAC-SHA256',
sign_version: '2.0',
access_token: access_token
};
}