Skip to content

fix: users logged out despite a valid refresh token (PROD-8704) - #250

Draft
deaflynx wants to merge 3 commits into
thingsboard:develop/1.9.0from
deaflynx:fix/prod-8704-refresh-token-wipe
Draft

fix: users logged out despite a valid refresh token (PROD-8704)#250
deaflynx wants to merge 3 commits into
thingsboard:develop/1.9.0from
deaflynx:fix/prod-8704-refresh-token-wipe

Conversation

@deaflynx

Copy link
Copy Markdown
Contributor

Context (PROD-8704)

The customer-facing bug — users logged out after a long idle despite a valid refresh token — is fixed in the dart client: thingsboard/thingsboard-dart-client#6 (the client cleared the session on any transport-failed token refresh).

This PR (app-side)

A small crash guard surfaced while reproducing the issue on device: TbClientService.onClientError force-unwrapped ThingsboardError.message, throwing Null check operator used on a null value on an offline cold start when the client reports an error whose message is null.

  • onClientError now falls back to the localized unknownError, matching the existing _getMessage pattern in the same file.

CE change; merges to PE. Verified on device (Pixel 6): 0 null-check crashes on the offline-resume path that previously produced them.

onClientError force-unwrapped ThingsboardError.message, throwing
"Null check operator used on a null value" on an offline cold start when
the client surfaces an error whose message is null. Fall back to the
localized unknownError, matching the existing _getMessage pattern in the
same file.

The session-loss root cause is fixed in the dart client (PROD-8704); this
is the accompanying app-side crash guard.

@deaflynx deaflynx left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Reviewed 1 changed file in fix: avoid crash rendering a null client error message (PROD-8704). Left 1 comment inline.

The null-safe fallback is correct and matches _getMessage in the same file. The inline comment is about the same force-unwrap surviving in two sibling call sites and about pulling the fallback into one helper.

Additional findings

These observations are about existing code outside the PR's diff — spotted while reading surrounding context.

  • lib/utils/providers/error_provider/error_provider.dart:22 and lib/core/context/tb_context.dart:142 — both still do showErrorNotification((_) => tbError.message!), i.e. the exact crash this PR fixes. TbContext is currently commented out in locator.dart, but the Error Riverpod provider is the same shape as onClientError; if either is wired to the client's onError, the same null message will throw there. Worth fixing in the same pass (see the inline comment for a shared helper).
  • lib/utils/services/tb_client_service/tb_client_service.dart:66onInitError doesn't branch on Utils.isConnectionError the way onClientError does a few lines below. With the dart client now rethrowing transport failures from init() (thingsboard-dart-client#6) while keeping the tokens, an offline cold start reaches this path with message: 'Unable to connect'. If I'm reading the client right, the interceptor also fires the onError callback for the same failure, so the user sees the connection-error dialog and a "Fatal application error occurred / Unable to connect" dialog. Probably not for this PR, but a connection-aware branch here (or a short-circuit when the error is a connection error) would make the offline-resume path much less alarming.
  • lib/utils/utils.dart:312isConnectionError classifies by e.message == 'Unable to connect', an exact English string produced inside the dart client's toThingsboardError. Anything the client reports without that precise wording (including a null message) falls through to the generic toast instead of the connection dialog, and a wording change upstream silently reroutes UI here. ThingsboardError carries errorCode, status and the wrapped error (a DioException whose type/error identify a SocketException or timeout), so keying off those would be more durable. Separate cleanup.

This review was auto-generated. Findings may contain errors — please verify before applying changes.

}
_overlayService.showErrorNotification((_) => e.message!);
_overlayService.showErrorNotification(
(context) => e.message ?? S.of(context).unknownError,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback itself is right. Two thoughts while here:

This is now the fourth copy of e.message ?? S.of(context).unknownError_getMessage just above (line 60), Error._getMessage in error_provider.dart:28, and the nested getMessage in TbContext.onFatalError (tb_context.dart:123). Meanwhile error_provider.dart:22 and tb_context.dart:142 still force-unwrap tbError.message! — the exact crash this PR fixes. Would it be worth a tiny extension next to the other translation helpers, e.g. in lib/utils/translation_utils.dart:

extension ThingsboardErrorTranslation on ThingsboardError {
  String translatedMessage(BuildContext context) =>
      message ?? S.of(context).unknownError;
}

Then this becomes (context) => e.translatedMessage(context), _getMessage shrinks to the fatalApplicationErrorOccurred prefix plus that call, and the two remaining ! sites get a drop-in replacement instead of waiting for the same crash report from whichever path is still live. It also gives you something cheap to cover with a testWidgets that pumps a MaterialApp with S.delegate and asserts both branches — there's no test for the new behavior right now (and no test/ dir at all), and since the bug was precisely a null-handling assumption, pinning it seems worth a few lines.

On UX: the user now gets a 2-second toast saying "Unknown error." with nothing to act on or quote to support. Since we only land here when the client gave no message, errorCode/status are usually still populated — folding a short code into the toast (there's already an errorOccured: "Error occured: {e}" string of that shape) tends to pay for itself in support triage. Fine to leave as-is for the crash fix; just flagging the trade-off.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f6a8885.

  • Added ThingsboardErrorTranslation.translatedMessage(context) in lib/utils/translation_utils.dart.
  • All four message ?? unknownError copies now go through it (_getMessage here, Error._getMessage, the nested getMessage in TbContext.onFatalError, and this toast).
  • Replaced the two remaining tbError.message! force-unwraps in Error.onError and TbContext.onError with the same call.

Skipped the widget test: the project has no test/ directory, so adding one for this alone is out of scope for this PR. Left the toast text as-is; folding in errorCode/status needs a new localized string, so I'd take that as a follow-up.

@deaflynx deaflynx added the bug Something isn't working label Sep 3, 2026
@deaflynx deaflynx added this to the 1.9.0 milestone Sep 3, 2026
…andlers (PROD-8704)

Add ThingsboardErrorTranslation.translatedMessage and use it in place of the
four duplicated 'message ?? unknownError' expressions. Also replace the two
remaining 'tbError.message!' force-unwraps in Error.onError and
TbContext.onError, which would throw on the same null message.
@deaflynx

deaflynx commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Re the additional findings:

  • error_provider.dart:22 / tb_context.dart:142 force-unwraps: fixed in f6a8885 via the shared translatedMessage extension (see inline thread).
  • onInitError connection-error branch and isConnectionError matching on the exact 'Unable to connect' string: agreed these are worth doing, but they change the offline cold-start UX rather than fix the crash, so leaving them out of this PR and tracking as separate cleanup.

Replace the three identical _getMessage helpers in TbClientService, Error
provider and TbContext with translatedFatalErrorMessage next to the
ThingsboardError message extension.
@deaflynx deaflynx changed the title fix: avoid crash rendering a null client error message (PROD-8704) fix: users logged out despite a valid refresh token (PROD-8704) Sep 4, 2026
@vvlladd28
vvlladd28 marked this pull request as draft September 4, 2026 08:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant