Repository navigation
fix: [SDK-5466] send on_session again when a token arrives after the session started under IV - #1527
Merged
Merged
Conversation
This was referenced Oct 12, 2026
on_session again when a token arrives after the session started under IV
abdulraqeeb33
requested changes
Oct 21, 2026
abdulraqeeb33
left a comment
Contributor
There was a problem hiding this comment.
The page path does what the PR says: a 401 leaves _onSessionSent clear, and a newer token is kept. Two holes in the retry.
- On the worker, a 401 counts as sent, so
onSessionPendingnever sticks and a later token hits "session already active". - On the page,
_onSessionSentis set only after the request returns, so two token writes in that window both sendsession_count: 1.
fadi-george
reviewed
Oct 23, 2026
sherwinski
force-pushed
the
sherwin/sdk-5466
branch
from
October 23, 2026 19:26
c2fbb20 to
21a444b
Compare
abdulraqeeb33
requested changes
Oct 23, 2026
abdulraqeeb33
left a comment
Contributor
There was a problem hiding this comment.
A failed on_session writes back the session object captured before the request. That put can recreate a row finalizeSession already deleted.
fadi-george
approved these changes
Oct 23, 2026
…rted under IV Under Identity Verification the on_session update for a page view was lost in 2 cases. Both match the loss on main when the unsigned request gets a 401. Page (no worker): a 401 set _onSessionSent, so the page never tried again after the app stored a fresh token. The flag now stays clear on a 401, and a token stored for the current user after the session start sends the update again through _handleJwtUpdated. Worker: a returning identified user with no stored token at session start marked the session Active and skipped the user request. A later token never sent it, because the next upsert hit 'Session already active'. The session now records onSessionPending when the request was skipped for a missing token. The next upsert that brings a token sends it once, on an active session and on a reactivation within the threshold. An anonymous user under IV has no backend user and does not leave the session pending. The page registers a JwtTokenStore update listener in internalInit. With a worker it asks for an upsert with the new token (SessionOrigin _JwtUpdated); without a worker it calls _sendOnSessionUpdateFromPage again.
…worker) The on_session resend adds 75 B gzip to the page bundle and 67 B to the worker. The base already sat at both limits.
…nd page view Worker: updateUserSession counts only a response other than 401 as sent, so a 401 or a failed request leaves the session pending. The pending flag now stores the external id, and the next upsert sends only for that user. The flag is cleared before the request, so an upsert that overlaps a slow request does not send a second on_session. Page: on_session sends run one at a time, so a token update during a request re-checks the sent flag after it. A 403 counts as sent. With a worker, a hidden or unfocused tab does not post the token upsert, because that upsert resets the worker's finalize timer.
The request can retry for 120 s, past the session threshold, so the row the request started with may be deleted or replaced by the time it gives up. A write of the captured object recreated a row finalizeSession deleted. The flag now goes on the current row, and only if it is still the same session.
sherwinski
force-pushed
the
sherwin/sdk-5466
branch
from
October 26, 2026 20:28
09782dd to
b842db0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
1 Line Summary
Send the
on_sessionupdate again when a JWT arrives after the session started under Identity Verification, on the page and in the worker.Details
Under Identity Verification the
on_sessionupdate (session_count: 1) for a page view was lost in 2 cases. Both match the loss onmaintoday when the unsigned request gets a 401.Page, no worker (
SessionManager._sendOnSessionUpdateFromPage): a 401 set_onSessionSent = true, so the page did not try again after the app stored a fresh token withupdateUserJwtorlogin.session_count: 1.internalInitregisters aJwtTokenStoreupdate listener that calls the newSessionManager._handleJwtUpdated(externalId)._handleJwtUpdateddoes nothing before the session start (the normal flow carries the token), when IV is inactive, or when the token is for another user. Without a worker it calls_sendOnSessionUpdateFromPageagain, which re-checks every guard. With a worker it asks for an upsert with the new token (SessionOrigin._JwtUpdated), but only from a visible, focused tab: an upsert from a hidden tab resets the worker's finalize timer, and the next focus upsert carries the token anyway.Worker (
upsertSessioninhelpers/service-worker.ts): a returning identified user with no stored token at session start marked the sessionActiveand skipped the user request. A laterupdateUserJwtorloginnever sent it, because the next upsert hitSession already active. A 401 was lost the same way.updateUserSessionnow returnsfalsewhen it skipped the request, got a 401, or the request did not reach the server. Any other response counts as sent.sendOnSessionCallstoresSession.onSessionPendingFor = externalIdwhen the request did not count as sent, the payload hasjwtRequired, and it has anexternalId. An anonymous user under IV has no backend user, so that skip does not leave the session pending. WithoutjwtRequiredthe legacy path is unchanged.externalIdand clears the field. A different user clears it and sends nothing. The field is cleared before the request and set back on a skip or a 401, so an upsert that overlaps a slow request finds nothing pending. The request can retry for 120 s, past the session threshold, so the flag goes on the stored row read after the request, and only if it is still the same session. A write of the captured object could recreate a rowfinalizeSessiondeleted.The
// TO DO: aggregate session count in case network call failsstays as is. This PR adds no counter.Open points, left for a follow-up
on_sessionfor the new user, because the anonymous skip does not set the pending field. Onmain, the anonymouson_sessiongoes out at session start and the identify call keeps the sameonesignal_id, so that user's session is counted._putJwtfires the token listener before the identity model switches, so_handleJwtUpdatedcompares against the old external id and returns. Nothing fires it again after the switch.userJwtInvalidated, so the replay stops once the app refreshes the token.Bundle size
Measured with size-limit on both builds:
feat/identity-verification)OneSignalSDK.page.es6.jsOneSignalSDK.sw.jsThe base already sat at both limits, so the
build:commits raise them.Systems Affected
Validation
Tests
Info
vp test run— 72 files, 871 tests pass.vp check— 0 errors, 0 warnings in the changed files.vp run build:prod— passes with the raised limits (45.32 kB page, 12.82 kB worker).New tests:
SessionManager.test.ts: a 401 or a failed request does not count as sent and a fresh token sends again; a 403 counts as sent; a send that starts while one is in flight sends nothing more;_handleJwtUpdatedwithout a worker sends the skippedon_sessiononce; with a worker a visible focused tab posts an upsert with_JwtUpdatedand the token, and a hidden or unfocused tab posts nothing; nothing before the session start, when IV is inactive, or for another user.ServiceWorker.test.ts: a skipped request, a 401, or a failed request stores the session as pending for the user; a later upsert with a token sends it once and a further upsert sends nothing; a 400 counts as sent; a pending inactive session reactivated within the threshold sends it; the pending request is not sent to a different user; two overlapping upserts send it once; a session finalized while the request retries is not recreated; a pending restore after a failed retry goes on the current session only; an anonymous user does not leave the session pending.init.test.ts:internalInitregisters the listener, so a stored token reaches_handleJwtUpdated.Checklist
Programming Checklist
Interfaces:
Functions:
Typescript:
Other:
elem of arraysyntax. PreferforEachor usemapcontextif possible. Instead, we can pass it to function/constructor so that we don't callOneSignal.contextScreenshots
Info
Not needed: no UI change.
Checklist
Related Tickets