Skip to content

Commit c8f2b38

Browse files
박건우박건우
authored andcommitted
feat: add session tracking
1 parent f1e591f commit c8f2b38

4 files changed

Lines changed: 115 additions & 2 deletions

File tree

src/core/Clix.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { EventService } from '../services/EventService';
66
import { LiveActivityAPIService } from '../services/LiveActivityAPIService';
77
import { LiveActivityService } from '../services/LiveActivityService';
88
import { NotificationService } from '../services/NotificationService';
9+
import { SessionService } from '../services/SessionService';
910
import { StorageService } from '../services/StorageService';
1011
import { TokenService } from '../services/TokenService';
1112
import { ClixLogger, ClixLogLevel } from '../utils/logging/ClixLogger';
@@ -29,6 +30,7 @@ export class Clix {
2930
tokenService?: TokenService;
3031
eventService?: EventService;
3132
deviceService?: DeviceService;
33+
sessionService?: SessionService;
3234
notificationService?: NotificationService;
3335
liveActivityService?: LiveActivityService;
3436

@@ -68,10 +70,16 @@ export class Clix {
6870
eventApiService,
6971
this.shared.deviceService
7072
);
73+
this.shared.sessionService = new SessionService(
74+
this.shared.storageService,
75+
this.shared.eventService,
76+
config.sessionTimeoutMs ?? 30000
77+
);
7178
this.shared.notificationService = new NotificationService(
7279
this.shared.deviceService,
7380
this.shared.tokenService,
74-
this.shared.eventService
81+
this.shared.eventService,
82+
this.shared.sessionService
7583
);
7684
this.shared.liveActivityService = new LiveActivityService(
7785
this.shared.deviceService,
@@ -81,6 +89,7 @@ export class Clix {
8189
this.shared.storageService.set(this.configKey, config);
8290
this.shared.liveActivityService.initialize();
8391
await this.shared.notificationService.initialize(); // NOTE(nyanxyz): must be initialized before any await calls
92+
await this.shared.sessionService.start();
8493
await this.shared.deviceService.initialize();
8594

8695
ClixLogger.debug('Clix SDK initialized successfully');

src/core/ClixConfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export interface ClixConfig {
66
endpoint: string;
77
logLevel: ClixLogLevel;
88
extraHeaders: Record<string, string>;
9+
sessionTimeoutMs?: number;
910
}

src/services/NotificationService.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type { ClixPushNotificationPayload } from '../models/ClixPushNotification
1919
import { ClixLogger } from '../utils/logging/ClixLogger';
2020
import { DeviceService } from './DeviceService';
2121
import { EventService } from './EventService';
22+
import { SessionService } from './SessionService';
2223
import { TokenService } from './TokenService';
2324

2425
type NotificationData = Record<string, any>;
@@ -66,7 +67,8 @@ export class NotificationService {
6667
constructor(
6768
private readonly deviceService: DeviceService,
6869
private readonly tokenService: TokenService,
69-
private readonly eventService: EventService
70+
private readonly eventService: EventService,
71+
private readonly sessionService?: SessionService
7072
) {}
7173

7274
async initialize(): Promise<void> {
@@ -438,6 +440,7 @@ export class NotificationService {
438440
payload: ClixPushNotificationPayload
439441
): Promise<void> {
440442
try {
443+
this.sessionService?.setPendingMessageId(payload.messageId);
441444
await this.eventService.trackEvent(
442445
'PUSH_NOTIFICATION_TAPPED',
443446
{},

src/services/SessionService.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { AppState, type AppStateStatus } from 'react-native';
2+
import { ClixLogger } from '../utils/logging/ClixLogger';
3+
import type { EventService } from './EventService';
4+
import type { StorageService } from './StorageService';
5+
6+
enum SessionEvent {
7+
SESSION_START = 'SESSION_START',
8+
}
9+
10+
export class SessionService {
11+
private static readonly LAST_ACTIVITY_KEY = 'clix_session_last_activity';
12+
13+
private pendingMessageId?: string;
14+
private readonly effectiveTimeoutMs: number;
15+
private appStateSubscription?: { remove: () => void };
16+
private lastAppState: AppStateStatus;
17+
18+
constructor(
19+
private readonly storageService: StorageService,
20+
private readonly eventService: EventService,
21+
sessionTimeoutMs: number
22+
) {
23+
this.effectiveTimeoutMs = Math.max(sessionTimeoutMs, 5000);
24+
this.lastAppState = AppState.currentState;
25+
}
26+
27+
async start(): Promise<void> {
28+
this.appStateSubscription = AppState.addEventListener(
29+
'change',
30+
this.handleAppStateChange.bind(this)
31+
);
32+
33+
const lastActivity = this.storageService.get<number>(
34+
SessionService.LAST_ACTIVITY_KEY
35+
);
36+
if (lastActivity) {
37+
const elapsed = Date.now() - lastActivity;
38+
if (elapsed <= this.effectiveTimeoutMs) {
39+
this.updateLastActivity();
40+
ClixLogger.debug('Continuing existing session');
41+
return;
42+
}
43+
}
44+
await this.startNewSession();
45+
}
46+
47+
private async handleAppStateChange(
48+
nextAppState: AppStateStatus
49+
): Promise<void> {
50+
if (this.lastAppState === 'background' && nextAppState === 'active') {
51+
// Small delay to allow notification tap handlers to set pendingMessageId
52+
await new Promise((resolve) => setTimeout(resolve, 100));
53+
54+
const lastActivity = this.storageService.get<number>(
55+
SessionService.LAST_ACTIVITY_KEY
56+
);
57+
if (lastActivity) {
58+
const elapsed = Date.now() - lastActivity;
59+
if (elapsed <= this.effectiveTimeoutMs) {
60+
this.updateLastActivity();
61+
this.lastAppState = nextAppState;
62+
return;
63+
}
64+
}
65+
await this.startNewSession();
66+
} else if (nextAppState === 'background') {
67+
this.updateLastActivity();
68+
}
69+
this.lastAppState = nextAppState;
70+
}
71+
72+
setPendingMessageId(messageId?: string): void {
73+
this.pendingMessageId = messageId;
74+
}
75+
76+
private async startNewSession(): Promise<void> {
77+
const messageId = this.pendingMessageId;
78+
this.pendingMessageId = undefined;
79+
this.updateLastActivity();
80+
81+
try {
82+
await this.eventService.trackEvent(
83+
SessionEvent.SESSION_START,
84+
{},
85+
messageId
86+
);
87+
ClixLogger.debug(`${SessionEvent.SESSION_START} tracked`);
88+
} catch (error) {
89+
ClixLogger.error(`Failed to track ${SessionEvent.SESSION_START}`, error);
90+
}
91+
}
92+
93+
private updateLastActivity(): void {
94+
this.storageService.set(SessionService.LAST_ACTIVITY_KEY, Date.now());
95+
}
96+
97+
cleanup(): void {
98+
this.appStateSubscription?.remove();
99+
}
100+
}

0 commit comments

Comments
 (0)