@cognior/iap-sdk 0.1.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.
Potentially problematic release.
This version of @cognior/iap-sdk might be problematic. Click here for more details.
- package/.github/copilot-instructions.md +95 -0
- package/README.md +79 -0
- package/TRACKING.md +105 -0
- package/USER_CONTEXT_README.md +284 -0
- package/package.json +154 -0
- package/src/config.ts +25 -0
- package/src/core/flowEngine.ts +1833 -0
- package/src/core/triggerManager.ts +1011 -0
- package/src/experiences/banner.ts +366 -0
- package/src/experiences/beacon.ts +668 -0
- package/src/experiences/hotspotTour.ts +654 -0
- package/src/experiences/hotspots.ts +566 -0
- package/src/experiences/modal.ts +1337 -0
- package/src/experiences/modalSequence.ts +1247 -0
- package/src/experiences/popover.ts +652 -0
- package/src/experiences/registry.ts +21 -0
- package/src/experiences/survey.ts +1639 -0
- package/src/experiences/taskList.ts +625 -0
- package/src/experiences/tooltip.ts +740 -0
- package/src/experiences/types.ts +395 -0
- package/src/experiences/walkthrough.ts +670 -0
- package/src/flow-sequence.ts +177 -0
- package/src/flows.ts +512 -0
- package/src/http.ts +61 -0
- package/src/index.ts +355 -0
- package/src/services/flowManager.ts +905 -0
- package/src/services/flowNormalizer.ts +74 -0
- package/src/services/locationContextService.ts +189 -0
- package/src/services/pageContextService.ts +221 -0
- package/src/services/userContextService.ts +286 -0
- package/src/state/appState.ts +0 -0
- package/src/state/hooks.ts +0 -0
- package/src/state/index.ts +0 -0
- package/src/state/migration.ts +0 -0
- package/src/state/store.ts +0 -0
- package/src/styles/banner.css.ts +0 -0
- package/src/styles/hotspot.css.ts +0 -0
- package/src/styles/hotspotTour.css.ts +0 -0
- package/src/styles/modal.css.ts +564 -0
- package/src/styles/survey.css.ts +1013 -0
- package/src/styles/taskList.css.ts +0 -0
- package/src/styles/tooltip.css.ts +149 -0
- package/src/styles/walkthrough.css.ts +0 -0
- package/src/tourUtils.ts +0 -0
- package/src/tracking.ts +223 -0
- package/src/utils/debounce.ts +66 -0
- package/src/utils/eventSequenceValidator.ts +124 -0
- package/src/utils/flowTrackingSystem.ts +524 -0
- package/src/utils/idGenerator.ts +155 -0
- package/src/utils/immediateValidationPrevention.ts +184 -0
- package/src/utils/normalize.ts +50 -0
- package/src/utils/privacyManager.ts +166 -0
- package/src/utils/ruleEvaluator.ts +199 -0
- package/src/utils/sanitize.ts +79 -0
- package/src/utils/selectors.ts +107 -0
- package/src/utils/stepExecutor.ts +345 -0
- package/src/utils/triggerNormalizer.ts +149 -0
- package/src/utils/validationInterceptor.ts +650 -0
- package/tsconfig.json +13 -0
- package/tsup.config.ts +13 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/services/flowNormalizer.ts
|
|
2
|
+
// Service for normalizing flow data from server format to client format
|
|
3
|
+
|
|
4
|
+
import type { ContextualFlow } from './flowManager';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Server flow format types
|
|
8
|
+
*/
|
|
9
|
+
interface ServerFlow {
|
|
10
|
+
id: string;
|
|
11
|
+
type: string;
|
|
12
|
+
data: any;
|
|
13
|
+
context?: {
|
|
14
|
+
elementSelector?: string;
|
|
15
|
+
elementTrigger?: string;
|
|
16
|
+
elementLocation?: string;
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
};
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Normalizes server flow data to the client-side format
|
|
24
|
+
* @param serverFlows Array of flows in server format
|
|
25
|
+
* @returns Array of normalized flows
|
|
26
|
+
*/
|
|
27
|
+
export function normalizeFlows(serverFlows: ServerFlow[]): ContextualFlow[] {
|
|
28
|
+
return serverFlows.map(serverFlow => {
|
|
29
|
+
const baseFlow = {
|
|
30
|
+
id: serverFlow.id,
|
|
31
|
+
type: serverFlow.type.toLowerCase(),
|
|
32
|
+
payload: serverFlow.data || {},
|
|
33
|
+
originalData: serverFlow
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Add contextual properties if available
|
|
37
|
+
if (serverFlow.context) {
|
|
38
|
+
return {
|
|
39
|
+
...baseFlow,
|
|
40
|
+
elementSelector: serverFlow.context.elementSelector,
|
|
41
|
+
elementTrigger: serverFlow.context.elementTrigger || 'click',
|
|
42
|
+
elementLocation: serverFlow.context.elementLocation
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return baseFlow;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Normalizes a single server flow to client format
|
|
52
|
+
* @param serverFlow Flow in server format
|
|
53
|
+
* @returns Normalized flow
|
|
54
|
+
*/
|
|
55
|
+
export function normalizeFlow(serverFlow: ServerFlow): ContextualFlow {
|
|
56
|
+
const baseFlow = {
|
|
57
|
+
id: serverFlow.id,
|
|
58
|
+
type: serverFlow.type.toLowerCase(),
|
|
59
|
+
payload: serverFlow.data || {},
|
|
60
|
+
originalData: serverFlow
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Add contextual properties if available
|
|
64
|
+
if (serverFlow.context) {
|
|
65
|
+
return {
|
|
66
|
+
...baseFlow,
|
|
67
|
+
elementSelector: serverFlow.context.elementSelector,
|
|
68
|
+
elementTrigger: serverFlow.context.elementTrigger || 'click',
|
|
69
|
+
elementLocation: serverFlow.context.elementLocation
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return baseFlow;
|
|
74
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// src/services/locationContextService.ts
|
|
2
|
+
// Service to track the current screen/location context
|
|
3
|
+
|
|
4
|
+
export interface LocationContext {
|
|
5
|
+
currentPath: string;
|
|
6
|
+
screenId?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Service for managing the current location context within the application.
|
|
11
|
+
* Tracks the current page path and screen ID to enable contextual experiences.
|
|
12
|
+
*/
|
|
13
|
+
export class LocationContextService {
|
|
14
|
+
private static _instance: LocationContextService;
|
|
15
|
+
private _currentContext: LocationContext;
|
|
16
|
+
private _listeners: Set<(context: LocationContext) => void> = new Set();
|
|
17
|
+
|
|
18
|
+
private constructor() {
|
|
19
|
+
// Initialize with normalized path (remove leading slashes)
|
|
20
|
+
this._currentContext = {
|
|
21
|
+
currentPath: window.location.pathname.replace(/^\/+/, '')
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Listen for navigation events
|
|
25
|
+
window.addEventListener('popstate', this.updateContext.bind(this));
|
|
26
|
+
window.addEventListener('hashchange', this.updateContext.bind(this));
|
|
27
|
+
|
|
28
|
+
// For SPAs, hook into history API
|
|
29
|
+
this.monitorHistoryChanges();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get the singleton instance of the LocationContextService
|
|
34
|
+
*/
|
|
35
|
+
public static getInstance(): LocationContextService {
|
|
36
|
+
if (!this._instance) {
|
|
37
|
+
this._instance = new LocationContextService();
|
|
38
|
+
}
|
|
39
|
+
return this._instance;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Set the current screen ID
|
|
44
|
+
* @param screenId The ID of the current screen/view
|
|
45
|
+
*/
|
|
46
|
+
public setScreenId(screenId: string): void {
|
|
47
|
+
// Normalize screenId by removing leading forward slashes
|
|
48
|
+
const normalizedScreenId = screenId.replace(/^\/+/, '');
|
|
49
|
+
|
|
50
|
+
this._currentContext = {
|
|
51
|
+
...this._currentContext,
|
|
52
|
+
screenId: normalizedScreenId
|
|
53
|
+
};
|
|
54
|
+
this.notifyListeners();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Set the current location context
|
|
59
|
+
* @param context New context values
|
|
60
|
+
*/
|
|
61
|
+
public setContext(context: Partial<LocationContext>): void {
|
|
62
|
+
// Create normalized context by removing leading slashes
|
|
63
|
+
const normalizedContext: Partial<LocationContext> = { ...context };
|
|
64
|
+
|
|
65
|
+
if (normalizedContext.currentPath) {
|
|
66
|
+
normalizedContext.currentPath = normalizedContext.currentPath.replace(/^\/+/, '');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (normalizedContext.screenId) {
|
|
70
|
+
normalizedContext.screenId = normalizedContext.screenId.replace(/^\/+/, '');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this._currentContext = {
|
|
74
|
+
...this._currentContext,
|
|
75
|
+
...normalizedContext
|
|
76
|
+
};
|
|
77
|
+
this.notifyListeners();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the current location context
|
|
82
|
+
* @returns The current location context
|
|
83
|
+
*/
|
|
84
|
+
public getContext(): LocationContext {
|
|
85
|
+
return { ...this._currentContext };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Subscribe to location context changes
|
|
90
|
+
* @param listener Function to call when the context changes
|
|
91
|
+
* @returns Function to unsubscribe
|
|
92
|
+
*/
|
|
93
|
+
public subscribe(listener: (context: LocationContext) => void): () => void {
|
|
94
|
+
this._listeners.add(listener);
|
|
95
|
+
return () => this._listeners.delete(listener);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Monitor history API changes for SPAs
|
|
100
|
+
* This helps detect navigation in single-page applications
|
|
101
|
+
*/
|
|
102
|
+
private monitorHistoryChanges(): void {
|
|
103
|
+
// Store original methods
|
|
104
|
+
const originalPushState = history.pushState;
|
|
105
|
+
const originalReplaceState = history.replaceState;
|
|
106
|
+
|
|
107
|
+
// Override pushState
|
|
108
|
+
history.pushState = (...args) => {
|
|
109
|
+
originalPushState.apply(history, args);
|
|
110
|
+
this.updateContext();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Override replaceState
|
|
114
|
+
history.replaceState = (...args) => {
|
|
115
|
+
originalReplaceState.apply(history, args);
|
|
116
|
+
this.updateContext();
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Update the current context based on window location
|
|
122
|
+
*/
|
|
123
|
+
private updateContext(): void {
|
|
124
|
+
// Normalize pathname by removing leading forward slashes
|
|
125
|
+
const normalizedPath = window.location.pathname.replace(/^\/+/, '');
|
|
126
|
+
|
|
127
|
+
this._currentContext = {
|
|
128
|
+
...this._currentContext,
|
|
129
|
+
currentPath: normalizedPath
|
|
130
|
+
};
|
|
131
|
+
this.notifyListeners();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if the current location matches a specific location requirement
|
|
136
|
+
* @param elementLocation The required location/route from the flow
|
|
137
|
+
* @returns True if the current location matches the requirement
|
|
138
|
+
*/
|
|
139
|
+
public matchesLocation(elementLocation: string): boolean {
|
|
140
|
+
if (!elementLocation) return true; // No requirement means match all
|
|
141
|
+
|
|
142
|
+
const currentPath = this._currentContext.currentPath || '';
|
|
143
|
+
const currentScreenId = this._currentContext.screenId || '';
|
|
144
|
+
|
|
145
|
+
// Normalize the required location
|
|
146
|
+
const normalizedRequired = elementLocation.replace(/^\/+/, '').toLowerCase();
|
|
147
|
+
|
|
148
|
+
// Check exact matches
|
|
149
|
+
if (currentPath.toLowerCase() === normalizedRequired ||
|
|
150
|
+
currentScreenId.toLowerCase() === normalizedRequired) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check if current path contains the required location
|
|
155
|
+
if (currentPath.toLowerCase().includes(normalizedRequired)) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check if current path starts with the required location (for nested routes)
|
|
160
|
+
if (currentPath.toLowerCase().startsWith(normalizedRequired + '/')) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check if URL hash contains the location
|
|
165
|
+
const hash = window.location.hash.replace(/^#+/, '').toLowerCase();
|
|
166
|
+
if (hash === normalizedRequired || hash.includes(normalizedRequired)) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check if URL search params contain route indicators
|
|
171
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
172
|
+
const routeParam = urlParams.get('route') || urlParams.get('page') || urlParams.get('view');
|
|
173
|
+
if (routeParam && routeParam.toLowerCase() === normalizedRequired) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Notify all listeners of context change
|
|
182
|
+
*/
|
|
183
|
+
private notifyListeners(): void {
|
|
184
|
+
this._listeners.forEach(listener => listener(this.getContext()));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Export singleton instance
|
|
189
|
+
export const locationContext = LocationContextService.getInstance();
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// src/services/pageContextService.ts
|
|
2
|
+
// Page context management for SPA and multi-page applications
|
|
3
|
+
|
|
4
|
+
export interface PageContext {
|
|
5
|
+
href: string;
|
|
6
|
+
pathname: string;
|
|
7
|
+
hash: string;
|
|
8
|
+
search: string;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PageChangeEvent {
|
|
13
|
+
previous: PageContext | null;
|
|
14
|
+
current: PageContext;
|
|
15
|
+
type: 'initial' | 'navigation' | 'popstate' | 'reload';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type PageChangeHandler = (event: PageChangeEvent) => void;
|
|
19
|
+
|
|
20
|
+
export class PageContextService {
|
|
21
|
+
private static instance: PageContextService | null = null;
|
|
22
|
+
private handlers: Set<PageChangeHandler> = new Set();
|
|
23
|
+
private currentContext: PageContext | null = null;
|
|
24
|
+
private initialized = false;
|
|
25
|
+
|
|
26
|
+
private constructor() {}
|
|
27
|
+
|
|
28
|
+
static getInstance(): PageContextService {
|
|
29
|
+
if (!PageContextService.instance) {
|
|
30
|
+
PageContextService.instance = new PageContextService();
|
|
31
|
+
}
|
|
32
|
+
return PageContextService.instance;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Initialize page context tracking
|
|
37
|
+
*/
|
|
38
|
+
initialize(): void {
|
|
39
|
+
if (this.initialized) return;
|
|
40
|
+
|
|
41
|
+
console.debug('[DAP] PageContextService: Initializing...');
|
|
42
|
+
|
|
43
|
+
// Set initial context
|
|
44
|
+
this.currentContext = this.captureCurrentContext();
|
|
45
|
+
|
|
46
|
+
// Listen for browser navigation
|
|
47
|
+
window.addEventListener('popstate', this.handlePopState.bind(this));
|
|
48
|
+
|
|
49
|
+
// Hook into History API for SPA navigation
|
|
50
|
+
this.interceptHistoryMethods();
|
|
51
|
+
|
|
52
|
+
this.initialized = true;
|
|
53
|
+
console.debug('[DAP] PageContextService: Initialized with context:', this.currentContext);
|
|
54
|
+
|
|
55
|
+
// Notify initial page load
|
|
56
|
+
this.notifyHandlers({
|
|
57
|
+
previous: null,
|
|
58
|
+
current: this.currentContext,
|
|
59
|
+
type: 'initial'
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get current page context
|
|
65
|
+
*/
|
|
66
|
+
getCurrentContext(): PageContext {
|
|
67
|
+
if (!this.currentContext) {
|
|
68
|
+
this.currentContext = this.captureCurrentContext();
|
|
69
|
+
}
|
|
70
|
+
return this.currentContext;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Subscribe to page change events
|
|
75
|
+
*/
|
|
76
|
+
subscribe(handler: PageChangeHandler): () => void {
|
|
77
|
+
this.handlers.add(handler);
|
|
78
|
+
return () => {
|
|
79
|
+
this.handlers.delete(handler);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Manually trigger page context update (for framework integration)
|
|
85
|
+
*/
|
|
86
|
+
updateContext(type: 'navigation' | 'reload' = 'navigation'): void {
|
|
87
|
+
const previous = this.currentContext;
|
|
88
|
+
const current = this.captureCurrentContext();
|
|
89
|
+
|
|
90
|
+
if (!this.hasContextChanged(previous, current)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.debug('[DAP] PageContextService: Context changed:', {
|
|
95
|
+
from: previous?.pathname,
|
|
96
|
+
to: current.pathname
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
this.currentContext = current;
|
|
100
|
+
this.notifyHandlers({
|
|
101
|
+
previous,
|
|
102
|
+
current,
|
|
103
|
+
type
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Check if selector is relevant for current page
|
|
109
|
+
*/
|
|
110
|
+
isSelectorRelevant(selector: string): boolean {
|
|
111
|
+
if (!selector) return false;
|
|
112
|
+
|
|
113
|
+
// For now, we don't filter selectors by page context
|
|
114
|
+
// All selectors are potentially relevant until proven otherwise
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get page identifier for debugging/logging
|
|
120
|
+
*/
|
|
121
|
+
getPageId(): string {
|
|
122
|
+
const context = this.getCurrentContext();
|
|
123
|
+
return `${context.pathname}${context.hash}${context.search}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Clean up resources
|
|
128
|
+
*/
|
|
129
|
+
destroy(): void {
|
|
130
|
+
if (!this.initialized) return;
|
|
131
|
+
|
|
132
|
+
console.debug('[DAP] PageContextService: Destroying...');
|
|
133
|
+
|
|
134
|
+
window.removeEventListener('popstate', this.handlePopState.bind(this));
|
|
135
|
+
this.restoreHistoryMethods();
|
|
136
|
+
|
|
137
|
+
this.handlers.clear();
|
|
138
|
+
this.currentContext = null;
|
|
139
|
+
this.initialized = false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Private methods
|
|
143
|
+
|
|
144
|
+
private captureCurrentContext(): PageContext {
|
|
145
|
+
return {
|
|
146
|
+
href: window.location.href,
|
|
147
|
+
pathname: window.location.pathname,
|
|
148
|
+
hash: window.location.hash,
|
|
149
|
+
search: window.location.search,
|
|
150
|
+
timestamp: Date.now()
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private hasContextChanged(previous: PageContext | null, current: PageContext): boolean {
|
|
155
|
+
if (!previous) return true;
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
previous.href !== current.href ||
|
|
159
|
+
previous.pathname !== current.pathname ||
|
|
160
|
+
previous.hash !== current.hash ||
|
|
161
|
+
previous.search !== current.search
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private handlePopState(event: PopStateEvent): void {
|
|
166
|
+
console.debug('[DAP] PageContextService: Popstate event detected');
|
|
167
|
+
this.updateContext('navigation');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private originalPushState = history.pushState;
|
|
171
|
+
private originalReplaceState = history.replaceState;
|
|
172
|
+
|
|
173
|
+
private interceptHistoryMethods(): void {
|
|
174
|
+
const self = this;
|
|
175
|
+
|
|
176
|
+
// Intercept pushState for SPA navigation
|
|
177
|
+
history.pushState = function(state: any, title: string, url?: string | URL | null) {
|
|
178
|
+
self.originalPushState.apply(history, arguments as any);
|
|
179
|
+
console.debug('[DAP] PageContextService: PushState detected:', url);
|
|
180
|
+
|
|
181
|
+
// Use setTimeout to ensure URL has updated
|
|
182
|
+
setTimeout(() => {
|
|
183
|
+
self.updateContext('navigation');
|
|
184
|
+
}, 0);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Intercept replaceState for SPA navigation
|
|
188
|
+
history.replaceState = function(state: any, title: string, url?: string | URL | null) {
|
|
189
|
+
self.originalReplaceState.apply(history, arguments as any);
|
|
190
|
+
console.debug('[DAP] PageContextService: ReplaceState detected:', url);
|
|
191
|
+
|
|
192
|
+
// Use setTimeout to ensure URL has updated
|
|
193
|
+
setTimeout(() => {
|
|
194
|
+
self.updateContext('navigation');
|
|
195
|
+
}, 0);
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private restoreHistoryMethods(): void {
|
|
200
|
+
history.pushState = this.originalPushState;
|
|
201
|
+
history.replaceState = this.originalReplaceState;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private notifyHandlers(event: PageChangeEvent): void {
|
|
205
|
+
console.debug('[DAP] PageContextService: Notifying handlers:', event.type, {
|
|
206
|
+
from: event.previous?.pathname,
|
|
207
|
+
to: event.current.pathname
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
this.handlers.forEach(handler => {
|
|
211
|
+
try {
|
|
212
|
+
handler(event);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error('[DAP] PageContextService: Handler error:', error);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Export singleton instance
|
|
221
|
+
export const pageContextService = PageContextService.getInstance();
|