diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index ff7da6d..cae6f7c 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -19,11 +19,11 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - + - name: npm test run: | npm ci npm run test-types --if-present npm run test-model --if-present env: - CI: true \ No newline at end of file + CI: true diff --git a/Changelog.md b/Changelog.md index edf1661..f84f2cd 100644 --- a/Changelog.md +++ b/Changelog.md @@ -137,6 +137,12 @@ - Adding more in-depth tests to further increase coverage around types and models, and adding more edge case API level tests +## 3.4.3 +- Long term fix for supporting older bridge types and creating new users. Issue #147 + +## 3.4.2 +- Temporary fix for older bridges that do not support the entertainment API. Issue #147 + ## 3.4.1 - Fixing issue with the lookup for the Hue motion sensor, issue #146 diff --git a/lib/api/Api.js b/lib/api/Api.js index 7b2d1e3..a9d75c1 100644 --- a/lib/api/Api.js +++ b/lib/api/Api.js @@ -122,7 +122,9 @@ module.exports = class Api { const self = this; if (self.isSyncing()) { - return self._syncPromise.then(() => {return self._state;}); + return self._syncPromise.then(() => { + return self._state; + }); } else { return Promise.resolve(self._state); } @@ -150,20 +152,24 @@ module.exports = class Api { syncWithBridge() { const self = this; - // We can only sync if there is a username passed to us, which will not be the case if we are creating the user - // first. - if (self._config.username) { - if (!self.isSyncing()) { - self._syncPromise = self.configuration.getAll() - .then(data => { - self._syncPromise = null; - self._state = new StateCache(data); - self._lastSyncTime = new Date().getTime(); - }) - .catch(() => { - self._syncPromise = null; - }); + if (!self.isSyncing()) { + if (self._config.username) { + // We can only sync if there is a username passed to us, which will not be the case if we are creating the user + // first. + self._syncPromise = self.configuration.getAll(); + } else { + // We can only obtain the open config when no user is passed in + self._syncPromise = self.configuration.getUnauthenticatedConfig(); } + + self._syncPromise = self._syncPromise.then(data => { + self._syncPromise = null; + self._state = new StateCache(data); + self._lastSyncTime = new Date().getTime(); + }) + .catch(() => { + self._syncPromise = null; + }); } } @@ -173,9 +179,10 @@ module.exports = class Api { * @returns {Promise} */ getLightDefinition(id) { - return this.getCachedState().then(() => { - return this._state.getLight(id); - }); + return this.getCachedState() + .then(() => { + return this._state.getLight(id); + }); } _getConfig() { @@ -189,4 +196,6 @@ module.exports = class Api { _getRemote() { return this._config.remote; } + + }; \ No newline at end of file diff --git a/lib/api/Configuration.js b/lib/api/Configuration.js index 4a43101..99e1df5 100644 --- a/lib/api/Configuration.js +++ b/lib/api/Configuration.js @@ -28,6 +28,14 @@ module.exports = class Configuration extends ApiDefinition { return this.execute(configurationApi.getFullState); } + /** + * Gets the aunauthenticated configuration data from the bridge. + * @returns {Promise} The unautenticated configuration data + */ + getUnauthenticatedConfig() { + return this.execute(configurationApi.getUnauthenticatedConfig); + } + /** * Updates a configuration value for the Hue Bridge. * @param data {Object | BridgeConfiguration} An Object (or BridgeConfiguration) representing the data that is to be diff --git a/lib/api/Users.js b/lib/api/Users.js index e0cdc4f..0a42ba6 100644 --- a/lib/api/Users.js +++ b/lib/api/Users.js @@ -72,7 +72,13 @@ module.exports = class Users extends ApiDefinition { * @returns {Promise>} */ createUser(appName, deviceName) { - return this.execute(configurationApi.createUser, {appName: appName, deviceName: deviceName, generateKey: true}); + return this.hueApi.getCachedState() + .then(state => { + //TODO may need to combine the modelid and API version, but am assuming that all newer bridges are kept up to + // date, as do not know the specific version number of the introduction of the generateclientkey parameter. + const oldBridge = state.modelid === 'BSB001'; + return this.execute(configurationApi.createUser, {appName: appName, deviceName: deviceName, generateKey: !oldBridge}); + }); } deleteUser(username) { diff --git a/lib/api/Users.test.js b/lib/api/Users.test.js index 99bbde8..3c96750 100644 --- a/lib/api/Users.test.js +++ b/lib/api/Users.test.js @@ -29,16 +29,16 @@ describe('Hue API #users', () => { it('should create a user when the link button has been pressed', async () => { const user = await unauthenticatedHue.users.createUser('node-hue-api', 'delete-tests'); - expect(user).to.have.length.greaterThan(39); + expect(user).to.have.property('username').to.have.length.greaterThan(39); + expect(user).to.have.property('clientkey'); createdUser = user; }); it('should not create a new user when link button not pressed', async () => { try { - const user = await unauthenticatedHue.users.createUser('node-hue-api', 'node-hue-api-tests'); - console.log(`Created user: ${user}`); - expect.fail('should not ger here unless the link button was pressed'); + await unauthenticatedHue.users.createUser('node-hue-api', 'node-hue-api-tests'); + expect.fail('should not get here unless the link button was pressed'); } catch (err) { expect(err).to.be.instanceof(ApiError); expect(err.getHueErrorType()).to.equal(101); @@ -46,6 +46,16 @@ describe('Hue API #users', () => { }); }); + //TODO would need to mock this now as it is based of the modelid of the bridge + // describe('#createUser() no clientkey', () => { + // + // it('should create a user when the link button has been pressed', async () => { + // const user = await unauthenticatedHue.users.createUser('node-hue-api', 'delete-tests', true); + // expect(user).to.have.property('username').to.have.length.greaterThan(39); + // expect(user).to.not.have.property('clientkey'); + // }); + // }); + describe.skip('#deleteUser()', () => { diff --git a/lib/api/http/endpoints/configuration.js b/lib/api/http/endpoints/configuration.js index 753e18e..98d17e7 100644 --- a/lib/api/http/endpoints/configuration.js +++ b/lib/api/http/endpoints/configuration.js @@ -43,7 +43,25 @@ module.exports = { .get() .acceptJson() .uri('/') + .pureJson(), +} + +function getUnAuthenticatedConfig() { + return new ApiEndpoint() + .get() + .acceptJson() + .uri('/config') .pureJson() + .postProcess(processUnauthenticatedData); +} + +module.exports = { + createUser: createUser(), + getConfiguration: getConfiguration(), + updateConfiguration: updateConfiguration(), + deleteUser: deleteUser(), + getFullState: getFullState(), + getUnauthenticatedConfig: getUnAuthenticatedConfig(), }; @@ -55,10 +73,17 @@ function processCreateUser(data) { } } + function createBridgeConfiguration(data) { return model.createFromBridge('configuration', null, data); } +function processUnauthenticatedData(data) { + return { + config: data + }; +} + function buildUserPayload(data) { //TODO utilize the type system //TODO perform validation on the strings here diff --git a/lib/api/stateCache.js b/lib/api/stateCache.js index e2b6ba7..518fbf1 100644 --- a/lib/api/stateCache.js +++ b/lib/api/stateCache.js @@ -28,5 +28,14 @@ class Cache { return light; } + + get modelid() { + // BSB001 is the first generation bridge, BSB002 is the current generation one that can support entertainment API + return this.data.config.modelid; + } + + get apiversion() { + return this.data.config.apiversion; + } } module.exports = Cache; \ No newline at end of file