Release 8.2.0 - Inbox v2 sync#1008
Conversation
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (75)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
|
@coderabbitai update PR description |
* task(SDK-5709): add Inbox V2 constants (T0.1) Adds the 5 shared constants (fetch type, response keys, prefs key, throttle window) that every Inbox V2 task reads from, so literal strings stay in one place and can't silently drift across files. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add sendInboxFetch and sendInboxDelete to CtApi (T0.2) Routes the V2 inbox to its dedicated endpoint so fetch/delete don't get batched into /a1 alongside regular events. Also teaches getUriForPath to split multi-segment relativeUrl on '/' before appending so paths like inbox/v2/getMessages aren't URL-encoded into a single %2F-laden segment; single-segment callers are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): parse isRead from V2 inbox JSON in CTMessageDAO (T0.3) Reads the new isRead boolean from V2 responses so cross-device read state survives into the DAO. Missing field defaults to unread, preserving V1 behaviour unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add CallResult sealed class for V2 network calls (DF1) Unifies the five possible outcomes of any single-shot V2 network call (success, throttled, disabled, HTTP error, transport failure) so callers pattern-match once and the compiler enforces exhaustive handling. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add EndpointCall interface for V2 network calls (DF2) Defines the single-method contract that every V2 endpoint implements, so orchestration, error mapping, and testing all plug into one shape regardless of whether the call is a fetch, delete, or future event push. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add NetworkScope coroutine owner per SDK instance (DF3) Introduces one CoroutineScope per CleverTap instance, backed by a SupervisorJob and an injectable dispatcher (default Dispatchers.IO), so V2 network coroutines can be launched with structured lifetimes and isolated failures without blocking the existing CTExecutorFactory threads. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add EventRequestBody helper for V2 call payloads (DF4) Centralizes the [header, event] JSON array layout every direct V2 call needs, so each EndpointCall builds only its own event object and never hand-rolls the surrounding array. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add FetchThrottle generic per-account rate limiter (DF5) Adds a reusable persistent throttle keyed by account id and pref name, so V2 fetch callers (public fetchInbox and pull-to-refresh) enforce the 5-min window across app restarts without baking feature-specific state into the class. Uses the existing Clock interface and TestClock for determinism. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add InboxFetchCall — first V2 EndpointCall implementor (DF6) Wires the V2 inbox fetch end-to-end: builds the wzrk_fetch event, wraps it in EventRequestBody, hits sendInboxFetch on the injected dispatcher, and maps each HTTP outcome to a CallResult (200→Success, 403→Disabled, else →HttpError, empty/parse/IO→NetworkFailure). Sets the per-endpoint error matrix pattern every future EndpointCall will follow. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add InboxV2Merger pure dual-filter functions (T1.4b) Extracts the V2 response merge math into a zero-dependency Kotlin object so the controller method can orchestrate DB and lock while the filter logic stays unit-testable with no mocks. Single predicate definition is shared by the pre-write and post-read passes so they can't drift. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add processV2Response orchestration to CTInboxController (T1.4) Threads the InboxV2Merger dual-filter through the controller's DB and lock: pre-write filter → upsert → re-read under messagesLock → post-read cleanup → batch delete of stale rows → in-memory swap. Returns whether anything changed so the response handler can decide when to fire the UI callback, matching the V1 updateMessages contract. Pending sets are empty today; T3.3 wires them to the pending_deletes/pending_reads tables. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add InboxV2Response mirroring V1 guards (T1.4a) Routes V2 fetch payloads through the same four safety checks V1 uses (analytics-only, key presence, parse try/catch, null-controller init) so the active-fetch path can't bypass guards the passive path enforces. Bridges to CTInboxController.processV2Response and fires inboxMessagesDidUpdate on real changes. Opens the minimum accessors the cross-package response handler needs: CTMessageDAO.initWithJSON becomes public and CTInboxController gains a getUserId() getter. Both classes remain @RestrictTo(LIBRARY) so consumer visibility is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add InboxV2Fetcher orchestrator (DF7) Puts the inbox-specific glue between the generic EndpointCall and the response handler: session-scoped disable flag, throttle gate (honoured for user-initiated triggers, bypassed for app-launch and onUserLogin), recordFetch on the allowed path, and hand-off to InboxV2Response on success so the lock, controller-init, and UI callback stay where T1.4a owns them. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add InboxV2Bridge Java-interop adapter and wire the V2 chain (DF8) Lets Java callers fire-and-forget an Inbox V2 fetch: the bridge launches the suspend fetcher on NetworkScope and delivers success/failure to an optional FetchInboxCallback. The factory now constructs the full InboxV2Response → InboxFetchCall → FetchThrottle → InboxV2Fetcher → InboxV2Bridge chain once per SDK instance and exposes the bridge through CoreState so the trigger wirings (T1.5/T1.6/T1.7) become one-liners. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): fetch Inbox V2 on cold app launch (T1.5) Drops a single bridge.submit(false, null) into ActivityLifeCycleManager's cold-launch block so the inbox is actively pulled once per app launch without a throttle. The factory now constructs the V2 chain before the lifecycle manager so the bridge is ready at wire time; activityLifeCycle- Manager and loginController keep their original relative order. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): fetch Inbox V2 after onUserLogin identity switch (T1.6) Hooks InboxV2Bridge.submit(false, null) into asyncProfileSwitchUser right after resetInbox() so the new identity's inbox loads immediately from the server instead of waiting on the next /a1 push. Throttle is bypassed — every identity switch is a fresh fetch. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): expose public fetchInbox() API on CleverTapAPI (T1.7) Gives customers with custom inbox UIs a way to force an on-demand refresh from the CleverTap servers. Two overloads (no-arg and callback-taking) route through InboxV2Bridge.submit with respectThrottle=true, so rapid user-initiated calls can't spam the endpoint. Null inbox controller is handled defensively: the callback fires with false, no network call. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): pull-to-refresh on built-in inbox via V2 fetch (T1.9) Wraps CTInboxListViewFragment's list in a SwipeRefreshLayout and routes the refresh gesture through the public fetchInbox API so the throttle applies and existing inboxMessagesDidUpdate path drives the adapter refresh. The outer wrapper delegates canChildScrollUp to whichever RecyclerView is actually on screen — mid-scroll upward drags scroll the list instead of falsely firing a refresh, which the default mTarget.canScrollVertically(-1) check on a LinearLayout target would otherwise do. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add message _id to Viewed and Clicked inbox events (T2.1) Inserts data.getMessageId() under evtData._id in pushInboxMessageStateEvent so backend can map Viewed/Clicked events to a specific inbox message and update server-side isRead. Without this field, cross-device read-state sync has no way to land. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): suppress rapid repeat inbox events on the same device (T2.2) Introduces a generic per-key sliding-window EventSuppressor backed by ConcurrentHashMap.put so two suppressors (Viewed 2s, Clicked 5s) can gate pushInboxMessageStateEvent without new locks. Closes the public-API dedup gap: customers building custom inboxes can't inflate analytics by firing Viewed on every scroll-into-view or double-tapping a message. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): skip Viewed events for already-read inbox messages (T2.3) Adds an early-return guard at the top of pushInboxMessageStateEvent: if the message is already read (V2 delivered isRead=true from another device), suppress the Viewed event to avoid cross-device duplicate view counts. Clicked semantics are unchanged — distinct taps on separate devices remain distinct engagement events. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): add pending-actions tables and DAO for offline resilience (T3.1) Adds two SQLite tables — inbox_pending_deletes and inbox_pending_reads — keyed by composite (USER_ID, ID) so a local delete/read intent survives app kill and a later V2 fetch can't resurrect a deleted message or overwrite a locally-read message until the server confirms the action. The DB migrates from v6 to v7 additively (no alteration of inboxMessages). The DAO mirrors existing conventions: belowMemThreshold guard, transaction- wrapped batch insert with CONFLICT_IGNORE, parameterized IN batch delete, cursor.use for reads, SQLiteException catches on writes. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): wire pending-reads into V2 fetch path (T3.3) Records a row in inbox_pending_reads whenever the user marks a message read, so a V2 fetch with a stale isRead=false can't un-read the message across app restart. processV2Response now reads both pending sets for real (replacing the T1.4 empty-set placeholder) and, once a server echo confirms a pending read, batch-removes the row. The cleanup runs BEFORE preWriteFilter because the merger's override mutates incoming DAOs in place and would otherwise make every pending id look server-confirmed. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): sync inbox deletes to the server with retry on init (T3.2) Adds an action-only EndpointCall<Unit> (InboxDeleteCall) and a coordinator that batches N deletes into one HTTP call, clears inbox_pending_deletes rows atomically on 2xx, and drains leftover pending rows at inbox-init time so a delete that failed offline eventually lands. Local intent is recorded before the server call so an app kill mid-sync can't lose it. Three literals are captured behind working assumptions, each flippable with a one-line edit once backend confirms: delete URL path, event name ("Message Deleted"), and the messages array container key. DBAdapter is fetched via a supplier so the coordinator's first DB load happens on the NetworkScope's IO dispatcher instead of the factory's main-thread construction path. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): make FetchThrottle in-memory (T4.1) Cross-session persistence was redundant. Every process start runs an un-throttled V2 fetch (cold launch and onUserLogin submit with respectThrottle=false), and InboxV2Fetcher calls recordFetch() before the endpoint runs regardless of the caller. The stored timestamp is therefore overwritten before any throttled caller can read it, so a per-instance AtomicLong yields identical observable behavior without the disk I/O, Context, config, and prefKey plumbing. Drops INBOX_V2_LAST_FETCH_TS_KEY, removes Robolectric from FetchThrottleTest, and simplifies construction in CleverTapFactory. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): tag inbox messages with V1/V2 source (T5.1) V1 and V2 inbox messages live in the same `inboxMessages` table but have divergent backend contracts: V2 events must carry `_id` and V2 deletes must sync to the v2 endpoint, while V1 must do neither. The new `InboxMessageSource` discriminator persists per-message on the DAO and a `source TEXT NOT NULL DEFAULT 'V1'` column (folded into the existing v7 migration — no version bump, branch is unreleased). `InboxResponse` tags parsed DAOs V1, `InboxV2Response` tags V2; `CTInboxController` exposes an `isV2Message(id)` helper so the gate sites in T5.2 / T5.3 can branch without reading source off the public `CTInboxMessage`. `CTInboxMessage` is intentionally untouched and `CTMessageDAO.toJSON()` does not include source, so the V1/V2 tag never reaches the public model via `getData()`. A regression test in `CTMessageDAOTest` asserts the JSON stays clean. No behavioral change yet; T5.2 and T5.3 consume the tag. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): include message _id only for V2 inbox events (T5.2) Backend rejects `_id` on Notification Viewed / Clicked events for V1 inbox messages. Gate the `_id` put in `AnalyticsManager.pushInboxMessageStateEvent()` on the T5.1 source tag via a private `isV2InboxMessage(msgId)` helper that consults `CTInboxController.isV2Message`. Null controller, null message id, and unknown ids all fall through to V1 behavior (no `_id` emitted). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): gate delete sync and pending tables on V2 source (T5.3) Backend does not handle V1 deletes, and V1 inbox messages have no server-side state to sync. Four sites in `CTInboxController` now gate on the T5.1 source tag: - `deleteInboxMessage` / `deleteInboxMessagesForIDs` only add to `inbox_pending_deletes` and invoke `InboxDeleteCoordinator.syncDelete` for V2 messages. V1 paths are local-only. - `_markReadForMessageWithId` / `_markReadForMessagesWithIds` only add to `inbox_pending_reads` for V2 messages. V1 markRead stays local. Batch paths (`deleteInboxMessagesForIDs`, `_markReadForMessagesWithIds`) iterate `messages` once under a single `messagesLock` with an id-set membership check — O(N+M) instead of O(N*M) per-id scans. Single-item paths read source directly off the DAO returned by `findMessageById`. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): QA polish — V2 event metadata fields and sync controller init - Stamp standard event metadata (s, pg, ep, f, lsl, pai, n) onto every V2 endpoint event body via a new buildInboxV2Event helper, so the V2 fetch and Message Deleted events match the shape of regular events the server expects. - Add ControllerManager.initializeInboxSync() and have InboxV2Response call it instead of the async initializeInbox(), so the very first V2 response on cold launch / post-login is no longer silently dropped while the controller is still initialising. - Verbose logging across the V2 call, fetcher, and coordinator paths to make the QA flow auditable. - Tests updated/added for all of the above. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): rename V2 inbox event id key from _id to wzrk_mid Backend updated the contract for V2 inbox Notification Viewed and Notification Clicked events: the message identifier now travels under the key wzrk_mid instead of _id. Plain key rename — V1 messages still emit no id field; V2 gating in isV2InboxMessage() unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): align V2 inbox delete with confirmed backend contract Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): persist full delete payload per pending row (T6.1) When a delete-sync fails, retryPending re-sends the deletes on the next inbox init. The retry payload was id-only — the local message was gone, so wzrk_id / wzrk_pivot / etc. weren't carried, and backend couldn't correlate the retried delete with the original campaign attribution. Adds a wzrkParams TEXT column to inbox_pending_deletes (folded into the unreleased v7 migration; no DATABASE_VERSION bump). The DAO now exposes two reads — getPendingDeleteIds(userId) for the merger's set-membership filter and getPendingDeletes(userId) returning a typed List<PendingDelete> for retry reconstruction. Single and batch insert methods take a wzrkParams JSONObject? alongside the id; addPendingDeletes operates on List<PendingDelete> so each row carries its own params. CTInboxController forwards dao.getWzrkParams() at delete time on both the single-message and batch-IDs paths, all within the existing single pass under messagesLock so source resolution still happens before the local cache wipe. InboxDeleteCoordinator.retryPending rebuilds each CTInboxMessage with id + wzrkParams so the retry POST body matches the initial sync body byte-for-byte. The merger is unchanged — it keeps reading getPendingDeleteIds for filtering. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): two-phase pending delete with TTL-driven cleanup (T6.2) Backend confirmed in QA that the V2 delete API has a 1-2 hour delete-propagation window: a 200 from /inbox/v2/deleteMessages only acknowledges the request was accepted; the message can still come back in V2 fetch responses for a couple of hours, and absence from a fetch is also non-authoritative. Treating 200 as "done" therefore lets a follow-up fetch resurrect the locally-deleted message into cache. Promotes inbox_pending_deletes through a two-state machine. New rows default to PENDING_SEND. On HTTP 200 the coordinator transitions the targeted rows to AWAITING_CONFIRM via markPendingDeletesAwaitingConfirm instead of deleting them, so InboxV2Merger keeps filtering the message out of incoming fetches via getPendingDeleteIds (both states). retryPending reads only PENDING_SEND rows. Final cleanup is TTL-driven: each pending row stores the message's wzrk_ttl as expires (folded into the v7 schema, no DATABASE_VERSION bump). When a V2 message lacks a usable TTL, CTInboxController falls back to now + 1 day via resolvePendingDeleteExpiry — comfortably past the propagation window without lingering forever. removeExpiredAwaitingConfirm sweeps state=AWAITING_CONFIRM AND expires<=now; it runs at the top of processV2Response (before reading the pending-delete id set so an expired row's id stops filtering the now-stale message) and at the start of retryPending (so devices that rarely fetch still self-clean). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): wire InboxV2Response into /a1 decorator chain (T7.1) Live-behaviour V2 inbox campaigns deliver inbox_notifs_v2 inside the /a1 event response. The decorator chain only knew about the V1 InboxResponse, so V2 payloads riding /a1 were silently dropped and live campaigns never reached the controller. InboxV2Response now extends CleverTapResponseDecorator with a 3-arg override that delegates to the existing single-arg processResponse, so the same parser handles both the direct fetch path and the /a1 decorator path. CleverTapFactory builds the instance once and splices it into cleverTapResponses next to InboxResponse; the V2 fetch pipeline keeps using the same instance. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): TTL-driven cleanup of pending_reads (T7.2) A pending_read row is cleared by the existing server-caught-up loop inside processV2Response — but only when the server eventually echoes the message back with isRead=1. Several paths starve that echo: user deletes the message after marking read, message TTL elapses on the backend, campaign retargeted, identity switch, etc. The row strands forever, overriding isRead on any future incoming message that happens to share the id. Mirroring the T6.2 pattern for pending_deletes: - inbox_pending_reads gains an `expires` column (folded into v7, no DATABASE_VERSION bump on this unreleased branch). - Captures wzrk_ttl at markRead time via the shared resolvePendingActionExpiry helper (renamed from resolvePendingDeleteExpiry now that both paths use it). 1-day fallback when the DAO has no usable TTL. - DAO grows addPendingRead(messageId, userId, expiresAt), batch addPendingReads(rows, userId), and removeExpiredPendingReads sweep. DBAdapter facade updated. - processV2Response sweeps expired pending_reads next to the existing AWAITING_CONFIRM sweep, before reading the pending sets. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): use controller-fresh DAO for inbox Viewed event CTInboxBaseMessageViewHolder.markItemAsRead synchronously mutates inboxMessage.setRead(true) on the UI thread before the async messageDidShow body runs. By the time pushInboxMessageStateEvent inspects data.isRead(), the UI mirror already reads true, which trips the T2.3 cross-device gate (skip Viewed if isRead) and drops the legitimate first Viewed event for the local mark. Switch the analytics path to use the freshly-fetched `message` from getInboxMessageForId (controller-backed, still read=false) and fire the event before markReadInboxMessage flips the controller state. Same external behavior, but the gate now sees the correct pre-mark state and the Viewed event reliably emits. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * task(SDK-5709): cross-device delete sync via index_state sweep (T7.3) Adds index_state column (PENDING_INDEXING | INDEXED) to inboxMessages. After each V2 fetch, incoming ids are bulk-promoted to INDEXED; V2 messages that are INDEXED (or stale PENDING_INDEXING older than the 6 h grace window) but absent from the fetch response are swept locally as cross-device deletes. Fresh PENDING_INDEXING rows are never swept, preventing false-positive removal of live-behaviour messages not yet indexed by the fetch backend. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * task(SDK-5709): wire FETCH sweep into processV2Response (T7.3 complete) Completes the cross-device delete sync feature started in the previous T7.3 commit (which added the DAO layer). Adds the controller-level sweep logic that runs on every V2 fetch response to remove locally-cached V2 messages that have been deleted on another device. Key changes: - InboxV2DeliverySource enum (FETCH | A1) disambiguates how a V2 payload arrived; the sweep is FETCH-only because only a fetch is a complete authoritative inbox snapshot. - InboxMessageDAO/Impl: findSweepableV2Ids(userId, staleCutoff) returns ids of INDEXED V2 rows and stale PENDING_INDEXING rows (older than the 6 h grace window) — both treated as reliable cross-device delete signals. - DBAdapter: wrappers for markIndexed and findSweepableV2Ids. - CTInboxController.processV2Response gains an InboxV2DeliverySource param and INDEXING_GRACE_SECONDS = 6 h constant. FETCH path: (1) bulk-promotes incoming ids to INDEXED, (2) sweeps absent sweepable ids from DB before postReadCleanup re-reads the full table. - InboxV2Response routes FETCH vs A1 source through to the controller. - 16 new tests across three test classes (DAO, controller, response). Co-authored-by: Cursor <[email protected]> * fix(SDK-5709): infinite-TTL guard, sweep verbose logs, and PBS INDEXED fix - Infinite TTL (ttl=0): resolvePendingActionExpiry now returns 0 (never expires) when the message TTL is 0, preventing premature expiry of fire-and-forget LBS non-persistent messages. DAO queries for removeExpiredAwaitingConfirm and removeExpiredPendingReads now include `expires > 0` so infinite-TTL rows are never purged. findSweepableV2Ids adds `expires != 0` to exclude infinite-TTL messages from cross-device sweep. - Sweep verbose logging: processV2Response now emits verbose logcat lines for markIndexed, sweep removals (or nothing-to-remove), and expired AWAITING_CONFIRM / pending-read row cleanup, enabling dev QA verification without a debugger. - PBS INDEXED bug fix: brand-new PBS rows delivered for the first time via FETCH were stored as PENDING_INDEXING because markIndexed ran before the row existed in the DB. processV2Response(FETCH) now stamps all toUpsert DAOs with indexState=INDEXED before calling upsertMessages, so the INSERT writes INDEXED directly. The ON CONFLICT clause intentionally omits index_state, so existing INDEXED rows are unaffected. Fixes PP-001 and PP-006 (cross-device sweep was blind until the second FETCH). Tests added: infinite-TTL guards in InboxMessageDAOImplTest, InboxPendingActionsDAOImplTest, and CTInboxControllerTest; regression test for the PBS INDEXED fix in CTInboxControllerTest. * fix(SDK-5709): record throttle only on Success/HttpError, not NetworkFailure A transport failure (timeout, no network) no longer stamps the 5-minute throttle window, so a pull-to-refresh or fetchInbox() retry is never silently swallowed after a failure where no server contact was made. HttpError still records the fetch — the server responded, so the throttle applies. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(SDK-5709): fix linting * fix(SDK-5709): dispatch fetchInbox failure callback on network thread The not-initialized early-exit in fetchInbox() was calling the callback synchronously on the caller's thread, violating the documented contract that callbacks fire on the SDK's network dispatcher. Routes it through InboxV2Bridge.submitFailure() so all callback paths use the same thread. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(SDK-5709): make EndpointCall a fun interface Single-abstract-method contract is now explicit in the type. Verified that fun interface with a suspend abstract method compiles on Kotlin 2.0.10. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(SDK-5709): remove dead networkScope and inboxDeleteCoordinator from CoreState Both fields were stored on CoreState but never accessed via it — each consumer already held its own injected reference (InboxV2Bridge and InboxDeleteCoordinator via constructor params, ControllerManager via setter). Removing the redundant fields keeps CoreState as a registry of things actually needed through it. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * task(SDK-5709): inject Clock into CTInboxController for testable time Replace raw System.currentTimeMillis() calls with Clock.currentTimeSeconds() via constructor injection; existing callers get Clock.SYSTEM through the delegating constructor. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(SDK-5709): replace Logger with ILogger * fix(SDK-5709): fix failing tests * task(SDK-5709): replace AtomicLong with @volatile for FetchThrottle timestamp Plain read in shouldThrottle and plain write in recordFetch need only volatile visibility, not atomic compound ops — @volatile is sufficient. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * test(SDK-5709): add DBAdapter V2 method tests and InboxV2Bridge submitFailure tests. Cover markIndexed, findSweepableV2Ids, all pending-delete/read CRUD methods, TTL-expiry sweeps, batch operations, and null/empty guards in DBAdapterTest. Add submitFailure happy-path and scope-cancellation tests in InboxV2BridgeTest. * fix(SDK-5709): when new user logs in, call fetch after arp is received * fix(SDK-5709): throttle only user-initiated inbox fetches; introduce FetchTrigger enum System calls (app-launch, onUserLogin) no longer record the throttle timestamp, so they cannot block a subsequent pull-to-refresh or fetchInbox() call. Only USER_INITIATED calls both check and record the throttle. Replaces the boolean respectThrottle flag with an enum class FetchTrigger for type-safe, self-documenting call sites. * inbox v2 pull-to-refresh disabled on 403 and restore V1 viewed event behaviour SDK-5709 (#1007) * fix(SDK-5709): hide pull-to-refresh on V2 403 and restore V1 viewed event behaviour Two behavioural fixes for the Inbox V2 rollout: 1. Pull-to-refresh (SwipeRefreshLayout) is now hidden — both eagerly on fragment creation and after the first fetch that returns 403 — so the gesture affordance disappears once the V2 endpoint is session-disabled. Exposes disabledForSession through InboxV2Fetcher → InboxV2Bridge → CleverTapAPI (@RestrictTo LIBRARY) for the fragment to query. 2. The cross-device read gate ("skip Viewed if isRead=true") is now guarded by isV2InboxMessage(), restoring pre-feature behaviour for V1 messages. Clients upgrading with V2 disabled (403) continue to receive Viewed events for already-read V1 messages, avoiding a silent regression. * fix(SDK-5709): disable swipe gesture only; setVisibility(GONE) hid the message list * fix(SDK-5709): reset throttle window on user login and consider non 200 status code as disable fetch * fix(SDK-5709): replace activity context with application context as per PR comment --------- Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]> Co-authored-by: Cursor <[email protected]>
ae9eaae to
e84c05e
Compare
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainLet me gather the PR details first before updating the description. 🏁 Script executed: Length of output: 46092 Now I have all the details. Let me compose and push a comprehensive PR description. 🏁 Script executed: Length of output: 746
SummaryImplements Inbox V2 sync for the CleverTap Android SDK (release 8.2.0). This PR introduces an active-fetch pipeline for the V2 inbox endpoint, cross-device read/delete synchronisation, offline resilience via pending-action tables, a public Jira: SDK-5709 MotivationThe existing inbox relied solely on passive delivery through the
Changes🔌 Network Fetch Layer (new)
📨 V2 Response Handling (new)
🔄 Inbox Orchestration (new)
🗄️ Database (modified/new)
🏷️ V1/V2 Source Tagging (new)
🔁 Cross-Device Sync
📲 Trigger Wiring
📡 Public API (new)
🎨 UI (modified)
📊 Analytics (modified)
Testing~20 new test files and significant additions to existing ones covering:
Backward Compatibility
|
* chore(SDK-5709): update docs
System calls (app-launch, onUserLogin) no longer record the throttle timestamp, so they cannot block a subsequent pull-to-refresh or fetchInbox() call. Only USER_INITIATED calls both check and record the throttle. Replaces the boolean respectThrottle flag with an enum class FetchTrigger for type-safe, self-documenting call sites.
* chore(SDK-5709): improve docs
System calls (app-launch, onUserLogin) no longer record the throttle timestamp, so they cannot block a subsequent pull-to-refresh or fetchInbox() call. Only USER_INITIATED calls both check and record the throttle. Replaces the boolean respectThrottle flag with an enum class FetchTrigger for type-safe, self-documenting call sites.
* chore(SDK-5709): bump version and run copyTemplates
System calls (app-launch, onUserLogin) no longer record the throttle timestamp, so they cannot block a subsequent pull-to-refresh or fetchInbox() call. Only USER_INITIATED calls both check and record the throttle. Replaces the boolean respectThrottle flag with an enum class FetchTrigger for type-safe, self-documenting call sites.
* chore(SDK-5709): update CTCORECHANGELOG.md regarding accounts not using v2 and uses 8.2.0
* chore(SDK-5709): update release date
* chore(SDK-5709): update sample app core version
Code Coverage Debug
Files |
No description provided.