Skip to content

feat: robust session lifecycle events (user.loaded, user.deauthenticated, session.authenticated)#77

Merged
roncodes merged 3 commits intomainfrom
feature/session-lifecycle-events
Mar 26, 2026
Merged

feat: robust session lifecycle events (user.loaded, user.deauthenticated, session.authenticated)#77
roncodes merged 3 commits intomainfrom
feature/session-lifecycle-events

Conversation

@roncodes
Copy link
Copy Markdown
Member

Summary

Implements a complete, framework-uniform session lifecycle event system across the three core services (session, current-user, events). All events are fired on three buses simultaneously so any listener — whether in the main app, an Ember Engine, or a third-party integration — receives them reliably.

Event buses

Bus How to subscribe Best for
CurrentUserService (Evented) currentUser.on("user.loaded", fn) Direct service listeners (backward-compatible)
EventsService (Evented) events.on("user.loaded", fn) Service-level listeners
Universe universe.on("user.loaded", fn) Engines and extensions (recommended)

Events added

Event Fired by When
session.authenticated SessionService.handleAuthentication() Login / session restore
session.invalidated SessionService.handleInvalidation() Logout
session.terminated SessionService.handleInvalidation() Logout (backward-compat alias)
user.loaded CurrentUserService.setUser() User + org fully loaded after login
user.updated CurrentUserService.refreshUser() In-session profile refresh
user.deauthenticated SessionService.handleInvalidation() Logout (semantic alias for integrations)
user.organization_switched CurrentUserService.switchOrganization() Org switch

Previously broken

trackUserLoaded() and trackSessionTerminated() existed in EventsService but were never called. This PR wires them up properly.

Motivation

This unblocks clean integration of third-party services (Intercom, PostHog, Attio, etc.) in Ember Engines without resorting to addObserver hacks on the session service, which does not extend Evented.

Related

  • fleetbase/internals PR: feature/schedule-call-menu-item — Intercom lifecycle initializer updated to consume user.loaded and user.deauthenticated from the universe bus

Ronald A Richardson and others added 3 commits March 25, 2026 20:55
Adds a complete, framework-uniform session lifecycle event system that
fires events on three buses simultaneously:

  1. CurrentUserService Evented bus (backward-compatible .on() listeners)
  2. EventsService Evented bus (local service-level listeners)
  3. Universe service bus (cross-engine listeners — recommended for engines)

Events fired:

  session.authenticated    — on login / session restore (SessionService)
  session.invalidated      — on logout (SessionService)
  session.terminated       — alias for session.invalidated (backward compat)
  user.loaded              — after user + org fully loaded (CurrentUserService)
  user.updated             — after in-session profile refresh (CurrentUserService)
  user.deauthenticated     — semantic logout alias for integrations (SessionService)
  user.organization_switched — on org switch (CurrentUserService)

Changes:
- session.js: inject universe + events services; fire session.authenticated
  in handleAuthentication(), fire session.invalidated / user.deauthenticated
  in handleInvalidation() with session duration; add _fireSessionEvent()
  helper that broadcasts on both events service and universe directly
- current-user.js: inject universe service; call events.trackUserLoaded()
  and universe.trigger('user.loaded') in setUser(); add refreshUser() and
  switchOrganization() helpers that fire user.updated and
  user.organization_switched respectively
- events.js: add trackSessionAuthenticated(), trackUserUpdated(),
  trackOrganizationSwitched(); trackSessionTerminated() now also fires
  user.deauthenticated; full JSDoc for all session lifecycle events

Previously trackUserLoaded() and trackSessionTerminated() existed but were
never called. All session lifecycle tracking methods are now wired up.
The previous commit introduced a handleInvalidation() override that fired
session lifecycle events but did not call super, meaning the parent
ember-simple-auth behaviour (redirecting the browser to rootURL after
logout via handleSessionInvalidated) was silently dropped.

This would have caused the app to stay on the current page after logout
instead of redirecting to the login screen — a critical breakage.

Fix: call super.handleInvalidation(routeAfterInvalidation) first, then
fire our session lifecycle events. The super call is always safe here
because it only performs a redirect/reload and has no return value we
need to act on.
@roncodes roncodes merged commit d742771 into main Mar 26, 2026
6 checks passed
@roncodes roncodes deleted the feature/session-lifecycle-events branch March 26, 2026 01:08
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.

1 participant