@uistate/core 4.0.0 → 4.0.1
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/package.json +1 -1
- package/src/stateInspector.js +0 -140
- package/src/telemetryPlugin.js +0 -638
package/package.json
CHANGED
package/src/stateInspector.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* StateInspector - Real-time state inspection panel for UIstate
|
|
3
|
-
*
|
|
4
|
-
* This module provides a configurable UI panel that displays and allows manipulation
|
|
5
|
-
* of CSS variables and state values in UIstate applications.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Toggle visibility with a dedicated button
|
|
9
|
-
* - Real-time updates of CSS variable values
|
|
10
|
-
* - Organized display of state by categories
|
|
11
|
-
* - Ability to filter state variables
|
|
12
|
-
* - Direct manipulation of state values
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Create a configured state inspector instance
|
|
17
|
-
* @param {Object} config - Configuration options
|
|
18
|
-
* @returns {Object} - StateInspector instance
|
|
19
|
-
*/
|
|
20
|
-
function createStateInspector(config = {}) {
|
|
21
|
-
// Default configuration
|
|
22
|
-
const defaultConfig = {
|
|
23
|
-
target: document.body,
|
|
24
|
-
initiallyVisible: false,
|
|
25
|
-
categories: ['app', 'ui', 'data'],
|
|
26
|
-
position: 'bottom-right',
|
|
27
|
-
maxHeight: '300px',
|
|
28
|
-
stateManager: null, // Optional reference to UIstate or CssState instance
|
|
29
|
-
theme: 'light'
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// Merge provided config with defaults
|
|
33
|
-
const options = { ...defaultConfig, ...config };
|
|
34
|
-
|
|
35
|
-
// Private state
|
|
36
|
-
let isVisible = options.initiallyVisible;
|
|
37
|
-
let panel = null;
|
|
38
|
-
let toggleButton = null;
|
|
39
|
-
let filterInput = null;
|
|
40
|
-
let stateContainer = null;
|
|
41
|
-
let currentFilter = '';
|
|
42
|
-
|
|
43
|
-
// StateInspector instance
|
|
44
|
-
const inspector = {
|
|
45
|
-
// Current configuration
|
|
46
|
-
config: options,
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Update configuration
|
|
50
|
-
* @param {Object} newConfig - New configuration options
|
|
51
|
-
*/
|
|
52
|
-
configure(newConfig) {
|
|
53
|
-
Object.assign(this.config, newConfig);
|
|
54
|
-
this.refresh();
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Attach the inspector panel to the target element
|
|
59
|
-
* @param {Element} element - Target element to attach to (defaults to config.target)
|
|
60
|
-
* @returns {Object} - The inspector instance for chaining
|
|
61
|
-
*/
|
|
62
|
-
attach(element = this.config.target) {
|
|
63
|
-
// Implementation will create and attach the panel
|
|
64
|
-
// For now, this is a placeholder
|
|
65
|
-
console.log('StateInspector: Panel would be attached to', element);
|
|
66
|
-
return this;
|
|
67
|
-
},
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Show the inspector panel
|
|
71
|
-
* @returns {Object} - The inspector instance for chaining
|
|
72
|
-
*/
|
|
73
|
-
show() {
|
|
74
|
-
isVisible = true;
|
|
75
|
-
if (panel) panel.style.display = 'block';
|
|
76
|
-
return this;
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Hide the inspector panel
|
|
81
|
-
* @returns {Object} - The inspector instance for chaining
|
|
82
|
-
*/
|
|
83
|
-
hide() {
|
|
84
|
-
isVisible = false;
|
|
85
|
-
if (panel) panel.style.display = 'none';
|
|
86
|
-
return this;
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Toggle the visibility of the inspector panel
|
|
91
|
-
* @returns {Object} - The inspector instance for chaining
|
|
92
|
-
*/
|
|
93
|
-
toggle() {
|
|
94
|
-
return isVisible ? this.hide() : this.show();
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Refresh the state display
|
|
99
|
-
* @returns {Object} - The inspector instance for chaining
|
|
100
|
-
*/
|
|
101
|
-
refresh() {
|
|
102
|
-
// Implementation will update the displayed state values
|
|
103
|
-
// For now, this is a placeholder
|
|
104
|
-
console.log('StateInspector: State display would be refreshed');
|
|
105
|
-
return this;
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Filter the displayed state variables
|
|
110
|
-
* @param {string} filterText - Text to filter by
|
|
111
|
-
* @returns {Object} - The inspector instance for chaining
|
|
112
|
-
*/
|
|
113
|
-
filter(filterText) {
|
|
114
|
-
currentFilter = filterText;
|
|
115
|
-
// Implementation will filter the displayed variables
|
|
116
|
-
return this.refresh();
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Clean up resources used by the inspector
|
|
121
|
-
*/
|
|
122
|
-
destroy() {
|
|
123
|
-
if (panel && panel.parentNode) {
|
|
124
|
-
panel.parentNode.removeChild(panel);
|
|
125
|
-
}
|
|
126
|
-
panel = null;
|
|
127
|
-
toggleButton = null;
|
|
128
|
-
filterInput = null;
|
|
129
|
-
stateContainer = null;
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
return inspector;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Create a default instance
|
|
137
|
-
const StateInspector = createStateInspector();
|
|
138
|
-
|
|
139
|
-
export default StateInspector;
|
|
140
|
-
export { createStateInspector };
|
package/src/telemetryPlugin.js
DELETED
|
@@ -1,638 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UIstate Telemetry Plugin
|
|
3
|
-
*
|
|
4
|
-
* A plugin for tracking and analyzing usage patterns in UIstate applications.
|
|
5
|
-
* Provides insights into method calls, state changes, and performance metrics.
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Automatic tracking of API method calls
|
|
9
|
-
* - State change monitoring
|
|
10
|
-
* - Performance timing
|
|
11
|
-
* - Usage statistics and reporting
|
|
12
|
-
* - Visual dashboard integration
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Create a telemetry plugin instance
|
|
17
|
-
* @param {Object} config - Configuration options
|
|
18
|
-
* @returns {Object} - Configured telemetry plugin
|
|
19
|
-
*/
|
|
20
|
-
const createTelemetryPlugin = (config = {}) => {
|
|
21
|
-
// Default configuration
|
|
22
|
-
const defaultConfig = {
|
|
23
|
-
enabled: true,
|
|
24
|
-
trackStateChanges: true,
|
|
25
|
-
trackMethodCalls: true,
|
|
26
|
-
trackPerformance: true,
|
|
27
|
-
maxEntries: 1000,
|
|
28
|
-
logToConsole: false,
|
|
29
|
-
samplingRate: 1.0, // 1.0 = track everything, 0.5 = track 50% of events
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// Merge provided config with defaults
|
|
33
|
-
const options = { ...defaultConfig, ...config };
|
|
34
|
-
|
|
35
|
-
// Telemetry data storage
|
|
36
|
-
const data = {
|
|
37
|
-
startTime: Date.now(),
|
|
38
|
-
methodCalls: new Map(),
|
|
39
|
-
stateChanges: new Map(),
|
|
40
|
-
performanceMetrics: new Map(),
|
|
41
|
-
unusedMethods: new Set(),
|
|
42
|
-
activeTimers: new Map()
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// Reference to the UIstate instance
|
|
46
|
-
let uistateRef = null;
|
|
47
|
-
|
|
48
|
-
// Original method references
|
|
49
|
-
const originalMethods = new Map();
|
|
50
|
-
|
|
51
|
-
// Methods to track (will be populated during initialization)
|
|
52
|
-
const methodsToTrack = [
|
|
53
|
-
'getState', 'setState', 'subscribe', 'observe',
|
|
54
|
-
'configureSerializer', 'getSerializerConfig', 'setSerializationMode',
|
|
55
|
-
'onAction', 'registerActions', 'attachDelegation', 'mount',
|
|
56
|
-
'renderTemplateFromCss', 'createComponent', 'applyClassesFromState'
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
// Helper function to check if we should sample this event
|
|
60
|
-
const shouldSample = () => {
|
|
61
|
-
return options.samplingRate >= 1.0 || Math.random() <= options.samplingRate;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// Helper function to track a method call
|
|
65
|
-
const trackMethodCall = (methodName, args = [], result = undefined, error = null, duration = 0) => {
|
|
66
|
-
if (!options.enabled || !options.trackMethodCalls || !shouldSample()) return;
|
|
67
|
-
|
|
68
|
-
if (!data.methodCalls.has(methodName)) {
|
|
69
|
-
data.methodCalls.set(methodName, []);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const calls = data.methodCalls.get(methodName);
|
|
73
|
-
|
|
74
|
-
// Limit the number of entries to prevent memory issues
|
|
75
|
-
if (calls.length >= options.maxEntries) {
|
|
76
|
-
calls.shift(); // Remove oldest entry
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Create a safe copy of arguments (avoiding circular references)
|
|
80
|
-
const safeArgs = Array.from(args).map(arg => {
|
|
81
|
-
try {
|
|
82
|
-
// For simple types, return as is
|
|
83
|
-
if (arg === null || arg === undefined ||
|
|
84
|
-
typeof arg === 'string' ||
|
|
85
|
-
typeof arg === 'number' ||
|
|
86
|
-
typeof arg === 'boolean') {
|
|
87
|
-
return arg;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// For functions, just return the function name or "[Function]"
|
|
91
|
-
if (typeof arg === 'function') {
|
|
92
|
-
return arg.name || "[Function]";
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// For objects, create a simplified representation
|
|
96
|
-
if (typeof arg === 'object') {
|
|
97
|
-
if (Array.isArray(arg)) {
|
|
98
|
-
return `[Array(${arg.length})]`;
|
|
99
|
-
}
|
|
100
|
-
return Object.keys(arg).length > 0
|
|
101
|
-
? `[Object: ${Object.keys(arg).join(', ')}]`
|
|
102
|
-
: '[Object]';
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return String(arg);
|
|
106
|
-
} catch (e) {
|
|
107
|
-
return "[Complex Value]";
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Create a safe copy of the result
|
|
112
|
-
let safeResult;
|
|
113
|
-
try {
|
|
114
|
-
if (result === null || result === undefined ||
|
|
115
|
-
typeof result === 'string' ||
|
|
116
|
-
typeof result === 'number' ||
|
|
117
|
-
typeof result === 'boolean') {
|
|
118
|
-
safeResult = result;
|
|
119
|
-
} else if (typeof result === 'function') {
|
|
120
|
-
safeResult = result.name || "[Function]";
|
|
121
|
-
} else if (typeof result === 'object') {
|
|
122
|
-
if (Array.isArray(result)) {
|
|
123
|
-
safeResult = `[Array(${result.length})]`;
|
|
124
|
-
} else {
|
|
125
|
-
safeResult = Object.keys(result).length > 0
|
|
126
|
-
? `[Object: ${Object.keys(result).join(', ')}]`
|
|
127
|
-
: '[Object]';
|
|
128
|
-
}
|
|
129
|
-
} else {
|
|
130
|
-
safeResult = String(result);
|
|
131
|
-
}
|
|
132
|
-
} catch (e) {
|
|
133
|
-
safeResult = "[Complex Value]";
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Record the call
|
|
137
|
-
calls.push({
|
|
138
|
-
timestamp: Date.now(),
|
|
139
|
-
relativeTime: Date.now() - data.startTime,
|
|
140
|
-
args: safeArgs,
|
|
141
|
-
result: safeResult,
|
|
142
|
-
error: error ? error.message : null,
|
|
143
|
-
duration
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Log to console if enabled
|
|
147
|
-
if (options.logToConsole) {
|
|
148
|
-
console.log(`[Telemetry] ${methodName}`, {
|
|
149
|
-
args: safeArgs,
|
|
150
|
-
result: safeResult,
|
|
151
|
-
error: error ? error.message : null,
|
|
152
|
-
duration
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
// Helper function to track state changes
|
|
158
|
-
const trackStateChange = (path, value, previousValue) => {
|
|
159
|
-
if (!options.enabled || !options.trackStateChanges || !shouldSample()) return;
|
|
160
|
-
|
|
161
|
-
if (!data.stateChanges.has(path)) {
|
|
162
|
-
data.stateChanges.set(path, []);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const changes = data.stateChanges.get(path);
|
|
166
|
-
|
|
167
|
-
// Limit the number of entries to prevent memory issues
|
|
168
|
-
if (changes.length >= options.maxEntries) {
|
|
169
|
-
changes.shift(); // Remove oldest entry
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Create safe copies of values
|
|
173
|
-
let safeValue, safePreviousValue;
|
|
174
|
-
try {
|
|
175
|
-
safeValue = JSON.stringify(value);
|
|
176
|
-
} catch (e) {
|
|
177
|
-
safeValue = String(value);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
safePreviousValue = JSON.stringify(previousValue);
|
|
182
|
-
} catch (e) {
|
|
183
|
-
safePreviousValue = String(previousValue);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Record the change
|
|
187
|
-
changes.push({
|
|
188
|
-
timestamp: Date.now(),
|
|
189
|
-
relativeTime: Date.now() - data.startTime,
|
|
190
|
-
value: safeValue,
|
|
191
|
-
previousValue: safePreviousValue
|
|
192
|
-
});
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
// Helper function to start a performance timer
|
|
196
|
-
const startTimer = (label) => {
|
|
197
|
-
if (!options.enabled || !options.trackPerformance) return;
|
|
198
|
-
|
|
199
|
-
data.activeTimers.set(label, performance.now());
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
// Helper function to end a performance timer
|
|
203
|
-
const endTimer = (label) => {
|
|
204
|
-
if (!options.enabled || !options.trackPerformance) return 0;
|
|
205
|
-
|
|
206
|
-
if (!data.activeTimers.has(label)) return 0;
|
|
207
|
-
|
|
208
|
-
const startTime = data.activeTimers.get(label);
|
|
209
|
-
const duration = performance.now() - startTime;
|
|
210
|
-
|
|
211
|
-
data.activeTimers.delete(label);
|
|
212
|
-
|
|
213
|
-
if (!data.performanceMetrics.has(label)) {
|
|
214
|
-
data.performanceMetrics.set(label, {
|
|
215
|
-
count: 0,
|
|
216
|
-
totalDuration: 0,
|
|
217
|
-
minDuration: Infinity,
|
|
218
|
-
maxDuration: 0,
|
|
219
|
-
samples: []
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const metrics = data.performanceMetrics.get(label);
|
|
224
|
-
metrics.count++;
|
|
225
|
-
metrics.totalDuration += duration;
|
|
226
|
-
metrics.minDuration = Math.min(metrics.minDuration, duration);
|
|
227
|
-
metrics.maxDuration = Math.max(metrics.maxDuration, duration);
|
|
228
|
-
|
|
229
|
-
// Store sample data (limited to prevent memory issues)
|
|
230
|
-
if (metrics.samples.length >= options.maxEntries) {
|
|
231
|
-
metrics.samples.shift();
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
metrics.samples.push({
|
|
235
|
-
timestamp: Date.now(),
|
|
236
|
-
relativeTime: Date.now() - data.startTime,
|
|
237
|
-
duration
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
return duration;
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
// Method proxying function
|
|
244
|
-
const createMethodProxy = (obj, methodName) => {
|
|
245
|
-
// Store original method
|
|
246
|
-
const originalMethod = obj[methodName];
|
|
247
|
-
originalMethods.set(methodName, originalMethod);
|
|
248
|
-
|
|
249
|
-
// Create proxy
|
|
250
|
-
return function(...args) {
|
|
251
|
-
if (!options.enabled) {
|
|
252
|
-
return originalMethod.apply(this, args);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
let result, error;
|
|
256
|
-
|
|
257
|
-
startTimer(`method.${methodName}`);
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
// Call original method
|
|
261
|
-
result = originalMethod.apply(this, args);
|
|
262
|
-
|
|
263
|
-
// Handle promises
|
|
264
|
-
if (result instanceof Promise) {
|
|
265
|
-
return result
|
|
266
|
-
.then(asyncResult => {
|
|
267
|
-
const duration = endTimer(`method.${methodName}`);
|
|
268
|
-
trackMethodCall(methodName, args, asyncResult, null, duration);
|
|
269
|
-
return asyncResult;
|
|
270
|
-
})
|
|
271
|
-
.catch(asyncError => {
|
|
272
|
-
const duration = endTimer(`method.${methodName}`);
|
|
273
|
-
trackMethodCall(methodName, args, undefined, asyncError, duration);
|
|
274
|
-
throw asyncError;
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const duration = endTimer(`method.${methodName}`);
|
|
279
|
-
trackMethodCall(methodName, args, result, null, duration);
|
|
280
|
-
return result;
|
|
281
|
-
|
|
282
|
-
} catch (e) {
|
|
283
|
-
error = e;
|
|
284
|
-
const duration = endTimer(`method.${methodName}`);
|
|
285
|
-
trackMethodCall(methodName, args, undefined, error, duration);
|
|
286
|
-
throw error;
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
// Create the telemetry plugin
|
|
292
|
-
const telemetryPlugin = {
|
|
293
|
-
name: 'telemetry',
|
|
294
|
-
|
|
295
|
-
// Initialize the plugin
|
|
296
|
-
init(api) {
|
|
297
|
-
if (!options.enabled) return this;
|
|
298
|
-
|
|
299
|
-
uistateRef = api;
|
|
300
|
-
|
|
301
|
-
// Proxy methods for tracking
|
|
302
|
-
if (options.trackMethodCalls) {
|
|
303
|
-
methodsToTrack.forEach(methodName => {
|
|
304
|
-
if (typeof api[methodName] === 'function') {
|
|
305
|
-
// Add to list of methods to check for usage
|
|
306
|
-
data.unusedMethods.add(methodName);
|
|
307
|
-
|
|
308
|
-
// Create proxy
|
|
309
|
-
api[methodName] = createMethodProxy(api, methodName);
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return this;
|
|
315
|
-
},
|
|
316
|
-
|
|
317
|
-
// Middleware for state changes
|
|
318
|
-
middlewares: [
|
|
319
|
-
(path, value, getState) => {
|
|
320
|
-
if (options.enabled && options.trackStateChanges) {
|
|
321
|
-
const previousValue = getState(path);
|
|
322
|
-
startTimer(`stateChange.${path}`);
|
|
323
|
-
|
|
324
|
-
// Return a proxy that will track after the state change
|
|
325
|
-
return {
|
|
326
|
-
__telemetryValue: true,
|
|
327
|
-
value,
|
|
328
|
-
path,
|
|
329
|
-
previousValue,
|
|
330
|
-
finalize() {
|
|
331
|
-
const duration = endTimer(`stateChange.${path}`);
|
|
332
|
-
trackStateChange(path, value, previousValue);
|
|
333
|
-
return value;
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
return value;
|
|
338
|
-
}
|
|
339
|
-
],
|
|
340
|
-
|
|
341
|
-
// Lifecycle hooks
|
|
342
|
-
hooks: {
|
|
343
|
-
beforeStateChange: (path, value) => {
|
|
344
|
-
// If this is our proxy value, do nothing
|
|
345
|
-
if (value && value.__telemetryValue) {
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
},
|
|
349
|
-
|
|
350
|
-
afterStateChange: (path, value) => {
|
|
351
|
-
// If this is our proxy value, finalize it
|
|
352
|
-
if (value && value.__telemetryValue) {
|
|
353
|
-
return value.finalize();
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
},
|
|
357
|
-
|
|
358
|
-
// Plugin methods
|
|
359
|
-
methods: {
|
|
360
|
-
// Enable or disable telemetry
|
|
361
|
-
setEnabled(enabled) {
|
|
362
|
-
options.enabled = enabled;
|
|
363
|
-
return this;
|
|
364
|
-
},
|
|
365
|
-
|
|
366
|
-
// Get current configuration
|
|
367
|
-
getConfig() {
|
|
368
|
-
return { ...options };
|
|
369
|
-
},
|
|
370
|
-
|
|
371
|
-
// Update configuration
|
|
372
|
-
configure(newConfig) {
|
|
373
|
-
Object.assign(options, newConfig);
|
|
374
|
-
return this;
|
|
375
|
-
},
|
|
376
|
-
|
|
377
|
-
// Reset telemetry data
|
|
378
|
-
reset() {
|
|
379
|
-
data.startTime = Date.now();
|
|
380
|
-
data.methodCalls.clear();
|
|
381
|
-
data.stateChanges.clear();
|
|
382
|
-
data.performanceMetrics.clear();
|
|
383
|
-
data.activeTimers.clear();
|
|
384
|
-
return this;
|
|
385
|
-
},
|
|
386
|
-
|
|
387
|
-
// Get method call statistics
|
|
388
|
-
getMethodStats() {
|
|
389
|
-
const stats = {};
|
|
390
|
-
|
|
391
|
-
data.methodCalls.forEach((calls, methodName) => {
|
|
392
|
-
// Mark method as used
|
|
393
|
-
data.unusedMethods.delete(methodName);
|
|
394
|
-
|
|
395
|
-
stats[methodName] = {
|
|
396
|
-
count: calls.length,
|
|
397
|
-
averageDuration: calls.reduce((sum, call) => sum + (call.duration || 0), 0) / calls.length,
|
|
398
|
-
lastCall: calls[calls.length - 1],
|
|
399
|
-
firstCall: calls[0]
|
|
400
|
-
};
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
return stats;
|
|
404
|
-
},
|
|
405
|
-
|
|
406
|
-
// Get detailed method call data
|
|
407
|
-
getMethodCalls(methodName) {
|
|
408
|
-
if (!methodName) {
|
|
409
|
-
const result = {};
|
|
410
|
-
data.methodCalls.forEach((calls, method) => {
|
|
411
|
-
result[method] = [...calls];
|
|
412
|
-
});
|
|
413
|
-
return result;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return data.methodCalls.has(methodName)
|
|
417
|
-
? [...data.methodCalls.get(methodName)]
|
|
418
|
-
: [];
|
|
419
|
-
},
|
|
420
|
-
|
|
421
|
-
// Get state change statistics
|
|
422
|
-
getStateStats() {
|
|
423
|
-
const stats = {};
|
|
424
|
-
|
|
425
|
-
data.stateChanges.forEach((changes, path) => {
|
|
426
|
-
stats[path] = {
|
|
427
|
-
count: changes.length,
|
|
428
|
-
lastChange: changes[changes.length - 1],
|
|
429
|
-
firstChange: changes[0]
|
|
430
|
-
};
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
return stats;
|
|
434
|
-
},
|
|
435
|
-
|
|
436
|
-
// Get detailed state change data
|
|
437
|
-
getStateChanges(path) {
|
|
438
|
-
if (!path) {
|
|
439
|
-
const result = {};
|
|
440
|
-
data.stateChanges.forEach((changes, statePath) => {
|
|
441
|
-
result[statePath] = [...changes];
|
|
442
|
-
});
|
|
443
|
-
return result;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
return data.stateChanges.has(path)
|
|
447
|
-
? [...data.stateChanges.get(path)]
|
|
448
|
-
: [];
|
|
449
|
-
},
|
|
450
|
-
|
|
451
|
-
// Get performance metrics
|
|
452
|
-
getPerformanceMetrics() {
|
|
453
|
-
const metrics = {};
|
|
454
|
-
|
|
455
|
-
data.performanceMetrics.forEach((data, label) => {
|
|
456
|
-
metrics[label] = {
|
|
457
|
-
count: data.count,
|
|
458
|
-
totalDuration: data.totalDuration,
|
|
459
|
-
averageDuration: data.totalDuration / data.count,
|
|
460
|
-
minDuration: data.minDuration,
|
|
461
|
-
maxDuration: data.maxDuration
|
|
462
|
-
};
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
return metrics;
|
|
466
|
-
},
|
|
467
|
-
|
|
468
|
-
// Get unused methods
|
|
469
|
-
getUnusedMethods() {
|
|
470
|
-
return [...data.unusedMethods];
|
|
471
|
-
},
|
|
472
|
-
|
|
473
|
-
// Get comprehensive telemetry report
|
|
474
|
-
getReport() {
|
|
475
|
-
return {
|
|
476
|
-
startTime: data.startTime,
|
|
477
|
-
duration: Date.now() - data.startTime,
|
|
478
|
-
methodStats: this.getMethodStats(),
|
|
479
|
-
stateStats: this.getStateStats(),
|
|
480
|
-
performanceMetrics: this.getPerformanceMetrics(),
|
|
481
|
-
unusedMethods: this.getUnusedMethods(),
|
|
482
|
-
config: this.getConfig(),
|
|
483
|
-
// Add detailed logs for download
|
|
484
|
-
detailedLogs: {
|
|
485
|
-
methodCalls: this.getMethodCalls(),
|
|
486
|
-
stateChanges: this.getStateChanges()
|
|
487
|
-
}
|
|
488
|
-
};
|
|
489
|
-
},
|
|
490
|
-
|
|
491
|
-
// Download telemetry logs as a JSON file
|
|
492
|
-
downloadLogs() {
|
|
493
|
-
const report = this.getReport();
|
|
494
|
-
const data = JSON.stringify(report, null, 2);
|
|
495
|
-
const blob = new Blob([data], { type: 'application/json' });
|
|
496
|
-
const url = URL.createObjectURL(blob);
|
|
497
|
-
|
|
498
|
-
const a = document.createElement('a');
|
|
499
|
-
a.href = url;
|
|
500
|
-
a.download = `uistate-telemetry-${new Date().toISOString().replace(/:/g, '-')}.json`;
|
|
501
|
-
document.body.appendChild(a);
|
|
502
|
-
a.click();
|
|
503
|
-
document.body.removeChild(a);
|
|
504
|
-
URL.revokeObjectURL(url);
|
|
505
|
-
|
|
506
|
-
return true;
|
|
507
|
-
},
|
|
508
|
-
|
|
509
|
-
// Create a visual dashboard
|
|
510
|
-
createDashboard(container = document.body) {
|
|
511
|
-
// Create dashboard element
|
|
512
|
-
const dashboard = document.createElement('div');
|
|
513
|
-
dashboard.className = 'uistate-telemetry-dashboard';
|
|
514
|
-
dashboard.style.cssText = `
|
|
515
|
-
position: fixed;
|
|
516
|
-
bottom: 10px;
|
|
517
|
-
right: 10px;
|
|
518
|
-
width: 300px;
|
|
519
|
-
max-height: 400px;
|
|
520
|
-
overflow: auto;
|
|
521
|
-
background: #fff;
|
|
522
|
-
border: 1px solid #ccc;
|
|
523
|
-
border-radius: 4px;
|
|
524
|
-
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
525
|
-
padding: 10px;
|
|
526
|
-
font-family: monospace;
|
|
527
|
-
font-size: 12px;
|
|
528
|
-
z-index: 9999;
|
|
529
|
-
`;
|
|
530
|
-
|
|
531
|
-
// Update function
|
|
532
|
-
const updateDashboard = () => {
|
|
533
|
-
const report = this.getReport();
|
|
534
|
-
|
|
535
|
-
dashboard.innerHTML = `
|
|
536
|
-
<h3 style="margin: 0 0 10px; font-size: 14px;">UIstate Telemetry</h3>
|
|
537
|
-
<div style="margin-bottom: 5px;">
|
|
538
|
-
<button id="telemetry-refresh">Refresh</button>
|
|
539
|
-
<button id="telemetry-reset">Reset</button>
|
|
540
|
-
<button id="telemetry-close">Close</button>
|
|
541
|
-
</div>
|
|
542
|
-
<div style="margin: 10px 0;">
|
|
543
|
-
<strong>Duration:</strong> ${Math.round((Date.now() - report.startTime) / 1000)}s
|
|
544
|
-
</div>
|
|
545
|
-
|
|
546
|
-
<h4 style="margin: 10px 0 5px; font-size: 13px;">Method Calls</h4>
|
|
547
|
-
<table style="width: 100%; border-collapse: collapse;">
|
|
548
|
-
<tr>
|
|
549
|
-
<th style="text-align: left; border-bottom: 1px solid #eee;">Method</th>
|
|
550
|
-
<th style="text-align: right; border-bottom: 1px solid #eee;">Count</th>
|
|
551
|
-
<th style="text-align: right; border-bottom: 1px solid #eee;">Avg Time</th>
|
|
552
|
-
</tr>
|
|
553
|
-
${Object.entries(report.methodStats)
|
|
554
|
-
.sort((a, b) => b[1].count - a[1].count)
|
|
555
|
-
.map(([method, stats]) => `
|
|
556
|
-
<tr>
|
|
557
|
-
<td style="padding: 2px 0; border-bottom: 1px solid #eee;">${method}</td>
|
|
558
|
-
<td style="text-align: right; padding: 2px 0; border-bottom: 1px solid #eee;">${stats.count}</td>
|
|
559
|
-
<td style="text-align: right; padding: 2px 0; border-bottom: 1px solid #eee;">${stats.averageDuration ? stats.averageDuration.toFixed(2) + 'ms' : 'n/a'}</td>
|
|
560
|
-
</tr>
|
|
561
|
-
`).join('')}
|
|
562
|
-
</table>
|
|
563
|
-
|
|
564
|
-
<h4 style="margin: 10px 0 5px; font-size: 13px;">State Changes</h4>
|
|
565
|
-
<table style="width: 100%; border-collapse: collapse;">
|
|
566
|
-
<tr>
|
|
567
|
-
<th style="text-align: left; border-bottom: 1px solid #eee;">Path</th>
|
|
568
|
-
<th style="text-align: right; border-bottom: 1px solid #eee;">Count</th>
|
|
569
|
-
</tr>
|
|
570
|
-
${Object.entries(report.stateStats)
|
|
571
|
-
.sort((a, b) => b[1].count - a[1].count)
|
|
572
|
-
.map(([path, stats]) => `
|
|
573
|
-
<tr>
|
|
574
|
-
<td style="padding: 2px 0; border-bottom: 1px solid #eee;">${path}</td>
|
|
575
|
-
<td style="text-align: right; padding: 2px 0; border-bottom: 1px solid #eee;">${stats.count}</td>
|
|
576
|
-
</tr>
|
|
577
|
-
`).join('')}
|
|
578
|
-
</table>
|
|
579
|
-
|
|
580
|
-
${report.unusedMethods.length > 0 ? `
|
|
581
|
-
<h4 style="margin: 10px 0 5px; font-size: 13px;">Unused Methods</h4>
|
|
582
|
-
<div style="color: #666;">
|
|
583
|
-
${report.unusedMethods.join(', ')}
|
|
584
|
-
</div>
|
|
585
|
-
` : ''}
|
|
586
|
-
`;
|
|
587
|
-
|
|
588
|
-
// Add event listeners
|
|
589
|
-
dashboard.querySelector('#telemetry-refresh').addEventListener('click', updateDashboard);
|
|
590
|
-
dashboard.querySelector('#telemetry-reset').addEventListener('click', () => {
|
|
591
|
-
this.reset();
|
|
592
|
-
updateDashboard();
|
|
593
|
-
});
|
|
594
|
-
dashboard.querySelector('#telemetry-close').addEventListener('click', () => {
|
|
595
|
-
dashboard.remove();
|
|
596
|
-
});
|
|
597
|
-
};
|
|
598
|
-
|
|
599
|
-
// Initial update
|
|
600
|
-
updateDashboard();
|
|
601
|
-
|
|
602
|
-
// Add to container
|
|
603
|
-
container.appendChild(dashboard);
|
|
604
|
-
|
|
605
|
-
return dashboard;
|
|
606
|
-
}
|
|
607
|
-
},
|
|
608
|
-
|
|
609
|
-
// Clean up
|
|
610
|
-
destroy() {
|
|
611
|
-
// Restore original methods
|
|
612
|
-
if (uistateRef) {
|
|
613
|
-
originalMethods.forEach((originalMethod, methodName) => {
|
|
614
|
-
if (uistateRef[methodName]) {
|
|
615
|
-
uistateRef[methodName] = originalMethod;
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// Clear data
|
|
621
|
-
data.methodCalls.clear();
|
|
622
|
-
data.stateChanges.clear();
|
|
623
|
-
data.performanceMetrics.clear();
|
|
624
|
-
data.activeTimers.clear();
|
|
625
|
-
data.unusedMethods.clear();
|
|
626
|
-
|
|
627
|
-
uistateRef = null;
|
|
628
|
-
}
|
|
629
|
-
};
|
|
630
|
-
|
|
631
|
-
return telemetryPlugin;
|
|
632
|
-
};
|
|
633
|
-
|
|
634
|
-
// Create a default instance with standard configuration
|
|
635
|
-
const telemetryPlugin = createTelemetryPlugin();
|
|
636
|
-
|
|
637
|
-
export default telemetryPlugin;
|
|
638
|
-
export { createTelemetryPlugin };
|