Skip to content

Commit

Permalink
Handle expired access token
Browse files Browse the repository at this point in the history
Save refresh token when authenticate with valid access token.
Add retry in request in case of expired access token.
  • Loading branch information
nioc committed Jul 29, 2021
1 parent 96f630a commit 2ff5ada
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 5 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# netatmo-nodejs-api

[![license: AGPLv3](https://img.shields.io/badge/license-AGPLv3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
[![Build Status](https://travis-ci.com/nioc/netatmo-nodejs-api.svg?branch=master)](https://travis-ci.com/nioc/netatmo-nodejs-api)
[![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)
[![GitHub release](https://img.shields.io/github/release/nioc/netatmo-nodejs-api.svg)](https://github.com/nioc/netatmo-nodejs-api/releases/latest)
[![npm](https://img.shields.io/npm/dt/netatmo-nodejs-api)](https://www.npmjs.com/package/netatmo-nodejs-api)
[![npms.io (final)](https://img.shields.io/npms-io/final-score/netatmo-nodejs-api)](https://www.npmjs.com/package/netatmo-nodejs-api)
[![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)
[![npm](https://img.shields.io/npm/dt/netatmo-nodejs-api)](https://www.npmjs.com/package/netatmo-nodejs-api)

Node.js API wrapper for Netatmo API.

Expand Down
15 changes: 14 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ class NetatmoClient {
*/
async authenticate (accessToken = null, refreshToken = null, expiresInTimestamp = 0, username = null, password = null) {
if (this.checkAndSetAccesToken(accessToken, expiresInTimestamp)) {
if (refreshToken) {
this.refreshToken = refreshToken
}
return new Token(accessToken, refreshToken, expiresInTimestamp)
}
if (refreshToken) {
Expand Down Expand Up @@ -228,9 +231,10 @@ class NetatmoClient {
* @param {string} path API path (example: `'/api/gethomedata'`)
* @param {object} params Parameters send as query string
* @param {object} data Data to post
* @param {boolean} isRetry This is the second try for this request (default false)
* @return {object|Array} Data in response
*/
async request (method, path, params = null, data = null) {
async request (method, path, params = null, data = null, isRetry = false) {
const config = {
...this.requestConfig,
method,
Expand Down Expand Up @@ -259,13 +263,22 @@ class NetatmoClient {
return result.data
} catch (error) {
if (error.response && error.response.data) {
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) {
// expired access token error, remove it and try to get a new one before a retry
this.accessToken = null
await this.authenticate(null, this.refreshToken, this.expiresInTimestamp, this.username, this.password)
return await this.request(method, path, params, data, true)
}
if (error.response.data.error_description) {
// bad request error
throw new Error(`HTTP request ${path} failed: ${error.response.data.error_description} (${error.response.status})`)
}
if (error.response.data.error && error.response.data.error.message) {
// standard error
throw new Error(`HTTP request ${path} failed: ${error.response.data.error.message} (${error.response.status})`)
}
if (error.response.data.error) {
// other error
throw new Error(`HTTP request ${path} failed: ${JSON.stringify(error.response.data.error)} (${error.response.status})`)
}
}
Expand Down
17 changes: 15 additions & 2 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@ describe('Request', function () {
.onGet('/timeout').timeout()
.onGet('/authError').reply(400, { error: 'invalid_request', error_description: 'Missing parameters, "username" and "password" are required' })
.onGet('/appError').reply(400, { error: { code: 1, message: 'Access token is missing' } })
.onGet('/tokenExpired401').reply(401, { error: { code: 3, message: 'Access token expired' } })
.onGet('/tokenExpired403').reply(403, { error: { code: 3, message: 'Access token expired' } })
.onGet('/tokenExpired401').replyOnce(401, { error: { code: 3, message: 'Access token expired' } })
.onGet('/tokenExpired401').reply(200, { body: '401' })
.onGet('/tokenExpired403').replyOnce(403, { error: { code: 3, message: 'Access token expired' } })
.onGet('/tokenExpired403').reply(200, { body: '403' })
.onPost('/oauth2/token').reply(200, authResult)
.onGet('/noMessageError').reply(500, { error: { code: 99 } })
.onAny().reply(404)
})
Expand Down Expand Up @@ -75,6 +78,16 @@ describe('Request', function () {
await client.authenticate(authResult.access_token, undefined, 3600 + Date.now() / 1000)
await assert.rejects(async () => { await client.request('GET', '/noMessageError') }, new Error('HTTP request /noMessageError failed: {"code":99} (500)'))
})
it('should retry in case of HTTP 401 expired token', async function () {
await client.authenticate(authResult.access_token, authResult.refresh_token, 3600 + Date.now() / 1000)
const result = await client.request('GET', '/tokenExpired401')
assert.deepStrictEqual(result, { body: '401' })
})
it('should retry in case of HTTP 403 expired token', async function () {
await client.authenticate(authResult.access_token, authResult.refresh_token, 3600 + Date.now() / 1000)
const result = await client.request('GET', '/tokenExpired403')
assert.deepStrictEqual(result, { body: '403' })
})
})

describe('Authentication', function () {
Expand Down

0 comments on commit 2ff5ada

Please sign in to comment.