From fabd4a6c66e9a11c8f03bf1241e949143023af92 Mon Sep 17 00:00:00 2001 From: poblouin Date: Sat, 5 Feb 2022 08:58:48 -0500 Subject: [PATCH] catch all thrown errors --- package.json | 2 +- src/constants.ts | 4 --- src/platform.ts | 22 ++++++++++--- src/spotify-api-wrapper.ts | 43 +++++++++++--------------- src/spotify-smart-speaker-accessory.ts | 2 +- src/spotify-speaker-accessory.ts | 2 +- src/types.ts | 2 +- tsconfig.json | 2 +- 8 files changed, 40 insertions(+), 39 deletions(-) delete mode 100644 src/constants.ts diff --git a/package.json b/package.json index b233404..39bc9ae 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "displayName": "Homebridge Spotify Speaker", "name": "@poblouin/homebridge-spotify-speaker", - "version": "0.0.5-beta", + "version": "0.1.0", "description": "Homebridge plugin that creates a speaker that plays a specific Spotify playlist", "license": "MIT", "author": "Pierre-Olivier Blouin ", diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index 36f178c..0000000 --- a/src/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const DEFAULT_SPOTIFY_CALLBACK = 'https://example.com/callback'; -export const SPOTIFY_MISSING_CONFIGURATION_ERROR = 'SPOTIFY_MISSING_CONFIGURATION_ERROR'; -export const SPOTIFY_AUTH_ERROR = 'SPOTIFY_AUTH_ERROR'; -export const SPOTIFY_REFRESH_TOKEN_ERROR = 'SPOTIFY_REFRESH_TOKEN_ERROR'; diff --git a/src/platform.ts b/src/platform.ts index cfd7eb2..5cf4949 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -12,7 +12,6 @@ import { PLATFORM_NAME, PLUGIN_NAME } from './settings'; import { SpotifySmartSpeakerAccessory } from './spotify-smart-speaker-accessory'; import { SpotifyFakeSpeakerAccessory } from './spotify-speaker-accessory'; import { SpotifyApiWrapper } from './spotify-api-wrapper'; -import { SPOTIFY_MISSING_CONFIGURATION_ERROR } from './constants'; const DEVICE_CLASS_CONFIG_MAP = { speaker: SpotifyFakeSpeakerAccessory, @@ -27,12 +26,21 @@ export class HomebridgeSpotifySpeakerPlatform implements DynamicPlatformPlugin { constructor(public readonly log: Logger, public readonly config: PlatformConfig, public readonly api: API) { this.log.debug('Finished initializing platform:', this.config.name); + if (!config.spotifyClientId || !config.spotifyClientSecret || !config.spotifyAuthCode) { + this.log.error('Missing configuration for this plugin to work, see the documentation for initial setup'); + return; + } + this.spotifyApiWrapper = new SpotifyApiWrapper(log, config, api); this.api.on('didFinishLaunching', async () => { log.debug('Executed didFinishLaunching callback'); - await this.spotifyApiWrapper.authenticate(); + const isAuthenticated = await this.spotifyApiWrapper.authenticate(); + if (!isAuthenticated) { + return; + } + this.logAvailableSpotifyDevices(); this.discoverDevices(); }); @@ -51,6 +59,10 @@ export class HomebridgeSpotifySpeakerPlatform implements DynamicPlatformPlugin { discoverDevices() { for (const device of this.config.devices) { const deviceClass = this.getDeviceConstructor(device.deviceType); + if (!deviceClass) { + continue; + } + const uuid = this.api.hap.uuid.generate(device.spotifyDeviceId); const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); @@ -73,16 +85,16 @@ export class HomebridgeSpotifySpeakerPlatform implements DynamicPlatformPlugin { } } - private getDeviceConstructor(deviceType) { + private getDeviceConstructor(deviceType): typeof SpotifySmartSpeakerAccessory | typeof SpotifyFakeSpeakerAccessory| null { if (!deviceType) { this.log.error('It is missing the `deviceType` in the configuration.'); - throw new Error(SPOTIFY_MISSING_CONFIGURATION_ERROR); + return null; } return DEVICE_CLASS_CONFIG_MAP[deviceType]; } - private async logAvailableSpotifyDevices() { + private async logAvailableSpotifyDevices(): Promise { const spotifyDevices = await this.spotifyApiWrapper.getMyDevices(); this.log.info('Available Spotify devices', spotifyDevices); } diff --git a/src/spotify-api-wrapper.ts b/src/spotify-api-wrapper.ts index 947b2be..790e404 100644 --- a/src/spotify-api-wrapper.ts +++ b/src/spotify-api-wrapper.ts @@ -3,14 +3,9 @@ import fs from 'fs'; import { API, Logger, PlatformConfig } from 'homebridge'; import SpotifyWebApi from 'spotify-web-api-node'; -import { - DEFAULT_SPOTIFY_CALLBACK, - SPOTIFY_AUTH_ERROR, - SPOTIFY_MISSING_CONFIGURATION_ERROR, - SPOTIFY_REFRESH_TOKEN_ERROR, -} from './constants'; import { SpotifyPlaybackState, WebapiError } from './types'; +const DEFAULT_SPOTIFY_CALLBACK = 'https://example.com/callback'; export class SpotifyApiWrapper { private readonly authCode: string; private readonly persistPath: string; @@ -18,11 +13,6 @@ export class SpotifyApiWrapper { private spotifyApi: SpotifyWebApi; constructor(public readonly log: Logger, public readonly config: PlatformConfig, public readonly api: API) { - if (!config.spotifyClientId || !config.spotifyClientSecret || !config.spotifyAuthCode) { - this.log.error('Missing configuration for this plugin to work, see the documentation for initial setup'); - throw new Error(SPOTIFY_MISSING_CONFIGURATION_ERROR); - } - this.authCode = config.spotifyAuthCode; this.persistPath = `${api.user.persistPath()}/.homebridge-spotify-speaker`; @@ -33,24 +23,25 @@ export class SpotifyApiWrapper { }); } - async authenticate() { + async authenticate(): Promise { await this.fetchTokensFromStorage(); if (this.spotifyApi.getAccessToken()) { this.log.debug('Spotify auth success using saved tokens'); - return; + return true; } await this.authWithCodeGrant(); if (this.spotifyApi.getAccessToken()) { this.log.debug('Spotify auth success using authorization code flow'); - return; + return true; } this.log.error( `We could not fetch the Spotify tokens nor authenticate using the code grant flow. Please redo the manual login step, provide the new auth code in the config then try again.`, ); - throw new Error(SPOTIFY_AUTH_ERROR); + + return false; } persistTokens() { @@ -111,7 +102,7 @@ export class SpotifyApiWrapper { } } - private async authWithCodeGrant() { + private async authWithCodeGrant(): Promise { this.log.debug('Attempting the code grant authorization flow'); try { @@ -120,7 +111,6 @@ export class SpotifyApiWrapper { this.spotifyApi.setRefreshToken(data.body['refresh_token']); } catch (err) { this.log.error('Could not authorize Spotify:\n\n', err); - throw new Error(SPOTIFY_AUTH_ERROR); } } @@ -143,16 +133,15 @@ export class SpotifyApiWrapper { this.spotifyApi.setRefreshToken(tokens.refreshToken); this.log.debug('Successfully fetched the tokens from storage, going to refresh the access token'); - try { - await this.refreshToken(); - } catch { + const areTokensRefreshed = await this.refreshTokens(); + if (!areTokensRefreshed) { // Reset the creds since they are wrong, we will try the code auth grant instead. this.spotifyApi.resetCredentials(); } } // TODO: Implement retries, it failed me once. - async refreshToken() { + async refreshTokens(): Promise { try { const data = await this.spotifyApi.refreshAccessToken(); this.log.debug('The access token has been refreshed!'); @@ -161,8 +150,10 @@ export class SpotifyApiWrapper { this.persistTokens(); } catch (err) { this.log.debug('Could not refresh access token: ', err); - throw new Error(SPOTIFY_REFRESH_TOKEN_ERROR); + return false; } + + return true; } // TODO: Use decorator or prettier pattern. @@ -174,11 +165,13 @@ export class SpotifyApiWrapper { if ((error as WebapiError).statusCode === 401) { this.log.debug('Access token has expired, attempting token refresh'); - await this.refreshToken(); - return cb(); + const areTokensRefreshed = await this.refreshTokens(); + if (areTokensRefreshed) { + return cb(); + } } - throw error; + this.log.error('Unexpected error when making a request to Spotify:', error); } } } diff --git a/src/spotify-smart-speaker-accessory.ts b/src/spotify-smart-speaker-accessory.ts index b4ecdcf..2559b72 100644 --- a/src/spotify-smart-speaker-accessory.ts +++ b/src/spotify-smart-speaker-accessory.ts @@ -68,7 +68,7 @@ export class SpotifySmartSpeakerAccessory { } }, SpotifySmartSpeakerAccessory.DEFAULT_POLL_INTERVAL_MS); - setInterval(() => this.platform.spotifyApiWrapper.refreshToken(), SpotifySmartSpeakerAccessory.DAY_INTERVAL); + setInterval(() => this.platform.spotifyApiWrapper.refreshTokens(), SpotifySmartSpeakerAccessory.DAY_INTERVAL); } handleCurrentMediaStateGet(): number { diff --git a/src/spotify-speaker-accessory.ts b/src/spotify-speaker-accessory.ts index 39da54a..48c1676 100644 --- a/src/spotify-speaker-accessory.ts +++ b/src/spotify-speaker-accessory.ts @@ -61,7 +61,7 @@ export class SpotifyFakeSpeakerAccessory { } }, SpotifyFakeSpeakerAccessory.DEFAULT_POLL_INTERVAL_MS); - setInterval(() => this.platform.spotifyApiWrapper.refreshToken(), SpotifyFakeSpeakerAccessory.DAY_INTERVAL); + setInterval(() => this.platform.spotifyApiWrapper.refreshTokens(), SpotifyFakeSpeakerAccessory.DAY_INTERVAL); } handleOnGet(): boolean { diff --git a/src/types.ts b/src/types.ts index 5007463..21e5acd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,6 @@ export interface HomebridgeSpotifySpeakerDevice { deviceName: string; - deviceStartVolume: number; + deviceType: string; spotifyDeviceId: string; spotifyPlaylistId: string; } diff --git a/tsconfig.json b/tsconfig.json index 3e8a160..d442fe1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ "strict": true, "esModuleInterop": true, "noImplicitAny": false, - "experimentalDecorators": true + "strictPropertyInitialization": false }, "include": ["src/"], "exclude": ["**/*.spec.ts"]