screen-flow 1.0.0
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.
- package/README.md +126 -0
- package/dist/adapters/console.d.ts +7 -0
- package/dist/adapters/console.js +14 -0
- package/dist/core/flow.d.ts +17 -0
- package/dist/core/flow.js +57 -0
- package/dist/core/session.d.ts +16 -0
- package/dist/core/session.js +50 -0
- package/dist/core/timer.d.ts +16 -0
- package/dist/core/timer.js +49 -0
- package/dist/core/tracker.d.ts +32 -0
- package/dist/core/tracker.js +82 -0
- package/dist/hooks.d.ts +7 -0
- package/dist/hooks.js +19 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +54 -0
- package/dist/lifecycle/appState.d.ts +10 -0
- package/dist/lifecycle/appState.js +42 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.js +2 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# screen-flow π§
|
|
2
|
+
|
|
3
|
+
> **Navigation intelligence, not just analytics.** One-stop, platform-agnostic tracker for React and React Native.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/your-username/screen-flow/blob/main/LICENSE)
|
|
6
|
+
[](https://bundlephobia.com/package/screen-flow)
|
|
7
|
+
[](https://github.com/your-username/screen-flow)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## π Why ScreenFlow?
|
|
12
|
+
|
|
13
|
+
In a world of bloated analytics SDKs (Segment, Firebase, Mixpanel), **ScreenFlow** is the minimalist's choice.
|
|
14
|
+
|
|
15
|
+
- **β‘ Zero Bloat:** Only a few KB. Won't slow down your app or increase your bundle size like 2MB+ commercial SDKs.
|
|
16
|
+
- **π‘οΈ Privacy First:** No data ever leaves the device unless *you* want it to. Total control over your user's privacy.
|
|
17
|
+
- **π± True Cross-Platform:** Write once, track everywhere. Same API for Web and React Native with automatic lifecycle detection.
|
|
18
|
+
- **π§ More than Counters:** Automatically tracks **duration**, maintains **history**, and detects **back-navigation** using smart algorithms.
|
|
19
|
+
- **πΎ Persistence Ready:** Unlike other lightweight trackers, ScreenFlow can persist data across app restarts.
|
|
20
|
+
- **π§ͺ 100% Transparent:** Open-source and fully covered by unit tests. No "black-box" tracking.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## β¨ Features
|
|
25
|
+
|
|
26
|
+
- π **Flow Tracking**: Keeps track of the last 30 screens visited.
|
|
27
|
+
- β±οΈ **Time Awareness**: Automatically tracks time spent on each screen.
|
|
28
|
+
- π **Smart Back Detection**: Detects back navigation even across multiple screens.
|
|
29
|
+
- βΈοΈ **Smart Pausing**: Pauses timers when the app is in background.
|
|
30
|
+
- βοΈ **React Ready**: Comes with first-class hooks like `useScreenFlow`.
|
|
31
|
+
- π **Adapter Pattern**: Send data to Console, Firestore, Segment, or your own API.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## π Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install screen-flow
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## βοΈ Quick Start
|
|
44
|
+
|
|
45
|
+
### 1. Initialize (Optional but recommended)
|
|
46
|
+
|
|
47
|
+
Setup your storage and output adapter.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { initScreenFlow, ConsoleAdapter } from 'screen-flow';
|
|
51
|
+
|
|
52
|
+
initScreenFlow({
|
|
53
|
+
adapter: new ConsoleAdapter(),
|
|
54
|
+
// Persistence for Web (localStorage) or React Native (AsyncStorage)
|
|
55
|
+
storage: localStorage,
|
|
56
|
+
sessionTimeout: 30 * 60 * 1000 // 30 mins
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Track Screens
|
|
61
|
+
|
|
62
|
+
#### βοΈ Functional Components (React)
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import { useScreenFlow } from 'screen-flow';
|
|
66
|
+
|
|
67
|
+
const Dashboard = () => {
|
|
68
|
+
useScreenFlow('Dashboard', { tab: 'overview' });
|
|
69
|
+
return <div>My Dashboard</div>;
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### π±οΈ Manual Tracking
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { onScreenChange } from 'screen-flow';
|
|
77
|
+
|
|
78
|
+
// Anywhere in your logic
|
|
79
|
+
await onScreenChange('Settings');
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## π Customizing Output (Adapters)
|
|
85
|
+
|
|
86
|
+
Connect ScreenFlow to any service in seconds.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { Adapter, ScreenEvent } from 'screen-flow';
|
|
90
|
+
|
|
91
|
+
class MyBrandAdapter implements Adapter {
|
|
92
|
+
onEvent(event: ScreenEvent) {
|
|
93
|
+
console.log(`User stayed on ${event.previousScreen} for ${event.duration}ms`);
|
|
94
|
+
// Send to your own server
|
|
95
|
+
myApi.log(event);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## π Screen Event Data Structure
|
|
103
|
+
|
|
104
|
+
| Property | Type | Description |
|
|
105
|
+
| :--- | :--- | :--- |
|
|
106
|
+
| `screen` | `string` | Current screen name |
|
|
107
|
+
| `previousScreen` | `string \| null` | Name of the last screen |
|
|
108
|
+
| `duration` | `number` | Time spent on the last screen (ms) |
|
|
109
|
+
| `flow` | `string[]` | Last 30 screens visited |
|
|
110
|
+
| `isBack` | `boolean` | True if this move was a "Back" navigation |
|
|
111
|
+
| `sessionId` | `string` | Unique, persistent session ID |
|
|
112
|
+
| `params` | `object` | Custom metadata provided by you |
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## π Support
|
|
117
|
+
|
|
118
|
+
If ScreenFlow saved you hours of work or helped you build a faster app, consider supporting the development!
|
|
119
|
+
|
|
120
|
+
<a href="https://www.buymeacoffee.com/yourusername" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## π‘οΈ License
|
|
125
|
+
|
|
126
|
+
ISC (Free and Open Source)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConsoleAdapter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Default adapter that logs screen events to the console.
|
|
6
|
+
*/
|
|
7
|
+
class ConsoleAdapter {
|
|
8
|
+
onEvent(event) {
|
|
9
|
+
const { screen, duration, isBack, timestamp } = event;
|
|
10
|
+
const date = new Date(timestamp).toLocaleTimeString();
|
|
11
|
+
console.log(`[ScreenFlow] ${date} - ${screen} (${duration}ms)${isBack ? ' [BACK]' : ''}`, event);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.ConsoleAdapter = ConsoleAdapter;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { StorageProvider } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Manages the navigation history stack.
|
|
4
|
+
* Keeps track of the last 30 screens and detects back navigation.
|
|
5
|
+
*/
|
|
6
|
+
export declare class FlowManager {
|
|
7
|
+
private history;
|
|
8
|
+
private storage?;
|
|
9
|
+
private readonly MAX_HISTORY;
|
|
10
|
+
private readonly STORAGE_KEY;
|
|
11
|
+
constructor(storage?: StorageProvider);
|
|
12
|
+
private init;
|
|
13
|
+
push(screenName: string): Promise<boolean>;
|
|
14
|
+
getHistory(): string[];
|
|
15
|
+
getPreviousScreen(): string | null;
|
|
16
|
+
private isBackNavigation;
|
|
17
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FlowManager = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Manages the navigation history stack.
|
|
6
|
+
* Keeps track of the last 30 screens and detects back navigation.
|
|
7
|
+
*/
|
|
8
|
+
class FlowManager {
|
|
9
|
+
constructor(storage) {
|
|
10
|
+
this.history = [];
|
|
11
|
+
this.MAX_HISTORY = 30;
|
|
12
|
+
this.STORAGE_KEY = 'sf_history';
|
|
13
|
+
this.storage = storage;
|
|
14
|
+
this.init();
|
|
15
|
+
}
|
|
16
|
+
async init() {
|
|
17
|
+
if (this.storage) {
|
|
18
|
+
const storedHistory = await this.storage.getItem(this.STORAGE_KEY);
|
|
19
|
+
if (storedHistory) {
|
|
20
|
+
try {
|
|
21
|
+
this.history = JSON.parse(storedHistory);
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
this.history = [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async push(screenName) {
|
|
30
|
+
const isBack = this.isBackNavigation(screenName);
|
|
31
|
+
this.history.push(screenName);
|
|
32
|
+
if (this.history.length > this.MAX_HISTORY) {
|
|
33
|
+
this.history.shift();
|
|
34
|
+
}
|
|
35
|
+
if (this.storage) {
|
|
36
|
+
await this.storage.setItem(this.STORAGE_KEY, JSON.stringify(this.history));
|
|
37
|
+
}
|
|
38
|
+
return isBack;
|
|
39
|
+
}
|
|
40
|
+
getHistory() {
|
|
41
|
+
return [...this.history];
|
|
42
|
+
}
|
|
43
|
+
getPreviousScreen() {
|
|
44
|
+
if (this.history.length < 2)
|
|
45
|
+
return null;
|
|
46
|
+
return this.history[this.history.length - 2];
|
|
47
|
+
}
|
|
48
|
+
isBackNavigation(screenName) {
|
|
49
|
+
if (this.history.length < 2)
|
|
50
|
+
return false;
|
|
51
|
+
// Smarter back detection: if the screen exists in the recent history
|
|
52
|
+
// (last 5 screens excluding the current one)
|
|
53
|
+
const recentHistory = this.history.slice(-6, -1);
|
|
54
|
+
return recentHistory.includes(screenName);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.FlowManager = FlowManager;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { StorageProvider } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Handles session identification.
|
|
4
|
+
* Generates a unique ID per session, persisted across restarts.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SessionManager {
|
|
7
|
+
private sessionId;
|
|
8
|
+
private storage?;
|
|
9
|
+
private readonly STORAGE_KEY;
|
|
10
|
+
private readonly LAST_ACTIVITY_KEY;
|
|
11
|
+
private readonly sessionTimeout;
|
|
12
|
+
constructor(storage?: StorageProvider, sessionTimeout?: number);
|
|
13
|
+
getSessionId(): Promise<string>;
|
|
14
|
+
private updateActivity;
|
|
15
|
+
private generateId;
|
|
16
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SessionManager = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Handles session identification.
|
|
6
|
+
* Generates a unique ID per session, persisted across restarts.
|
|
7
|
+
*/
|
|
8
|
+
class SessionManager {
|
|
9
|
+
constructor(storage, sessionTimeout = 30 * 60 * 1000) {
|
|
10
|
+
this.sessionId = null;
|
|
11
|
+
this.STORAGE_KEY = 'sf_session_id';
|
|
12
|
+
this.LAST_ACTIVITY_KEY = 'sf_last_activity';
|
|
13
|
+
this.storage = storage;
|
|
14
|
+
this.sessionTimeout = sessionTimeout;
|
|
15
|
+
}
|
|
16
|
+
async getSessionId() {
|
|
17
|
+
if (this.sessionId) {
|
|
18
|
+
await this.updateActivity();
|
|
19
|
+
return this.sessionId;
|
|
20
|
+
}
|
|
21
|
+
if (this.storage) {
|
|
22
|
+
const storedId = await this.storage.getItem(this.STORAGE_KEY);
|
|
23
|
+
const lastActivity = await this.storage.getItem(this.LAST_ACTIVITY_KEY);
|
|
24
|
+
if (storedId && lastActivity) {
|
|
25
|
+
const elapsed = Date.now() - parseInt(lastActivity, 10);
|
|
26
|
+
if (elapsed < this.sessionTimeout) {
|
|
27
|
+
this.sessionId = storedId;
|
|
28
|
+
await this.updateActivity();
|
|
29
|
+
return this.sessionId;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// New session
|
|
34
|
+
this.sessionId = this.generateId();
|
|
35
|
+
if (this.storage) {
|
|
36
|
+
await this.storage.setItem(this.STORAGE_KEY, this.sessionId);
|
|
37
|
+
await this.updateActivity();
|
|
38
|
+
}
|
|
39
|
+
return this.sessionId;
|
|
40
|
+
}
|
|
41
|
+
async updateActivity() {
|
|
42
|
+
if (this.storage) {
|
|
43
|
+
await this.storage.setItem(this.LAST_ACTIVITY_KEY, Date.now().toString());
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
generateId() {
|
|
47
|
+
return Math.random().toString(36).substring(2, 11) + Date.now().toString(36);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.SessionManager = SessionManager;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles duration tracking for screens.
|
|
3
|
+
* Automatically pauses when app is backgrounded.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ScreenTimer {
|
|
6
|
+
private startTime;
|
|
7
|
+
private pausedTime;
|
|
8
|
+
private isPaused;
|
|
9
|
+
private lastPauseStart;
|
|
10
|
+
constructor();
|
|
11
|
+
start(): void;
|
|
12
|
+
pause(): void;
|
|
13
|
+
resume(): void;
|
|
14
|
+
getDuration(): number;
|
|
15
|
+
reset(): number;
|
|
16
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScreenTimer = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Handles duration tracking for screens.
|
|
6
|
+
* Automatically pauses when app is backgrounded.
|
|
7
|
+
*/
|
|
8
|
+
class ScreenTimer {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.startTime = 0;
|
|
11
|
+
this.pausedTime = 0;
|
|
12
|
+
this.isPaused = false;
|
|
13
|
+
this.lastPauseStart = 0;
|
|
14
|
+
this.start();
|
|
15
|
+
}
|
|
16
|
+
start() {
|
|
17
|
+
this.startTime = Date.now();
|
|
18
|
+
this.pausedTime = 0;
|
|
19
|
+
this.isPaused = false;
|
|
20
|
+
}
|
|
21
|
+
pause() {
|
|
22
|
+
if (!this.isPaused) {
|
|
23
|
+
this.lastPauseStart = Date.now();
|
|
24
|
+
this.isPaused = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
resume() {
|
|
28
|
+
if (this.isPaused) {
|
|
29
|
+
this.pausedTime += Date.now() - this.lastPauseStart;
|
|
30
|
+
this.isPaused = false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
getDuration() {
|
|
34
|
+
if (this.startTime === 0)
|
|
35
|
+
return 0;
|
|
36
|
+
const currentDuration = Date.now() - this.startTime - this.pausedTime;
|
|
37
|
+
// If currently paused, don't count the time since pause started
|
|
38
|
+
if (this.isPaused) {
|
|
39
|
+
return currentDuration - (Date.now() - this.lastPauseStart);
|
|
40
|
+
}
|
|
41
|
+
return currentDuration;
|
|
42
|
+
}
|
|
43
|
+
reset() {
|
|
44
|
+
const duration = this.getDuration();
|
|
45
|
+
this.start();
|
|
46
|
+
return duration;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.ScreenTimer = ScreenTimer;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { TrackerOptions } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Main coordinator for screen tracking.
|
|
4
|
+
* Manages timer, flow, and session across the application lifecycle.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ScreenTracker {
|
|
7
|
+
private timer;
|
|
8
|
+
private flow;
|
|
9
|
+
private session;
|
|
10
|
+
private adapter;
|
|
11
|
+
private currentScreen;
|
|
12
|
+
constructor(options?: TrackerOptions);
|
|
13
|
+
/**
|
|
14
|
+
* Tracks a screen change.
|
|
15
|
+
* @param screenName The name of the screen being navigated to.
|
|
16
|
+
* @param params Optional parameters associated with the screen change.
|
|
17
|
+
*/
|
|
18
|
+
track(screenName: string, params?: Record<string, any>): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Returns the current session ID.
|
|
21
|
+
*/
|
|
22
|
+
getSessionId(): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Returns the navigation history.
|
|
25
|
+
*/
|
|
26
|
+
getHistory(): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Clears the tracker state (useful for tests).
|
|
29
|
+
*/
|
|
30
|
+
clearInstance(): void;
|
|
31
|
+
}
|
|
32
|
+
export declare const tracker: ScreenTracker;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tracker = exports.ScreenTracker = void 0;
|
|
4
|
+
const timer_1 = require("./timer");
|
|
5
|
+
const flow_1 = require("./flow");
|
|
6
|
+
const session_1 = require("./session");
|
|
7
|
+
const appState_1 = require("../lifecycle/appState");
|
|
8
|
+
const console_1 = require("../adapters/console");
|
|
9
|
+
/**
|
|
10
|
+
* Main coordinator for screen tracking.
|
|
11
|
+
* Manages timer, flow, and session across the application lifecycle.
|
|
12
|
+
*/
|
|
13
|
+
class ScreenTracker {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.currentScreen = null;
|
|
16
|
+
this.timer = new timer_1.ScreenTimer();
|
|
17
|
+
this.flow = new flow_1.FlowManager(options.storage);
|
|
18
|
+
this.session = new session_1.SessionManager(options.storage, options.sessionTimeout);
|
|
19
|
+
this.adapter = options.adapter || new console_1.ConsoleAdapter();
|
|
20
|
+
// Setup platform-agnostic lifecycle listener
|
|
21
|
+
new appState_1.AppStateListener((status) => {
|
|
22
|
+
if (status === 'background') {
|
|
23
|
+
this.timer.pause();
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
this.timer.resume();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Tracks a screen change.
|
|
32
|
+
* @param screenName The name of the screen being navigated to.
|
|
33
|
+
* @param params Optional parameters associated with the screen change.
|
|
34
|
+
*/
|
|
35
|
+
async track(screenName, params) {
|
|
36
|
+
if (this.currentScreen === screenName)
|
|
37
|
+
return;
|
|
38
|
+
const previousScreen = this.currentScreen;
|
|
39
|
+
this.currentScreen = screenName;
|
|
40
|
+
// Reset timer and get duration spent on the previous screen
|
|
41
|
+
const duration = this.timer.reset();
|
|
42
|
+
// Update flow and detect if this is a back navigation
|
|
43
|
+
const isBack = await this.flow.push(screenName);
|
|
44
|
+
// Prepare the event
|
|
45
|
+
const event = {
|
|
46
|
+
screen: screenName,
|
|
47
|
+
previousScreen,
|
|
48
|
+
duration,
|
|
49
|
+
flow: this.flow.getHistory(),
|
|
50
|
+
isBack,
|
|
51
|
+
sessionId: await this.session.getSessionId(),
|
|
52
|
+
timestamp: Date.now(),
|
|
53
|
+
params
|
|
54
|
+
};
|
|
55
|
+
// Dispatch event to the adapter
|
|
56
|
+
this.adapter.onEvent(event);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Returns the current session ID.
|
|
60
|
+
*/
|
|
61
|
+
async getSessionId() {
|
|
62
|
+
return await this.session.getSessionId();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Returns the navigation history.
|
|
66
|
+
*/
|
|
67
|
+
getHistory() {
|
|
68
|
+
return this.flow.getHistory();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Clears the tracker state (useful for tests).
|
|
72
|
+
*/
|
|
73
|
+
clearInstance() {
|
|
74
|
+
this.currentScreen = null;
|
|
75
|
+
this.timer.start();
|
|
76
|
+
this.flow = new flow_1.FlowManager(this.storage);
|
|
77
|
+
this.session = new session_1.SessionManager(this.storage);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.ScreenTracker = ScreenTracker;
|
|
81
|
+
// Export a singleton instance for easier usage
|
|
82
|
+
exports.tracker = new ScreenTracker();
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook to automatically track screen changes in React components.
|
|
3
|
+
*
|
|
4
|
+
* @param screenName The name of the screen to track.
|
|
5
|
+
* @param params Optional parameters for the screen.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useScreenFlow(screenName: string, params?: Record<string, any>): void;
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useScreenFlow = useScreenFlow;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const index_1 = require("./index");
|
|
6
|
+
/**
|
|
7
|
+
* Hook to automatically track screen changes in React components.
|
|
8
|
+
*
|
|
9
|
+
* @param screenName The name of the screen to track.
|
|
10
|
+
* @param params Optional parameters for the screen.
|
|
11
|
+
*/
|
|
12
|
+
function useScreenFlow(screenName, params) {
|
|
13
|
+
const isFirstRender = (0, react_1.useRef)(true);
|
|
14
|
+
(0, react_1.useEffect)(() => {
|
|
15
|
+
// Track on mount or whenever screenName/params change
|
|
16
|
+
(0, index_1.onScreenChange)(screenName, params);
|
|
17
|
+
isFirstRender.current = false;
|
|
18
|
+
}, [screenName, JSON.stringify(params)]);
|
|
19
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export { ScreenTracker, tracker } from './core/tracker';
|
|
3
|
+
export { ConsoleAdapter } from './adapters/console';
|
|
4
|
+
import { tracker } from './core/tracker';
|
|
5
|
+
import { TrackerOptions } from './types';
|
|
6
|
+
/**
|
|
7
|
+
* Initializes the ScreenTracker with custom options (adapter, storage).
|
|
8
|
+
*/
|
|
9
|
+
export declare const initScreenFlow: (options: TrackerOptions) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Tracks a screen change.
|
|
12
|
+
*/
|
|
13
|
+
export declare const onScreenChange: (screenName: string, params?: Record<string, any>) => Promise<void>;
|
|
14
|
+
export * from './hooks';
|
|
15
|
+
/**
|
|
16
|
+
* Main API for screen tracking.
|
|
17
|
+
* @example
|
|
18
|
+
* import screenFlow from 'screen-flow';
|
|
19
|
+
* await screenFlow.track('Home');
|
|
20
|
+
*/
|
|
21
|
+
export default tracker;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.onScreenChange = exports.initScreenFlow = exports.ConsoleAdapter = exports.tracker = exports.ScreenTracker = void 0;
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
19
|
+
var tracker_1 = require("./core/tracker");
|
|
20
|
+
Object.defineProperty(exports, "ScreenTracker", { enumerable: true, get: function () { return tracker_1.ScreenTracker; } });
|
|
21
|
+
Object.defineProperty(exports, "tracker", { enumerable: true, get: function () { return tracker_1.tracker; } });
|
|
22
|
+
var console_1 = require("./adapters/console");
|
|
23
|
+
Object.defineProperty(exports, "ConsoleAdapter", { enumerable: true, get: function () { return console_1.ConsoleAdapter; } });
|
|
24
|
+
const tracker_2 = require("./core/tracker");
|
|
25
|
+
/**
|
|
26
|
+
* Initializes the ScreenTracker with custom options (adapter, storage).
|
|
27
|
+
*/
|
|
28
|
+
const initScreenFlow = (options) => {
|
|
29
|
+
tracker_2.tracker.adapter = options.adapter || tracker_2.tracker.adapter;
|
|
30
|
+
// Update internal managers with storage/options
|
|
31
|
+
if (options.storage) {
|
|
32
|
+
tracker_2.tracker.flow['storage'] = options.storage;
|
|
33
|
+
tracker_2.tracker.session['storage'] = options.storage;
|
|
34
|
+
}
|
|
35
|
+
if (options.sessionTimeout) {
|
|
36
|
+
tracker_2.tracker.session['sessionTimeout'] = options.sessionTimeout;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
exports.initScreenFlow = initScreenFlow;
|
|
40
|
+
/**
|
|
41
|
+
* Tracks a screen change.
|
|
42
|
+
*/
|
|
43
|
+
const onScreenChange = async (screenName, params) => {
|
|
44
|
+
await tracker_2.tracker.track(screenName, params);
|
|
45
|
+
};
|
|
46
|
+
exports.onScreenChange = onScreenChange;
|
|
47
|
+
__exportStar(require("./hooks"), exports);
|
|
48
|
+
/**
|
|
49
|
+
* Main API for screen tracking.
|
|
50
|
+
* @example
|
|
51
|
+
* import screenFlow from 'screen-flow';
|
|
52
|
+
* await screenFlow.track('Home');
|
|
53
|
+
*/
|
|
54
|
+
exports.default = tracker_2.tracker;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AppStateChangeListener } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Platform-agnostic AppState listener.
|
|
4
|
+
* Automatically detects React Native vs Web.
|
|
5
|
+
*/
|
|
6
|
+
export declare class AppStateListener {
|
|
7
|
+
private listener;
|
|
8
|
+
constructor(listener: AppStateChangeListener);
|
|
9
|
+
private init;
|
|
10
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AppStateListener = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Platform-agnostic AppState listener.
|
|
6
|
+
* Automatically detects React Native vs Web.
|
|
7
|
+
*/
|
|
8
|
+
class AppStateListener {
|
|
9
|
+
constructor(listener) {
|
|
10
|
+
this.listener = listener;
|
|
11
|
+
this.init();
|
|
12
|
+
}
|
|
13
|
+
init() {
|
|
14
|
+
// Check if we are in React Native
|
|
15
|
+
try {
|
|
16
|
+
// Use dynamic require to avoid bundling RN in web builds
|
|
17
|
+
const { AppState } = require('react-native');
|
|
18
|
+
if (AppState && AppState.addEventListener) {
|
|
19
|
+
AppState.addEventListener('change', (nextState) => {
|
|
20
|
+
if (nextState === 'active') {
|
|
21
|
+
this.listener('active');
|
|
22
|
+
}
|
|
23
|
+
else if (nextState === 'background' || nextState === 'inactive') {
|
|
24
|
+
this.listener('background');
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
// React Native not available, fallback to Web
|
|
32
|
+
}
|
|
33
|
+
// Fallback to Web (document visibility API)
|
|
34
|
+
if (typeof document !== 'undefined' && document.addEventListener) {
|
|
35
|
+
document.addEventListener('visibilitychange', () => {
|
|
36
|
+
const status = document.visibilityState === 'visible' ? 'active' : 'background';
|
|
37
|
+
this.listener(status);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.AppStateListener = AppStateListener;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a single screen event in the navigation flow.
|
|
3
|
+
*/
|
|
4
|
+
export interface ScreenEvent {
|
|
5
|
+
screen: string;
|
|
6
|
+
previousScreen: string | null;
|
|
7
|
+
duration: number;
|
|
8
|
+
flow: string[];
|
|
9
|
+
isBack: boolean;
|
|
10
|
+
sessionId: string;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
params?: Record<string, any>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Interface for output adapters.
|
|
16
|
+
*/
|
|
17
|
+
export interface Adapter {
|
|
18
|
+
onEvent(event: ScreenEvent): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Interface for storage providers (persistence).
|
|
22
|
+
*/
|
|
23
|
+
export interface StorageProvider {
|
|
24
|
+
getItem(key: string): string | null | Promise<string | null>;
|
|
25
|
+
setItem(key: string, value: string): void | Promise<void>;
|
|
26
|
+
removeItem(key: string): void | Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Options for initializing the ScreenTracker.
|
|
30
|
+
*/
|
|
31
|
+
export interface TrackerOptions {
|
|
32
|
+
adapter?: Adapter;
|
|
33
|
+
storage?: StorageProvider;
|
|
34
|
+
sessionTimeout?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* App state types to unify React and React Native behavior.
|
|
38
|
+
*/
|
|
39
|
+
export type AppStateStatus = 'active' | 'background';
|
|
40
|
+
export type AppStateChangeListener = (status: AppStateStatus) => void;
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "screen-flow",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Navigation intelligence library for React and React Native",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepublishOnly": "npm run build",
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"test:watch": "jest --watch"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"react",
|
|
15
|
+
"react-native",
|
|
16
|
+
"navigation",
|
|
17
|
+
"analytics",
|
|
18
|
+
"flow",
|
|
19
|
+
"screen-tracking"
|
|
20
|
+
],
|
|
21
|
+
"author": "Mansi Kansagara (https://github.com/Mansikansagara9)",
|
|
22
|
+
"license": "ISC",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/Mansikansagara9/screen-flow.git"
|
|
30
|
+
},
|
|
31
|
+
"contributors": [
|
|
32
|
+
{
|
|
33
|
+
"name": "Kaushik Vaghasiya",
|
|
34
|
+
"email": "kaushikvaghasiya134@gmail.com",
|
|
35
|
+
"url": "https://github.com/kaushik134"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/jest": "^30.0.0",
|
|
40
|
+
"@types/react": "^19.2.7",
|
|
41
|
+
"jest": "^29.7.0",
|
|
42
|
+
"ts-jest": "^29.4.6",
|
|
43
|
+
"typescript": "^5.0.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": ">=16.8.0",
|
|
47
|
+
"react-native": ">=0.60.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"react-native": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|