Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,31 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
- Added `IterableApi.switchProject(context, apiKey, config, callback)` for apps that need to move a running install from one Iterable project to another (for example a multi-region app switching between a US and an EU project) without an app restart. The method returns immediately and performs the whole sequence on the SDK's background executor: it disables the push token on the previous project, clears that project's identity from memory and from storage, drops its cached in-app, embedded and unknown-user state along with its activation criteria and push attribution, purges the persisted offline queue, rebuilds the auth manager and the keychain against the new config, and re-initializes with the new API key. SDK calls made between the call and the callback are queued and then run in order against the new project.
- The callback is a new `IterableProjectSwitchCallback`, delivered on the main thread. Its single method `onProjectSwitched(boolean cleanTeardown)` reports whether every cleanup step completed cleanly; `false` means the SDK **is** on the new project but at least one cleanup step was noisy, never that the switch failed. It is a single-method interface so that a lambda receives the result: reusing `IterableInitializationCallback` would not work, because its only abstract method takes no arguments and a lambda would bind to that one and discard the result. `false` is expected in normal operation and is not an error: an app that does not use push, or that has no device token yet, always sees it, because the switch cannot confirm a device disable for the previous project. Either way, carry on and re-identify the user.
- Calls made between `switchProject` and its callback are queued and replayed against the new project, except for `trackPushOpen`. A push payload carries the campaignId, templateId and messageId of the project that sent it, so replaying it would report it against a project where those IDs do not exist; it runs inline instead, against whichever project is live at the time. Queueing during ordinary background initialization is unchanged.
- Per-project state held on the shared instance is cleared as part of the switch: the inbox session ID, the stored push payload and notification data, and any device attributes set with `setDeviceAttribute`. The device ID and visitor consent are project-agnostic and are deliberately kept. Set device attributes again from the callback if they still apply to the new project.
- Called with an empty or whitespace-only API key, nothing is torn down, the SDK stays on the project it is already on, and the callback reports `false`. Called before any `initialize` the callback also reports `false`, because no teardown ran and no device disable could be confirmed.
- `switchProject` does not re-identify the user. Call `setEmail`/`setUserId` from the callback. The JWT auth retry budget does not carry over: it is per auth manager instance, the switch rebuilds the auth manager, and identifying a user clears it besides.
- The disable for the previous project captures that project's API key **and** its region endpoint when it is initiated, and the switch waits (up to 2 seconds) for the disable to be handed to the request layer before swapping. `users/disableDevice` is project-scoped on the backend, so without the captured key the disable would usually land on the new project instead, leaving the previous project still delivering push to the device; without the captured endpoint a switch between data regions would send the previous project's key to the new project's region, which is rejected. If the wait times out the switch still completes and the callback reports `false`, and the disable that dispatches afterwards still reaches the project it was created for.
- A `true` callback means every teardown step completed at the point it fired. It is not a guarantee that the device disable reached the network: the disable is handed to the request layer, which may queue it, and the callback is not held open for the response. This matches the iOS SDK. An app that needs certainty about the outgoing project's device state should not infer it from this callback.
- Called before any `initialize`, it behaves as `initializeInBackground` and logs a warning. Called with the API key already in use, it is a no-op reporting `true`. Called while an initialization is still in flight, it waits for that initialization and then switches. Called while a switch is already running, the callback is added to that switch instead of starting a second teardown.
- Passing a null `context` or `apiKey` throws `IllegalArgumentException`. Both parameters are `@NonNull`, so a null is a programmer error rather than a runtime condition, and reporting it through the callback would overload the same boolean that means "switched, but noisily".
- The SDK now logs a warning when a push is received while a project switch is in progress, so a push that was sent by the previous project can be recognised in logs.

### Fixed
- Restored offline support for `disablePush()`. When offline mode is enabled, a `users/disableDevice` request made while the network is unavailable is once again persisted and retried instead of being dropped. This behaviour shipped in 3.5.16 and regressed in 3.7.0, leaving Android as the only SDK that silently lost a device disable when the network was down. A queued `disableDevice` is now also preserved across logout (`setEmail`/`setUserId` to a different user), so the disable still reaches the user it was created for.
- `users/registerDeviceToken` is now queued in offline mode as well, matching the iOS SDK. Push registration made while the network is unavailable is retried instead of being lost, and because the offline queue drains in `scheduledAt` order, a logout-then-login sequence replays as disable-then-register and leaves the device enabled. Note that the offline queue only drains while the app is in the foreground, so a Firebase token refresh received in the background is now sent on the next foreground rather than immediately.
- A queued request that is discarded before it can be sent now calls its failure handler instead of never calling back at all. This matters most for the completion handlers passed to `setEmail`/`setUserId`: they travel with the queued `users/registerDeviceToken`, so logging in as a different user used to strand them, and an app that dismisses a login spinner in that callback would wait forever. The failure reason states that the request was discarded because the user logged out.
- Offline requests now persist the API endpoint they were created for, so a queued request that is replayed later is always sent to the region it was created for with the key it was created with. Previously a rehydrated request fell back to whichever data region was live at flush time, which could send a request built for one project to another project's region. Requests already on disk from an earlier SDK version keep resolving their endpoint the old way, so no queued work is lost on upgrade.
- Switching projects no longer discards a queued `users/disableDevice` request. The rest of the offline queue is still dropped, but the device disable is kept so the token is still disabled on the project being left, even when the switch happens offline. This matches the iOS SDK.
- `IterableApi.setEmail` and `IterableApi.setUserId`'s longest overloads (the ones taking an identity resolution and both callbacks) are now queued while the SDK is initializing or switching projects, like every shorter overload already was. An app calling those overloads directly previously bypassed the queue.
- `IterableApi.track`'s and `IterableApi.updateEmail`'s longest overloads are now queued as well, for the same reason. They were public and ran inline while every shorter overload was queued, so behaviour during initialization or a project switch depended on which overload the app happened to call.
- `switchProject` no longer starts a teardown while a background initialization is still in flight, and a callback passed to `initializeInBackground` during a switch is no longer dropped.
- `switchProject` now recovers if the SDK's background executor is shut down at the moment the teardown or the queue drain is submitted, which could happen when an app called `switchProject` again from inside a switch callback. Previously that could either crash the app or leave the SDK permanently unable to execute queued calls.
- The auth token ready listener list no longer accepts the same listener twice, which could make the offline task runner process an auth recovery more than once.
- SDK calls are no longer able to slip past the initialization/switch gate. The gate was read and acted on separately, so a call could pass the check just before a switch raised it and then run against a half torn-down SDK; the check and the queueing are now a single atomic step, as they already are on iOS.

## [3.10.0]
### Added
Expand Down
Loading
Loading