Skip to content

Commit

Permalink
fix: Fixed bug when making API calls from page handlers in installed …
Browse files Browse the repository at this point in the history
…apps (#205)
  • Loading branch information
bflorian committed Sep 1, 2021
1 parent 9bbae29 commit 34b50d5
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 45 deletions.
2 changes: 1 addition & 1 deletion jest.config.js
Expand Up @@ -6,7 +6,7 @@ module.exports = {
collectCoverageFrom: ['lib/**/*.js'],
coverageReporters: ['json', 'text'],
testEnvironment: 'node',
testPathIgnorePatterns: ['test/data'],
testPathIgnorePatterns: ['test/data', 'test/utilities'],
testMatch: ['**/test/**/*.[jt]s?(x)'],
setupFiles: ['<rootDir>/config/jest.setup.js']
}
2 changes: 2 additions & 0 deletions lib/util/smart-app-context.d.ts
Expand Up @@ -87,6 +87,8 @@ export interface SmartAppContext {
* Retrieve the tokens of the installed instance from the token store and return a new, authenticated
* SmartAppContext. This method is typically used to allow API calls to be made from the handlers
* of CONFIGURATION/PAGE lifecycle events.
* @deprecated This method will be removed at some point after the platform has been changed to include
* valid tokens in CONFIGURATION events as it does for all other lifecycle events.
*/
retrieveTokens(): Promise<SmartAppContext>

Expand Down
8 changes: 8 additions & 0 deletions lib/util/smart-app-context.js
@@ -1,6 +1,7 @@
'use strict'

const i18n = require('i18n')
const {Mutex} = require('async-mutex')
const {
SmartThingsClient,
BearerTokenAuthenticator,
Expand Down Expand Up @@ -146,6 +147,10 @@ module.exports = class SmartAppContext {
}
}

/**
* @deprecated This method will be removed at some point after the platform has been changed to include
* valid tokens in CONFIGURATION events as it does for all other lifecycle events.
*/
async retrieveTokens() {
const {app} = this
if (app._contextStore) {
Expand All @@ -154,11 +159,14 @@ module.exports = class SmartAppContext {
this.locationId = data.locationId
this.authToken = data.authToken
this.refreshToken = data.refreshToken
this.apiMutex = new Mutex()

const authenticator = new SequentialRefreshTokenAuthenticator(
this.authToken,
new TokenStore(this.installedAppId, app._contextStore, app._clientId, app._clientSecret),
this.apiMutex
)

const config = {
locationId: this.locationId,
installedAppId: this.installedAppId
Expand Down
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -34,7 +34,7 @@
},
"homepage": "https://github.com/SmartThingsCommunity/smartapp-sdk-nodejs#readme",
"dependencies": {
"@smartthings/core-sdk": "^1.7.0",
"@smartthings/core-sdk": "^1.9.0",
"@types/aws-lambda": "^8.10.51",
"@types/i18n": "^0.8.6",
"async-mutex": "^0.1.4",
Expand Down
35 changes: 1 addition & 34 deletions test/unit/smartapp-context-spec.js
Expand Up @@ -4,40 +4,7 @@ const {
SequentialRefreshTokenAuthenticator} = require('@smartthings/core-sdk')
const SmartApp = require('../../lib/smart-app')
const SmartAppContext = require('../../lib/util/smart-app-context')

class ContextStore {
constructor() {
this.contexts = {}
}

get(installedAppId) {
return new Promise(resolve => {
resolve(this.contexts[installedAppId])
})
}

put(params) {
this.contexts[params.installedAppId] = params
return new Promise(resolve => {
resolve()
})
}

update(installedAppId, params) {
this.contexts[params.installedAppId].authToken = params.authToken
this.contexts[params.installedAppId].refreshToken = params.refreshToken
return new Promise(resolve => {
resolve()
})
}

delete(installedAppId) {
this.contexts[installedAppId] = null
return new Promise(resolve => {
resolve()
})
}
}
const ContextStore = require('../utilities/context-store')

describe('smartapp-context-spec', () => {
let app
Expand Down
32 changes: 32 additions & 0 deletions test/unit/smartapp-spec.js
@@ -1,4 +1,5 @@
const SmartApp = require('../../lib/smart-app')
const ContextStore = require('../utilities/context-store')

describe('smartapp-spec', () => {
let app
Expand Down Expand Up @@ -66,4 +67,35 @@ describe('smartapp-spec', () => {

logSpy.mockClear()
})

it('should construct mutext for page event', async () => {
let mutex
app.contextStore(new ContextStore())
app.page('mainPage', (context, _) => {
mutex = context.apiMutex
})

const pageEvent = {
lifecycle: 'CONFIGURATION',
executionId: '00000000-0000-0000-0000-000000000000',
locale: 'en',
version: '0.1.0',
client: {
os: 'ios',
version: '0.0.0',
language: 'fr'
},
configurationData: {
installedAppId: '00000000-0000-0000-0000-000000000000',
phase: 'PAGE',
pageId: 'mainPage',
previousPageId: '',
config: {}
},
settings: {}
}

await expect(app.handleMockCallback(pageEvent)).resolves.not.toThrow()
expect(mutex).toBeDefined()
})
})
39 changes: 39 additions & 0 deletions test/utilities/context-store.js
@@ -0,0 +1,39 @@
class ContextStore {
constructor() {
this.contexts = {
'00000000-0000-0000-0000-000000000000': {
locationId: 'e9a56178-3518-49f3-b944-a25ac941c3bd'
}
}
}

get(installedAppId) {
return new Promise(resolve => {
resolve(this.contexts[installedAppId])
})
}

put(params) {
this.contexts[params.installedAppId] = params
return new Promise(resolve => {
resolve()
})
}

update(installedAppId, params) {
this.contexts[params.installedAppId].authToken = params.authToken
this.contexts[params.installedAppId].refreshToken = params.refreshToken
return new Promise(resolve => {
resolve()
})
}

delete(installedAppId) {
this.contexts[installedAppId] = null
return new Promise(resolve => {
resolve()
})
}
}

module.exports = ContextStore

0 comments on commit 34b50d5

Please sign in to comment.