Skip to content

feat(SDK-520): replace magic 1000ms timeouts with named, configurable…#882

Open
jferrao-itrbl wants to merge 1 commit into
masterfrom
feature/SDK-520-replace-hardcoded-magic-timeouts
Open

feat(SDK-520): replace magic 1000ms timeouts with named, configurable…#882
jferrao-itrbl wants to merge 1 commit into
masterfrom
feature/SDK-520-replace-hardcoded-magic-timeouts

Conversation

@jferrao-itrbl

@jferrao-itrbl jferrao-itrbl commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

🔹 JIRA Ticket(s) if any

✏️ Description

Replace hardcoded magic timeouts:

  • Add IterableConfig.androidWakeDelayMs and authCallbackTimeoutMs to expose the two hardcoded timeouts as tunable, documented values.
  • Replace the fixed-window auth callback gate with an event-driven Promise latch; the timer survives only as a safety-net fallback.

… constants

- Add IterableConfig.androidWakeDelayMs and authCallbackTimeoutMs to
  expose the two hardcoded timeouts as tunable, documented values.
- Replace the fixed-window auth callback gate with an event-driven
  Promise latch; the timer survives only as a safety-net fallback.
@jferrao-itrbl jferrao-itrbl requested a review from a team July 2, 2026 14:18
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Lines Statements Branches Functions
Coverage: 71%
71.22% (562/789) 60.86% (224/368) 67.05% (173/258)

@qltysh

qltysh Bot commented Jul 2, 2026

Copy link
Copy Markdown

Qlty


⚠️ Comments skipped @jferrao-itrbl doesn't have a Qlty seat in Iterable.

Qlty doesn't post analysis or coverage comments for contributors without a seat. An authorized user can grant @jferrao-itrbl a seat from this pull request's page in Qlty.

Comment on lines +1136 to +1153
Promise.race<AuthLatchResult>([nativeLatch, timeoutLatch]).then(
(result) => {
// Clear the resolver so a late native event after the timeout
// is a no-op.
authLatchResolver = null;
pendingAuthResult = null;
if (result === IterableAuthResponseResult.SUCCESS) {
promiseResult.successCallback?.();
} else if (result === IterableAuthResponseResult.FAILURE) {
// We are currently only reporting JWT related errors. In
// the future, we should handle other types of errors as
// well.
promiseResult.failureCallback?.();
} else {
IterableLogger?.log('No callback received from native layer');
}
} else {
IterableLogger?.log('No callback received from native layer');
}
}, 1000);
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

authLatchResolver and pendingAuthResult are shared across every handleAuthCalled, so overlapping auth invocations can wipe each other's state.

Concrete failure:

  1. Auth # 1: handleAuthCalled resets, authHandler resolves, latch # 1 created, authLatchResolver = resolve1, race # 1 starts.
  2. Auth # 2: handleAuthCalled fires while # 1 is still racing. Reset nulls authLatchResolver, authHandler # 2 resolves, latch # 2 created, authLatchResolver = resolve2.
  3. Race # 1's safety-net timer fires, resolves race # 1 with NO_CALLBACK. The .then handler runs authLatchResolver = null and pendingAuthResult = null, wiping resolve2 and any buffered result belonging to # 2.
  4. Native handleAuthSuccessCalled for either invocation now has nowhere to land. Native completes the request, JS silently drops the successCallback / failureCallback.

In production this looks like JWT refresh flows that succeed on the server but the app never learns about it.

Please scope this state per invocation, e.g. capture the resolver/buffer inside a per-call object and only null out that object's fields in its own .then. A test that fires two handleAuthCalled events back to back and asserts both successCallbacks are invoked would lock this down.

* auth success/failure event arrives. Overridable via
* {@link IterableConfig.authCallbackTimeoutMs}.
*/
const AUTH_CALLBACK_TIMEOUT_DEFAULT_MS = 1000;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Default of 1000ms defeats the "safety net" framing.

Native success/failure only fires after passAlongAuthToken and a full HTTP round-trip against Iterable, which routinely exceeds 1s on real mobile networks. With this default, the timer wins the race first, the resolver-clearing branch runs, and the late native event ends up hitting the null-resolver path so successCallback / failureCallback gets dropped.

Native waits up to 30s for us on both platforms (CountDownLatch.await(30, TimeUnit.SECONDS) on Android, authHandlerSemaphore.wait(timeout: 30.0) on iOS), so JS closing the loop at 1s is asymmetric.

My suggestion would be bumping the default to 5000-10000ms so the timer is genuinely a fallback for stuck native, not a race the timer usually wins.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants