Skip to content

Commit 2ff5ada

Browse files
committed
Handle expired access token
Save refresh token when authenticate with valid access token. Add retry in request in case of expired access token.
1 parent 96f630a commit 2ff5ada

File tree

3 files changed

+32
-5
lines changed

3 files changed

+32
-5
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# netatmo-nodejs-api
22

33
[![license: AGPLv3](https://img.shields.io/badge/license-AGPLv3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
4+
[![Build Status](https://travis-ci.com/nioc/netatmo-nodejs-api.svg?branch=master)](https://travis-ci.com/nioc/netatmo-nodejs-api)
5+
[![Coverage Status](https://coveralls.io/repos/github/nioc/netatmo-nodejs-api/badge.svg?branch=master)](https://coveralls.io/github/nioc/netatmo-nodejs-api?branch=master)
46
[![GitHub release](https://img.shields.io/github/release/nioc/netatmo-nodejs-api.svg)](https://github.com/nioc/netatmo-nodejs-api/releases/latest)
5-
[![npm](https://img.shields.io/npm/dt/netatmo-nodejs-api)](https://www.npmjs.com/package/netatmo-nodejs-api)
67
[![npms.io (final)](https://img.shields.io/npms-io/final-score/netatmo-nodejs-api)](https://www.npmjs.com/package/netatmo-nodejs-api)
7-
[![Coverage Status](https://coveralls.io/repos/github/nioc/netatmo-nodejs-api/badge.svg?branch=master)](https://coveralls.io/github/nioc/netatmo-nodejs-api?branch=master)
8+
[![npm](https://img.shields.io/npm/dt/netatmo-nodejs-api)](https://www.npmjs.com/package/netatmo-nodejs-api)
89

910
Node.js API wrapper for Netatmo API.
1011

lib/index.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ class NetatmoClient {
8585
*/
8686
async authenticate (accessToken = null, refreshToken = null, expiresInTimestamp = 0, username = null, password = null) {
8787
if (this.checkAndSetAccesToken(accessToken, expiresInTimestamp)) {
88+
if (refreshToken) {
89+
this.refreshToken = refreshToken
90+
}
8891
return new Token(accessToken, refreshToken, expiresInTimestamp)
8992
}
9093
if (refreshToken) {
@@ -228,9 +231,10 @@ class NetatmoClient {
228231
* @param {string} path API path (example: `'/api/gethomedata'`)
229232
* @param {object} params Parameters send as query string
230233
* @param {object} data Data to post
234+
* @param {boolean} isRetry This is the second try for this request (default false)
231235
* @return {object|Array} Data in response
232236
*/
233-
async request (method, path, params = null, data = null) {
237+
async request (method, path, params = null, data = null, isRetry = false) {
234238
const config = {
235239
...this.requestConfig,
236240
method,
@@ -259,13 +263,22 @@ class NetatmoClient {
259263
return result.data
260264
} catch (error) {
261265
if (error.response && error.response.data) {
266+
if (!isRetry && (error.response.status === 403 || error.response.status === 401) && error.response.data.error && error.response.data.error.code && error.response.data.error.code === 3) {
267+
// expired access token error, remove it and try to get a new one before a retry
268+
this.accessToken = null
269+
await this.authenticate(null, this.refreshToken, this.expiresInTimestamp, this.username, this.password)
270+
return await this.request(method, path, params, data, true)
271+
}
262272
if (error.response.data.error_description) {
273+
// bad request error
263274
throw new Error(`HTTP request ${path} failed: ${error.response.data.error_description} (${error.response.status})`)
264275
}
265276
if (error.response.data.error && error.response.data.error.message) {
277+
// standard error
266278
throw new Error(`HTTP request ${path} failed: ${error.response.data.error.message} (${error.response.status})`)
267279
}
268280
if (error.response.data.error) {
281+
// other error
269282
throw new Error(`HTTP request ${path} failed: ${JSON.stringify(error.response.data.error)} (${error.response.status})`)
270283
}
271284
}

test/index.test.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,11 @@ describe('Request', function () {
4545
.onGet('/timeout').timeout()
4646
.onGet('/authError').reply(400, { error: 'invalid_request', error_description: 'Missing parameters, "username" and "password" are required' })
4747
.onGet('/appError').reply(400, { error: { code: 1, message: 'Access token is missing' } })
48-
.onGet('/tokenExpired401').reply(401, { error: { code: 3, message: 'Access token expired' } })
49-
.onGet('/tokenExpired403').reply(403, { error: { code: 3, message: 'Access token expired' } })
48+
.onGet('/tokenExpired401').replyOnce(401, { error: { code: 3, message: 'Access token expired' } })
49+
.onGet('/tokenExpired401').reply(200, { body: '401' })
50+
.onGet('/tokenExpired403').replyOnce(403, { error: { code: 3, message: 'Access token expired' } })
51+
.onGet('/tokenExpired403').reply(200, { body: '403' })
52+
.onPost('/oauth2/token').reply(200, authResult)
5053
.onGet('/noMessageError').reply(500, { error: { code: 99 } })
5154
.onAny().reply(404)
5255
})
@@ -75,6 +78,16 @@ describe('Request', function () {
7578
await client.authenticate(authResult.access_token, undefined, 3600 + Date.now() / 1000)
7679
await assert.rejects(async () => { await client.request('GET', '/noMessageError') }, new Error('HTTP request /noMessageError failed: {"code":99} (500)'))
7780
})
81+
it('should retry in case of HTTP 401 expired token', async function () {
82+
await client.authenticate(authResult.access_token, authResult.refresh_token, 3600 + Date.now() / 1000)
83+
const result = await client.request('GET', '/tokenExpired401')
84+
assert.deepStrictEqual(result, { body: '401' })
85+
})
86+
it('should retry in case of HTTP 403 expired token', async function () {
87+
await client.authenticate(authResult.access_token, authResult.refresh_token, 3600 + Date.now() / 1000)
88+
const result = await client.request('GET', '/tokenExpired403')
89+
assert.deepStrictEqual(result, { body: '403' })
90+
})
7891
})
7992

8093
describe('Authentication', function () {

0 commit comments

Comments
 (0)