@fleetbase/ember-core 0.3.14 → 0.3.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -19,6 +19,11 @@ export const hostServices = [
|
|
|
19
19
|
'sidebar',
|
|
20
20
|
'dashboard',
|
|
21
21
|
'universe',
|
|
22
|
+
'universe/menu-service',
|
|
23
|
+
'universe/registry-service',
|
|
24
|
+
'universe/hook-service',
|
|
25
|
+
'universe/widget-service',
|
|
26
|
+
'universe/extension-manager',
|
|
22
27
|
'events',
|
|
23
28
|
'intl',
|
|
24
29
|
'abilities',
|
|
@@ -21,6 +21,11 @@ export const services = [
|
|
|
21
21
|
'sidebar',
|
|
22
22
|
'dashboard',
|
|
23
23
|
'universe',
|
|
24
|
+
'universe/menu-service',
|
|
25
|
+
'universe/registry-service',
|
|
26
|
+
'universe/hook-service',
|
|
27
|
+
'universe/widget-service',
|
|
28
|
+
'universe/extension-manager',
|
|
24
29
|
'events',
|
|
25
30
|
'intl',
|
|
26
31
|
'abilities',
|
|
@@ -10,6 +10,25 @@ import { storageFor } from 'ember-local-storage';
|
|
|
10
10
|
import { debug } from '@ember/debug';
|
|
11
11
|
import lookupUserIp from '../utils/lookup-user-ip';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* CurrentUserService
|
|
15
|
+
*
|
|
16
|
+
* Manages the authenticated user's identity and preferences. Extends Evented
|
|
17
|
+
* so that any service or component can subscribe to user lifecycle events
|
|
18
|
+
* directly on this service.
|
|
19
|
+
*
|
|
20
|
+
* Session lifecycle events emitted (on both this service and via EventsService
|
|
21
|
+
* which re-broadcasts them on the universe bus for cross-engine listeners):
|
|
22
|
+
*
|
|
23
|
+
* user.loaded — fired after a successful login or session restore.
|
|
24
|
+
* Payload: (user, organization, properties)
|
|
25
|
+
*
|
|
26
|
+
* user.updated — fired when the user record is refreshed in-session
|
|
27
|
+
* (e.g. profile edit). Payload: (user, properties)
|
|
28
|
+
*
|
|
29
|
+
* user.organization_switched — fired when the user switches active org.
|
|
30
|
+
* Payload: (organization, properties)
|
|
31
|
+
*/
|
|
13
32
|
export default class CurrentUserService extends Service.extend(Evented) {
|
|
14
33
|
@service session;
|
|
15
34
|
@service store;
|
|
@@ -18,6 +37,7 @@ export default class CurrentUserService extends Service.extend(Evented) {
|
|
|
18
37
|
@service notifications;
|
|
19
38
|
@service intl;
|
|
20
39
|
@service events;
|
|
40
|
+
@service universe;
|
|
21
41
|
|
|
22
42
|
@tracked user = { id: 'anon' };
|
|
23
43
|
@tracked userSnapshot = { id: 'anon' };
|
|
@@ -302,14 +322,47 @@ export default class CurrentUserService extends Service.extend(Evented) {
|
|
|
302
322
|
return defaultValue;
|
|
303
323
|
}
|
|
304
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Sets the current user and fires all user.loaded lifecycle events.
|
|
327
|
+
*
|
|
328
|
+
* This is the canonical place where the authenticated user identity is
|
|
329
|
+
* established. It fires:
|
|
330
|
+
*
|
|
331
|
+
* 1. `this.trigger('user.loaded', user)` — on the currentUser service
|
|
332
|
+
* itself (Evented), for direct service-level listeners.
|
|
333
|
+
*
|
|
334
|
+
* 2. `this.events.trackUserLoaded(user, organization)` — on the events
|
|
335
|
+
* service, which re-broadcasts on both the events bus and the universe
|
|
336
|
+
* bus so cross-engine listeners (Intercom, PostHog, Attio, etc.) can
|
|
337
|
+
* subscribe via `universe.on('user.loaded', handler)`.
|
|
338
|
+
*
|
|
339
|
+
* @param {Model} user
|
|
340
|
+
*/
|
|
305
341
|
async setUser(user) {
|
|
306
342
|
const snapshot = await this.getUserSnapshot(user);
|
|
307
343
|
|
|
308
344
|
// Set current user
|
|
309
345
|
this.set('user', user);
|
|
310
346
|
this.set('userSnapshot', snapshot);
|
|
347
|
+
|
|
348
|
+
// Resolve the organization for event payload
|
|
349
|
+
const organization = this.store.peekRecord('company', user.get('company_uuid'));
|
|
350
|
+
|
|
351
|
+
// 1. Trigger on the currentUser Evented bus (backward-compatible)
|
|
311
352
|
this.trigger('user.loaded', user);
|
|
312
353
|
|
|
354
|
+
// 2. Fire through the events service — broadcasts on both events bus
|
|
355
|
+
// and universe bus for cross-engine listeners
|
|
356
|
+
if (this.events) {
|
|
357
|
+
this.events.trackUserLoaded(user, organization);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// 3. Trigger directly on universe for framework-level uniformity —
|
|
361
|
+
// guarantees delivery to all engines on the shared bus
|
|
362
|
+
if (this.universe) {
|
|
363
|
+
this.universe.trigger('user.loaded', user, organization);
|
|
364
|
+
}
|
|
365
|
+
|
|
313
366
|
// Set permissions
|
|
314
367
|
this.permissions = this.getUserPermissions(user);
|
|
315
368
|
|
|
@@ -323,4 +376,55 @@ export default class CurrentUserService extends Service.extend(Evented) {
|
|
|
323
376
|
await this.loadLocale();
|
|
324
377
|
}
|
|
325
378
|
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Fires a user.updated event when the user record is refreshed in-session.
|
|
382
|
+
* Call this after any in-session profile update to keep integrations in sync.
|
|
383
|
+
*
|
|
384
|
+
* @param {Model} user
|
|
385
|
+
*/
|
|
386
|
+
async refreshUser(user) {
|
|
387
|
+
const snapshot = await this.getUserSnapshot(user);
|
|
388
|
+
this.set('user', user);
|
|
389
|
+
this.set('userSnapshot', snapshot);
|
|
390
|
+
|
|
391
|
+
const organization = this.store.peekRecord('company', user.get('company_uuid'));
|
|
392
|
+
|
|
393
|
+
this.trigger('user.updated', user);
|
|
394
|
+
|
|
395
|
+
if (this.events) {
|
|
396
|
+
this.events.trackEvent('user.updated', {
|
|
397
|
+
user_id: user?.id,
|
|
398
|
+
organization_id: organization?.id,
|
|
399
|
+
organization_name: organization?.name,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (this.universe) {
|
|
404
|
+
this.universe.trigger('user.updated', user, organization);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Fires a user.organization_switched event when the user changes their
|
|
410
|
+
* active organization. Call this after a successful org switch.
|
|
411
|
+
*
|
|
412
|
+
* @param {Model} organization
|
|
413
|
+
*/
|
|
414
|
+
switchOrganization(organization) {
|
|
415
|
+
this.company = organization;
|
|
416
|
+
|
|
417
|
+
this.trigger('user.organization_switched', organization);
|
|
418
|
+
|
|
419
|
+
if (this.events) {
|
|
420
|
+
this.events.trackEvent('user.organization_switched', {
|
|
421
|
+
organization_id: organization?.id,
|
|
422
|
+
organization_name: organization?.name,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (this.universe) {
|
|
427
|
+
this.universe.trigger('user.organization_switched', organization);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
326
430
|
}
|
package/addon/services/events.js
CHANGED
|
@@ -3,11 +3,66 @@ import Evented from '@ember/object/evented';
|
|
|
3
3
|
import config from 'ember-get-config';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* EventsService
|
|
7
7
|
*
|
|
8
|
-
* Provides a centralized event tracking system for Fleetbase.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Provides a centralized, standardized event tracking system for Fleetbase.
|
|
9
|
+
*
|
|
10
|
+
* This service is the single source of truth for all application lifecycle
|
|
11
|
+
* events. It emits every event on two buses simultaneously:
|
|
12
|
+
*
|
|
13
|
+
* 1. Its own Evented bus — for direct service/component listeners:
|
|
14
|
+
* this.events.on('user.loaded', handler)
|
|
15
|
+
*
|
|
16
|
+
* 2. The universe service bus — for cross-engine listeners (recommended
|
|
17
|
+
* for use in Ember Engines / extensions):
|
|
18
|
+
* this.universe.on('user.loaded', handler)
|
|
19
|
+
*
|
|
20
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
* Session Lifecycle Events
|
|
22
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
*
|
|
24
|
+
* session.authenticated Fired after a successful login or session restore.
|
|
25
|
+
* Payload: (properties)
|
|
26
|
+
*
|
|
27
|
+
* session.invalidated Fired after the session is destroyed (logout).
|
|
28
|
+
* Payload: (duration_seconds, properties)
|
|
29
|
+
*
|
|
30
|
+
* session.terminated Alias for session.invalidated — provided for
|
|
31
|
+
* backward compatibility and semantic clarity.
|
|
32
|
+
* Payload: (duration_seconds, properties)
|
|
33
|
+
*
|
|
34
|
+
* user.loaded Fired after the authenticated user record and
|
|
35
|
+
* organization have been fully loaded into the
|
|
36
|
+
* currentUser service. This is the canonical event
|
|
37
|
+
* for integrations to boot (Intercom, PostHog, etc.)
|
|
38
|
+
* Payload: (user, organization, properties)
|
|
39
|
+
*
|
|
40
|
+
* user.updated Fired when the user record is refreshed in-session
|
|
41
|
+
* (e.g. after a profile edit).
|
|
42
|
+
* Payload: (user, properties)
|
|
43
|
+
*
|
|
44
|
+
* user.deauthenticated Fired when the user identity is cleared on logout.
|
|
45
|
+
* Semantic alias for session.invalidated — use this
|
|
46
|
+
* to shut down integrations cleanly (Intercom, etc.)
|
|
47
|
+
* Payload: (duration_seconds, properties)
|
|
48
|
+
*
|
|
49
|
+
* user.organization_switched Fired when the user switches their active org.
|
|
50
|
+
* Payload: (organization, properties)
|
|
51
|
+
*
|
|
52
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
* Resource Events
|
|
54
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
55
|
+
*
|
|
56
|
+
* resource.created Generic resource creation.
|
|
57
|
+
* resource.updated Generic resource update.
|
|
58
|
+
* resource.deleted Generic resource deletion.
|
|
59
|
+
* resource.imported Bulk import.
|
|
60
|
+
* resource.exported Export.
|
|
61
|
+
* resource.bulk_action Bulk action (delete, archive, etc.)
|
|
62
|
+
* {modelName}.created Model-specific creation (e.g. order.created)
|
|
63
|
+
* {modelName}.updated Model-specific update.
|
|
64
|
+
* {modelName}.deleted Model-specific deletion.
|
|
65
|
+
* {modelName}.exported Model-specific export.
|
|
11
66
|
*
|
|
12
67
|
* @class EventsService
|
|
13
68
|
* @extends Service
|
|
@@ -16,6 +71,108 @@ export default class EventsService extends Service.extend(Evented) {
|
|
|
16
71
|
@service universe;
|
|
17
72
|
@service currentUser;
|
|
18
73
|
|
|
74
|
+
// =========================================================================
|
|
75
|
+
// Session Lifecycle Tracking
|
|
76
|
+
// =========================================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Tracks a successful authentication (login or session restore).
|
|
80
|
+
*
|
|
81
|
+
* Called by SessionService.handleAuthentication().
|
|
82
|
+
*
|
|
83
|
+
* @param {Object} [props={}] - Additional properties to include
|
|
84
|
+
*/
|
|
85
|
+
trackSessionAuthenticated(props = {}) {
|
|
86
|
+
const properties = this.#enrichProperties(props);
|
|
87
|
+
this.#trigger('session.authenticated', properties);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Tracks when a user session is terminated (logout).
|
|
92
|
+
*
|
|
93
|
+
* Called by SessionService.handleInvalidation(). Also fires the semantic
|
|
94
|
+
* `user.deauthenticated` event so integrations can react to the user
|
|
95
|
+
* identity being cleared without needing to know about session internals.
|
|
96
|
+
*
|
|
97
|
+
* @param {Number|null} duration - Session duration in seconds (null if unknown)
|
|
98
|
+
* @param {Object} [props={}] - Additional properties to include
|
|
99
|
+
*/
|
|
100
|
+
trackSessionTerminated(duration, props = {}) {
|
|
101
|
+
const properties = this.#enrichProperties({
|
|
102
|
+
session_duration: duration,
|
|
103
|
+
...props,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Fire session.invalidated (technical event)
|
|
107
|
+
this.#trigger('session.invalidated', duration, properties);
|
|
108
|
+
|
|
109
|
+
// Fire session.terminated (backward-compatible alias)
|
|
110
|
+
this.#trigger('session.terminated', duration, properties);
|
|
111
|
+
|
|
112
|
+
// Fire user.deauthenticated (semantic event for integrations)
|
|
113
|
+
this.#trigger('user.deauthenticated', duration, properties);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Tracks when the current user is loaded (session initialized).
|
|
118
|
+
*
|
|
119
|
+
* Called by CurrentUserService.setUser() after a successful login or
|
|
120
|
+
* session restore. This is the canonical event for integrations to boot.
|
|
121
|
+
*
|
|
122
|
+
* @param {Object} user - The authenticated user model
|
|
123
|
+
* @param {Object} organization - The user's active organization model
|
|
124
|
+
* @param {Object} [props={}] - Additional properties to include
|
|
125
|
+
*/
|
|
126
|
+
trackUserLoaded(user, organization, props = {}) {
|
|
127
|
+
const properties = this.#enrichProperties({
|
|
128
|
+
user_id: user?.id,
|
|
129
|
+
organization_id: organization?.id,
|
|
130
|
+
organization_name: organization?.name,
|
|
131
|
+
...props,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
this.#trigger('user.loaded', user, organization, properties);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Tracks when the user record is refreshed in-session (e.g. profile edit).
|
|
139
|
+
*
|
|
140
|
+
* Called by CurrentUserService.refreshUser().
|
|
141
|
+
*
|
|
142
|
+
* @param {Object} user - The updated user model
|
|
143
|
+
* @param {Object} [props={}] - Additional properties to include
|
|
144
|
+
*/
|
|
145
|
+
trackUserUpdated(user, props = {}) {
|
|
146
|
+
const properties = this.#enrichProperties({
|
|
147
|
+
user_id: user?.id,
|
|
148
|
+
...props,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
this.#trigger('user.updated', user, properties);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Tracks when the user switches their active organization.
|
|
156
|
+
*
|
|
157
|
+
* Called by CurrentUserService.switchOrganization().
|
|
158
|
+
*
|
|
159
|
+
* @param {Object} organization - The new active organization model
|
|
160
|
+
* @param {Object} [props={}] - Additional properties to include
|
|
161
|
+
*/
|
|
162
|
+
trackOrganizationSwitched(organization, props = {}) {
|
|
163
|
+
const properties = this.#enrichProperties({
|
|
164
|
+
organization_id: organization?.id,
|
|
165
|
+
organization_name: organization?.name,
|
|
166
|
+
...props,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
this.#trigger('user.organization_switched', organization, properties);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// =========================================================================
|
|
173
|
+
// Resource Event Tracking
|
|
174
|
+
// =========================================================================
|
|
175
|
+
|
|
19
176
|
/**
|
|
20
177
|
* Tracks the creation of a resource
|
|
21
178
|
*
|
|
@@ -131,38 +288,9 @@ export default class EventsService extends Service.extend(Evented) {
|
|
|
131
288
|
this.#trigger('resource.bulk_action', verb, resources, firstResource, properties);
|
|
132
289
|
}
|
|
133
290
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
* @param {Object} user - The user object
|
|
138
|
-
* @param {Object} organization - The organization object
|
|
139
|
-
* @param {Object} [props={}] - Additional properties to include
|
|
140
|
-
*/
|
|
141
|
-
trackUserLoaded(user, organization, props = {}) {
|
|
142
|
-
const properties = this.#enrichProperties({
|
|
143
|
-
user_id: user?.id,
|
|
144
|
-
organization_id: organization?.id,
|
|
145
|
-
organization_name: organization?.name,
|
|
146
|
-
...props,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
this.#trigger('user.loaded', user, organization, properties);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Tracks when a user session is terminated
|
|
154
|
-
*
|
|
155
|
-
* @param {Number} duration - Session duration in seconds
|
|
156
|
-
* @param {Object} [props={}] - Additional properties to include
|
|
157
|
-
*/
|
|
158
|
-
trackSessionTerminated(duration, props = {}) {
|
|
159
|
-
const properties = this.#enrichProperties({
|
|
160
|
-
session_duration: duration,
|
|
161
|
-
...props,
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
this.#trigger('session.terminated', duration, properties);
|
|
165
|
-
}
|
|
291
|
+
// =========================================================================
|
|
292
|
+
// Generic Event Tracking
|
|
293
|
+
// =========================================================================
|
|
166
294
|
|
|
167
295
|
/**
|
|
168
296
|
* Tracks a generic custom event
|
|
@@ -190,11 +318,11 @@ export default class EventsService extends Service.extend(Evented) {
|
|
|
190
318
|
// =========================================================================
|
|
191
319
|
|
|
192
320
|
/**
|
|
193
|
-
* Triggers an event on both the events service and universe service
|
|
321
|
+
* Triggers an event on both the events service and universe service.
|
|
194
322
|
*
|
|
195
323
|
* This dual event system allows listeners to subscribe to events on either:
|
|
196
|
-
*
|
|
197
|
-
*
|
|
324
|
+
* - this.events.on('event.name', handler) — local listeners
|
|
325
|
+
* - this.universe.on('event.name', handler) — cross-engine listeners
|
|
198
326
|
*
|
|
199
327
|
* @private
|
|
200
328
|
* @param {String} eventName - The event name
|
|
@@ -5,11 +5,42 @@ import { later } from '@ember/runloop';
|
|
|
5
5
|
import { debug } from '@ember/debug';
|
|
6
6
|
import getWithDefault from '../utils/get-with-default';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* SessionService
|
|
10
|
+
*
|
|
11
|
+
* Extends ember-simple-auth's session service to add:
|
|
12
|
+
*
|
|
13
|
+
* - Proper lifecycle event firing through the EventsService (which
|
|
14
|
+
* re-broadcasts on both the events bus and the universe bus).
|
|
15
|
+
* - Session start timestamp tracking for duration calculation.
|
|
16
|
+
*
|
|
17
|
+
* Session lifecycle events fired (via EventsService → universe bus):
|
|
18
|
+
*
|
|
19
|
+
* session.authenticated — fired after a successful login/session restore.
|
|
20
|
+
* Payload: (properties)
|
|
21
|
+
*
|
|
22
|
+
* session.invalidated — fired after the session is destroyed (logout).
|
|
23
|
+
* Payload: (duration_seconds, properties)
|
|
24
|
+
*
|
|
25
|
+
* user.deauthenticated — alias for session.invalidated, provided as a
|
|
26
|
+
* semantic convenience for integrations that want
|
|
27
|
+
* to react to the user identity being cleared
|
|
28
|
+
* (e.g. Intercom shutdown, PostHog reset).
|
|
29
|
+
* Payload: (duration_seconds, properties)
|
|
30
|
+
*
|
|
31
|
+
* All events are fired on both the EventsService Evented bus and the universe
|
|
32
|
+
* service, so listeners can use either:
|
|
33
|
+
*
|
|
34
|
+
* this.events.on('session.authenticated', handler)
|
|
35
|
+
* this.universe.on('session.authenticated', handler) ← recommended for engines
|
|
36
|
+
*/
|
|
8
37
|
export default class SessionService extends SimpleAuthSessionService {
|
|
9
38
|
@service router;
|
|
10
39
|
@service currentUser;
|
|
11
40
|
@service fetch;
|
|
12
41
|
@service notifications;
|
|
42
|
+
@service events;
|
|
43
|
+
@service universe;
|
|
13
44
|
|
|
14
45
|
/**
|
|
15
46
|
* Set where to transition to
|
|
@@ -25,6 +56,14 @@ export default class SessionService extends SimpleAuthSessionService {
|
|
|
25
56
|
*/
|
|
26
57
|
@tracked _isOnboarding = false;
|
|
27
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Timestamp (ms) when the session was authenticated.
|
|
61
|
+
* Used to calculate session duration on invalidation.
|
|
62
|
+
*
|
|
63
|
+
* @var {Number|null}
|
|
64
|
+
*/
|
|
65
|
+
@tracked _sessionStartedAt = null;
|
|
66
|
+
|
|
28
67
|
/**
|
|
29
68
|
* Set this as onboarding.
|
|
30
69
|
*
|
|
@@ -44,11 +83,20 @@ export default class SessionService extends SimpleAuthSessionService {
|
|
|
44
83
|
}
|
|
45
84
|
|
|
46
85
|
/**
|
|
47
|
-
* Overwrite the handle authentication method
|
|
86
|
+
* Overwrite the handle authentication method.
|
|
87
|
+
*
|
|
88
|
+
* Fires `session.authenticated` through the events service so that
|
|
89
|
+
* integrations (Intercom, PostHog, etc.) can react to a successful login.
|
|
48
90
|
*
|
|
49
91
|
* @void
|
|
50
92
|
*/
|
|
51
93
|
async handleAuthentication() {
|
|
94
|
+
// Record session start time for duration tracking on logout
|
|
95
|
+
this._sessionStartedAt = Date.now();
|
|
96
|
+
|
|
97
|
+
// Fire session.authenticated event
|
|
98
|
+
this._fireSessionEvent('session.authenticated');
|
|
99
|
+
|
|
52
100
|
if (this._isOnboarding) {
|
|
53
101
|
return;
|
|
54
102
|
}
|
|
@@ -76,6 +124,37 @@ export default class SessionService extends SimpleAuthSessionService {
|
|
|
76
124
|
removeLoaderNode();
|
|
77
125
|
}
|
|
78
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Extends the parent handleInvalidation method.
|
|
129
|
+
*
|
|
130
|
+
* IMPORTANT: super.handleInvalidation(routeAfterInvalidation) is called
|
|
131
|
+
* first to preserve the ember-simple-auth behaviour of redirecting the
|
|
132
|
+
* user to the login page (via handleSessionInvalidated). Our event
|
|
133
|
+
* firing happens after so it cannot interfere with the redirect.
|
|
134
|
+
*
|
|
135
|
+
* @param {String} routeAfterInvalidation - Passed through from ember-simple-auth
|
|
136
|
+
* @void
|
|
137
|
+
*/
|
|
138
|
+
handleInvalidation(routeAfterInvalidation) {
|
|
139
|
+
// 1. Always call super first — this performs the actual post-logout
|
|
140
|
+
// redirect/reload that ember-simple-auth is responsible for.
|
|
141
|
+
super.handleInvalidation(routeAfterInvalidation);
|
|
142
|
+
|
|
143
|
+
const durationSeconds = this._sessionStartedAt ? Math.round((Date.now() - this._sessionStartedAt) / 1000) : null;
|
|
144
|
+
|
|
145
|
+
// 2. Fire session lifecycle events for integrations (Intercom, PostHog, etc.)
|
|
146
|
+
if (this.events) {
|
|
147
|
+
this.events.trackSessionTerminated(durationSeconds);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 3. Fire user.deauthenticated directly on universe for framework-level
|
|
151
|
+
// uniformity — engines can listen without needing the events service.
|
|
152
|
+
this._fireSessionEvent('user.deauthenticated', { session_duration: durationSeconds });
|
|
153
|
+
|
|
154
|
+
// Reset session start time
|
|
155
|
+
this._sessionStartedAt = null;
|
|
156
|
+
}
|
|
157
|
+
|
|
79
158
|
/**
|
|
80
159
|
* Loads the current authenticated user
|
|
81
160
|
*
|
|
@@ -217,4 +296,39 @@ export default class SessionService extends SimpleAuthSessionService {
|
|
|
217
296
|
throw new Error(error.message);
|
|
218
297
|
});
|
|
219
298
|
}
|
|
299
|
+
|
|
300
|
+
// =========================================================================
|
|
301
|
+
// Private helpers
|
|
302
|
+
// =========================================================================
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Fires a named session lifecycle event on both the events service and
|
|
306
|
+
* directly on the universe service.
|
|
307
|
+
*
|
|
308
|
+
* Firing on universe directly (in addition to via events service) ensures
|
|
309
|
+
* that all engines and extensions receive the event on the shared framework-
|
|
310
|
+
* level bus regardless of whether the events service has fully initialised.
|
|
311
|
+
*
|
|
312
|
+
* Listeners can subscribe via:
|
|
313
|
+
* this.universe.on('session.authenticated', handler)
|
|
314
|
+
* this.universe.on('user.deauthenticated', handler)
|
|
315
|
+
* this.events.on('session.authenticated', handler)
|
|
316
|
+
*
|
|
317
|
+
* @private
|
|
318
|
+
* @param {String} eventName
|
|
319
|
+
* @param {Object} [extraProps={}]
|
|
320
|
+
*/
|
|
321
|
+
_fireSessionEvent(eventName, extraProps = {}) {
|
|
322
|
+
// 1. Fire through the events service (dual-broadcasts on events + universe bus)
|
|
323
|
+
if (this.events) {
|
|
324
|
+
this.events.trackEvent(eventName, extraProps);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 2. Also trigger directly on universe for framework-level uniformity —
|
|
328
|
+
// ensures the event reaches all engines even if events service is
|
|
329
|
+
// not yet available or not injected in a given engine context.
|
|
330
|
+
if (this.universe) {
|
|
331
|
+
this.universe.trigger(eventName, extraProps);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
220
334
|
}
|
package/package.json
CHANGED