[hydrawise] Handle API auth changes (#16221)

* Handles a new condition where the service rejects a request as unauthorized, but really we just need to refresh our token after 60 seconds.

Signed-off-by: Dan Cunningham <dan@digitaldan.com>
This commit is contained in:
Dan Cunningham 2024-01-07 05:39:38 -08:00 committed by GitHub
parent 90480d0a10
commit c8d2d4b171
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 36 additions and 9 deletions

View File

@ -324,6 +324,8 @@ public class HydrawiseGraphQLClient {
} catch (ExecutionException e) {
// Hydrawise returns back a 40x status, but without a valid Realm , so jetty throws an exception,
// this allows us to catch this in a callback and handle accordingly
logger.debug("ExecutionException", e);
logger.debug("ExecutionException {} {}", responseCode.get(), responseMessage);
switch (responseCode.get()) {
case 401:
case 403:

View File

@ -62,6 +62,7 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access
* Minimum amount of time we can poll for updates
*/
private static final int MIN_REFRESH_SECONDS = 30;
private static final int TOKEN_REFRESH_SECONDS = 60;
private static final String BASE_URL = "https://app.hydrawise.com/api/v2/";
private static final String AUTH_URL = BASE_URL + "oauth/access-token";
private static final String CLIENT_SECRET = "zn3CrjglwNV1";
@ -74,6 +75,7 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access
private @Nullable OAuthClientService oAuthService;
private @Nullable HydrawiseGraphQLClient apiClient;
private @Nullable ScheduledFuture<?> pollFuture;
private @Nullable ScheduledFuture<?> tokenFuture;
private @Nullable Customer lastData;
private int refresh;
@ -102,6 +104,7 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access
public void dispose() {
logger.debug("Handler disposed.");
clearPolling();
clearTokenRefresh();
OAuthClientService oAuthService = this.oAuthService;
if (oAuthService != null) {
oAuthService.removeAccessTokenRefreshListener(this);
@ -184,20 +187,40 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access
pollFuture = scheduler.scheduleWithFixedDelay(this::poll, initalDelay, refresh, TimeUnit.SECONDS);
}
/**
* The API will randomly reject a request with a 401 not authorized, waiting a min and refreshing the token usually
* fixes it
*/
private synchronized void retryToken() {
clearTokenRefresh();
tokenFuture = scheduler.schedule(() -> {
try {
OAuthClientService oAuthService = this.oAuthService;
if (oAuthService != null) {
oAuthService.refreshToken();
initPolling(0, MIN_REFRESH_SECONDS);
}
} catch (OAuthException | IOException | OAuthResponseException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
}, TOKEN_REFRESH_SECONDS, TimeUnit.SECONDS);
}
/**
* Stops/clears this thing's polling future
*/
private void clearPolling() {
ScheduledFuture<?> localFuture = pollFuture;
if (isFutureValid(localFuture)) {
if (localFuture != null) {
localFuture.cancel(false);
}
}
clearFuture(pollFuture);
}
private boolean isFutureValid(@Nullable ScheduledFuture<?> future) {
return future != null && !future.isCancelled();
private void clearTokenRefresh() {
clearFuture(tokenFuture);
}
private void clearFuture(@Nullable final ScheduledFuture<?> future) {
if (future != null) {
future.cancel(true);
}
}
private void poll() {
@ -232,8 +255,10 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
} catch (HydrawiseAuthenticationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
logger.debug("Token has been rejected, will try to refresh token in {} secs: {}", TOKEN_REFRESH_SECONDS,
e.getLocalizedMessage());
clearPolling();
retryToken();
}
}
}