diff --git a/src/smapi.ts b/src/smapi.ts index 5790919..c1a194a 100644 --- a/src/smapi.ts +++ b/src/smapi.ts @@ -248,32 +248,7 @@ class SonosSoap { getCredentialsForToken(token: string): SmapiToken | undefined { logger.debug("getCredentialsForToken called with: " + token); logger.debug("Current tokens: " + JSON.stringify(this.tokenStore.getAll())); - - // First try direct lookup - let smapiToken = this.tokenStore.get(token); - if (smapiToken) { - return smapiToken; - } - - // If not found, try to find by service token (for cases where token was refreshed) - // This is a fallback mechanism to handle token refresh scenarios - const allTokens = this.tokenStore.getAll(); - for (const [storedToken, storedSmapiToken] of Object.entries(allTokens)) { - try { - const verifyResult = this.smapiAuthTokens.verify(storedSmapiToken); - if (E.isRight(verifyResult)) { - // This token is valid, check if it matches our service token - const serviceToken = verifyResult.right; - // We can't easily extract the service token from the JWT without the key, - // so we'll rely on the direct lookup for now - } - } catch (error) { - // Token is invalid/expired, skip it - continue; - } - } - - return undefined; + return this.tokenStore.get(token); } associateCredentialsForToken(token: string, fullSmapiToken: SmapiToken, oldToken?:string) { logger.debug("Adding token: " + token + " " + JSON.stringify(fullSmapiToken)); @@ -572,7 +547,7 @@ function bindSmapiSoapServiceToExpress( logger.info("Token refresh successful, issuing new SMAPI token"); return smapiAuthTokens.issue(it.serviceToken); }), - TE.tap(swapToken(credentials?.loginToken?.token)), + TE.tap(swapToken(undefined)), // Don't pass old token to avoid circular reference issues TE.map((newToken) => ({ Fault: { faultcode: "Client.TokenRefreshRequired", @@ -641,7 +616,7 @@ function bindSmapiSoapServiceToExpress( return pipe( musicService.refreshToken(serviceToken), TE.map((it) => smapiAuthTokens.issue(it.serviceToken)), - TE.tap(swapToken(creds?.loginToken?.token)), // Pass the old token to be replaced + TE.tap(swapToken(undefined)), // Don't pass old token to avoid circular reference issues TE.map((it) => ({ refreshAuthTokenResult: { authToken: it.token, diff --git a/src/smapi_token_store.ts b/src/smapi_token_store.ts index bbe4466..49c88aa 100644 --- a/src/smapi_token_store.ts +++ b/src/smapi_token_store.ts @@ -41,11 +41,13 @@ export class InMemorySmapiTokenStore implements SmapiTokenStore { const smapiToken = this.tokens[tokenKey]; if (smapiToken) { const verifyResult = smapiAuthTokens.verify(smapiToken); + // Only delete if token verification fails with InvalidTokenError + // Do NOT delete ExpiredTokenError as those can still be refreshed if (E.isLeft(verifyResult)) { const error = verifyResult.left; - // Delete both invalid and expired tokens to prevent accumulation - if (error._tag === 'InvalidTokenError' || error._tag === 'ExpiredTokenError') { - logger.debug(`Deleting ${error._tag} token from in-memory store`); + // Only delete invalid tokens, not expired ones (which can be refreshed) + if (error._tag === 'InvalidTokenError') { + logger.debug(`Deleting invalid token from in-memory store`); delete this.tokens[tokenKey]; deletedCount++; } @@ -54,7 +56,7 @@ export class InMemorySmapiTokenStore implements SmapiTokenStore { } if (deletedCount > 0) { - logger.info(`Cleaned up ${deletedCount} token(s) from in-memory store`); + logger.info(`Cleaned up ${deletedCount} invalid token(s) from in-memory store`); } return deletedCount; @@ -140,11 +142,13 @@ export class FileSmapiTokenStore implements SmapiTokenStore { const smapiToken = this.tokens[tokenKey]; if (smapiToken) { const verifyResult = smapiAuthTokens.verify(smapiToken); + // Only delete if token verification fails with InvalidTokenError + // Do NOT delete ExpiredTokenError as those can still be refreshed if (E.isLeft(verifyResult)) { const error = verifyResult.left; - // Delete both invalid and expired tokens to prevent accumulation - if (error._tag === 'InvalidTokenError' || error._tag === 'ExpiredTokenError') { - logger.debug(`Deleting ${error._tag} token from file store`); + // Only delete invalid tokens, not expired ones (which can be refreshed) + if (error._tag === 'InvalidTokenError') { + logger.debug(`Deleting invalid token from file store`); delete this.tokens[tokenKey]; deletedCount++; } @@ -153,7 +157,7 @@ export class FileSmapiTokenStore implements SmapiTokenStore { } if (deletedCount > 0) { - logger.info(`Cleaned up ${deletedCount} token(s) from file store`); + logger.info(`Cleaned up ${deletedCount} invalid token(s) from file store`); this.saveToFile(); } diff --git a/src/sqlite_smapi_token_store.ts b/src/sqlite_smapi_token_store.ts index 988f2f0..8f3f21f 100644 --- a/src/sqlite_smapi_token_store.ts +++ b/src/sqlite_smapi_token_store.ts @@ -122,11 +122,13 @@ export class SQLiteSmapiTokenStore implements SmapiTokenStore { const smapiToken = tokens[tokenKey]; if (smapiToken) { const verifyResult = smapiAuthTokens.verify(smapiToken); + // Only delete if token verification fails with InvalidTokenError + // Do NOT delete ExpiredTokenError as those can still be refreshed if (E.isLeft(verifyResult)) { const error = verifyResult.left; - // Delete both invalid and expired tokens to prevent accumulation - if (error._tag === 'InvalidTokenError' || error._tag === 'ExpiredTokenError') { - logger.debug(`Deleting ${error._tag} token from SQLite store`); + // Only delete invalid tokens, not expired ones (which can be refreshed) + if (error._tag === 'InvalidTokenError') { + logger.debug(`Deleting invalid token from SQLite store`); this.delete(tokenKey); deletedCount++; } @@ -135,7 +137,7 @@ export class SQLiteSmapiTokenStore implements SmapiTokenStore { } if (deletedCount > 0) { - logger.info(`Cleaned up ${deletedCount} token(s) from SQLite store`); + logger.info(`Cleaned up ${deletedCount} invalid token(s) from SQLite store`); } return deletedCount;