@appxiom/web 0.1.8

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.
Binary file
@@ -0,0 +1,48 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ export const memory: WebAssembly.Memory;
4
+ export const __wbg_get_leakrecord_age: (a: number) => number;
5
+ export const __wbg_get_leakrecord_first_seen: (a: number) => bigint;
6
+ export const __wbg_get_leakrecord_gc_survivals: (a: number) => number;
7
+ export const __wbg_get_leakrecord_instance_id: (a: number) => number;
8
+ export const __wbg_get_leakrecord_last_gc_epoch: (a: number) => number;
9
+ export const __wbg_leakanalyzer_free: (a: number, b: number) => void;
10
+ export const __wbg_leakrecord_free: (a: number, b: number) => void;
11
+ export const __wbg_set_leakrecord_age: (a: number, b: number) => void;
12
+ export const __wbg_set_leakrecord_first_seen: (a: number, b: bigint) => void;
13
+ export const __wbg_set_leakrecord_gc_survivals: (a: number, b: number) => void;
14
+ export const __wbg_set_leakrecord_instance_id: (a: number, b: number) => void;
15
+ export const __wbg_set_leakrecord_last_gc_epoch: (a: number, b: number) => void;
16
+ export const end_goal: (a: number, b: number) => number;
17
+ export const get_device_state_js: () => number;
18
+ export const handle_api_after_request: (a: number, b: number, c: number, d: bigint) => number;
19
+ export const handle_api_after_response: (a: number, b: number, c: number, d: bigint, e: number, f: number, g: bigint, h: number) => number;
20
+ export const handle_api_on_error: (a: number, b: number, c: number, d: bigint, e: number) => number;
21
+ export const init: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => number;
22
+ export const leakanalyzer_evaluate_leaks: (a: number) => number;
23
+ export const leakanalyzer_new: () => number;
24
+ export const leakanalyzer_notify_gc_epoch: (a: number) => number;
25
+ export const leakanalyzer_report_leak: (a: number) => number;
26
+ export const leakanalyzer_track_instance: (a: number, b: number) => number;
27
+ export const log_activity: (a: number, b: number, c: number) => number;
28
+ export const log_event: (a: number, b: number) => number;
29
+ export const log_event_activity: (a: number, b: number, c: number) => number;
30
+ export const log_resource_load_error: (a: number, b: number, c: number) => number;
31
+ export const report_custom_issue: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => number;
32
+ export const report_error: (a: number) => number;
33
+ export const set_custom_id: (a: number, b: number) => number;
34
+ export const start_goal: (a: number, b: number) => number;
35
+ export const __wasm_bindgen_func_elem_458: (a: number, b: number, c: number) => void;
36
+ export const __wasm_bindgen_func_elem_1045: (a: number, b: number, c: number, d: number) => void;
37
+ export const __wasm_bindgen_func_elem_1102: (a: number, b: number, c: number, d: number) => void;
38
+ export const __wasm_bindgen_func_elem_455: (a: number, b: number, c: number) => void;
39
+ export const __wasm_bindgen_func_elem_461: (a: number, b: number, c: number) => void;
40
+ export const __wasm_bindgen_func_elem_461_4: (a: number, b: number, c: number) => void;
41
+ export const __wasm_bindgen_func_elem_450: (a: number, b: number, c: number) => void;
42
+ export const __wasm_bindgen_func_elem_465: (a: number, b: number) => void;
43
+ export const __wbindgen_export: (a: number, b: number) => number;
44
+ export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
45
+ export const __wbindgen_export3: (a: number) => void;
46
+ export const __wbindgen_export4: (a: number, b: number) => void;
47
+ export const __wbindgen_export5: (a: number, b: number, c: number) => void;
48
+ export const __wbindgen_add_to_stack_pointer: (a: number) => number;
@@ -0,0 +1,10 @@
1
+ import AX from '../core/ax.js';
2
+
3
+
4
+
5
+ // Add a new function ax to console object. It should have blue color and aprefix with "AX: "
6
+ const log_api = AX.instance.wasm.log_api;
7
+ console.ax = (message) => {
8
+ //log_api(`AX: ${message}`);
9
+ console.log(`%cAX: ${message}`, 'color: blue; font-weight: bold;');
10
+ };
package/core/ax.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Singleton class representing the AX-JS core module.
3
+ */
4
+
5
+ import * as wasm from '../ax_web_core/ax_web_core.js';
6
+
7
+ export default class AX {
8
+ // Static instance
9
+ static instance = null;
10
+
11
+ // Application and platform keys
12
+ appKey;
13
+ platformKey;
14
+ version;
15
+ buildIdentifier;
16
+ wasm;
17
+
18
+ // Singleton pattern
19
+
20
+ constructor(appKey, platformKey, version, buildIdentifier) {
21
+ if (AX.instance) return AX.instance;
22
+ this.wasm = wasm;
23
+ this.appKey = appKey;
24
+ this.platformKey = platformKey;
25
+ this.version = version;
26
+ this.buildIdentifier = buildIdentifier;
27
+ AX.instance = this;
28
+ // Additional initialization logic can go here
29
+ }
30
+
31
+ // Launch the AX module
32
+ async launch() {
33
+ // Load wasm module
34
+ await this.wasm.default();
35
+ // Load runtime patches synchronously using dynamic imports
36
+ await import('../console/patch.js');
37
+ await import('../error/patch.js');
38
+ await import('../http/ax-http.js');
39
+ await import('../event/ax-event.js');
40
+ await import('../memory/ax-dom-leak.js');
41
+
42
+ // initialize the wasm module with app key, platform key, version, and build identifier
43
+ await this.wasm.init(this.appKey, this.platformKey, this.version, this.buildIdentifier);
44
+
45
+ }
46
+
47
+ // Start a goal with the given name
48
+ startGoal(name) {
49
+ if (this.wasm && this.wasm.start_goal) {
50
+ this.wasm.start_goal(name);
51
+ } else {
52
+ // console.warn("AX Wasm not loaded, cannot start goal:", name);
53
+ }
54
+ }
55
+
56
+ // End a goal with the given name, properties, and value
57
+ endGoal(name, properties = {}, value = null) {
58
+ if (this.wasm && this.wasm.end_goal) {
59
+ this.wasm.end_goal(name, "Success", properties, value);
60
+ } else {
61
+ // console.warn("AX Wasm not loaded, cannot end goal:", name);
62
+ }
63
+ }
64
+
65
+ // Report a custom issue
66
+ // Severity can be
67
+ // - 0 -> Minor
68
+ // - 1 -> Major
69
+ // - 2 -> Fatal
70
+ reportCustomIssue(title, description, severity, data = {}, stacktrace = null) {
71
+ if (this.wasm && this.wasm.report_custom_issue) {
72
+ this.wasm.report_custom_issue(title, description, severity, data, stacktrace);
73
+ }
74
+ }
75
+
76
+ // Set a custom ID
77
+ setCustomId(customId) {
78
+ if (this.wasm && this.wasm.set_custom_id) {
79
+ this.wasm.set_custom_id(customId);
80
+ }
81
+ }
82
+
83
+ // Log activity with the given name and properties
84
+ logActivity(name, properties = {}) {
85
+ if (this.wasm && this.wasm.log_activity) {
86
+ this.wasm.log_activity(name, properties);
87
+ }
88
+ }
89
+
90
+ // Log an event activity with the given name and properties
91
+ logEventActivity(name, properties = {}) {
92
+ if (this.wasm && this.wasm.log_event_activity) {
93
+ this.wasm.log_event_activity(name, properties);
94
+ }
95
+ }
96
+ }
package/error/patch.js ADDED
@@ -0,0 +1,47 @@
1
+ import AX from '../core/ax.js';
2
+
3
+ // 1. Patch Sync errors.
4
+ // Store old onerror handler if it exists
5
+ const oldOnError = window.onerror;
6
+ window.onerror = function(message, source, lineno, colno, error) {
7
+ if (oldOnError) {
8
+ try {
9
+ oldOnError(message, source, lineno, colno, error);
10
+ } catch (e) {}
11
+ }
12
+ handleOnError(1, message, source, lineno, colno, error);
13
+ }
14
+
15
+ async function handleOnError(category, message, source, lineno, colno, error) {
16
+ let e = {
17
+ category: category,
18
+ message: message,
19
+ fileName: source,
20
+ lineNumber: lineno,
21
+ columnNumber: colno,
22
+ stacktrace: error ? error.stack : null
23
+ };
24
+ AX.instance.wasm.report_error(e);
25
+ }
26
+
27
+ // 2. Async errors
28
+ window.addEventListener('unhandledrejection', function(event) {
29
+ handleOnError(2, event.reason ? event.reason.message : null, null, null, null, event.reason);
30
+ });
31
+
32
+ // 3. Late handled promises - rejectionhandled
33
+ window.addEventListener('rejectionhandled', function(event) {
34
+ handleOnError(3, event.reason ? event.reason.message : null, null, null, null, event.reason);
35
+ });
36
+
37
+ // 4. Resource loading errors
38
+ window.addEventListener('error', function(event) {
39
+ if (event.target && (event.target.src || event.target.href)) {
40
+ const url = event.target.src || event.target.href;
41
+ AX.instance.wasm.log_resource_load_error(url, {
42
+ message: event.message || 'Resource load error',
43
+ tagName: event.target.tagName,
44
+ innerHTML: event.target.innerHTML,
45
+ });
46
+ }
47
+ }, true);
@@ -0,0 +1,167 @@
1
+ import AX from '../core/ax.js';
2
+
3
+ // Track page load and unload events
4
+ function reportExit() {
5
+ // navigator.sendBeacon(
6
+ // "/telemetry",
7
+ // JSON.stringify({
8
+ // type: "page_exit",
9
+ // url: location.href,
10
+ // ts: Date.now(),
11
+ // })
12
+ // );
13
+ }
14
+
15
+ // Track page load
16
+ window.addEventListener("load", () => {
17
+ AX.instance.wasm.log_event_activity("Page Load", {
18
+ url: location.href,
19
+ referrer: document.referrer,
20
+ userAgent: navigator.userAgent,
21
+ });
22
+ });
23
+
24
+ // // Track page unload
25
+ // document.addEventListener("visibilitychange", () => {
26
+ // if (document.visibilityState === "hidden") {
27
+ // AX.instance.wasm.log_event_activity("Page out of visibility", {
28
+ // url: location.href,
29
+ // ts: Date.now(),
30
+ // });
31
+ // reportExit();
32
+ // } else if (document.visibilityState === "visible") {
33
+ // AX.instance.wasm.log_event_activity("Page in visibility", {
34
+ // url: location.href,
35
+ // ts: Date.now(),
36
+ // });
37
+ // }
38
+ // });
39
+
40
+ // // Also track pagehide for older browsers
41
+ // window.addEventListener("pagehide", () => {
42
+ // AX.instance.wasm.log_event_activity("Page Unload", {
43
+ // url: location.href,
44
+ // ts: Date.now(),
45
+ // });
46
+ // reportExit();
47
+ // });
48
+
49
+ // Track SPA route changes (if using a SPA framework)
50
+ // This is a generic example; you may need to adapt it based on your SPA framework.
51
+ window.addEventListener("popstate", () => {
52
+ AX.instance.wasm.log_event_activity("SPA Route Change", {
53
+ url: location.href,
54
+ ts: Date.now(),
55
+ });
56
+ });
57
+
58
+ // Track hash changes for SPA routing
59
+ window.addEventListener("hashchange", () => {
60
+ AX.instance.wasm.log_event_activity("Hash Change", {
61
+ url: location.href,
62
+ ts: Date.now(),
63
+ });
64
+ });
65
+
66
+ // Patch pushState and replaceState to track SPA route changes
67
+ (function () {
68
+ const originalPushState = history.pushState;
69
+ const originalReplaceState = history.replaceState;
70
+
71
+ history.pushState = function (...args) {
72
+ AX.instance.wasm.log_event_activity("SPA Route Change (pushState)", {
73
+ url: location.href,
74
+ ts: Date.now(),
75
+ });
76
+ // dispatch event ax-push-state.
77
+ const pushStateEvent = new Event('ax-push-state');
78
+ window.dispatchEvent(pushStateEvent);
79
+
80
+ const result = originalPushState.apply(this, args);
81
+ return result;
82
+ };
83
+
84
+ history.replaceState = function (...args) {
85
+ AX.instance.wasm.log_event_activity("SPA Route Change (replaceState)", {
86
+ url: location.href,
87
+ ts: Date.now(),
88
+ });
89
+ const replaceStateEvent = new Event('ax-replace-state');
90
+ window.dispatchEvent(replaceStateEvent);
91
+
92
+ const result = originalReplaceState.apply(this, args);
93
+ return result;
94
+ };
95
+ })();
96
+
97
+ // Track clicks on buttons and links
98
+ document.addEventListener('click', (event) => {
99
+ const target = event.target.closest('button, a, li');
100
+ if (!target) return;
101
+
102
+ const info = {
103
+ type: event.type,
104
+ tag: target.tagName.toLowerCase(),
105
+ text: target.innerText.trim(),
106
+ href: target.getAttribute('href'),
107
+ x: event.clientX,
108
+ y: event.clientY,
109
+ ctrl: event.ctrlKey,
110
+ shift: event.shiftKey,
111
+ meta: event.metaKey,
112
+ button: event.button,
113
+ timestamp: event.timeStamp,
114
+ url: window.location.href,
115
+ path: event.composedPath().map(el => el.tagName || el.nodeName),
116
+ };
117
+
118
+ AX.instance.wasm.log_event_activity(`Clicked - ${info.tag} - ${info.text}`, info);
119
+ });
120
+
121
+ // Track form elements
122
+ // listen for events after focus is changed from the element.
123
+ document.addEventListener('change', function (event) {
124
+ const el = event.target;
125
+ if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) {
126
+ return; // Ignore non-form elements
127
+ }
128
+
129
+ const fieldType = el.type || el.tagName.toLowerCase();
130
+ let valueSummary;
131
+
132
+ switch (fieldType) {
133
+ case 'checkbox':
134
+ case 'radio':
135
+ valueSummary = el.checked;
136
+ break;
137
+ case 'select-one':
138
+ valueSummary = el.selectedIndex; // or el.value if it's non-sensitive
139
+ break;
140
+ case 'select-multiple':
141
+ valueSummary = Array.from(el.selectedOptions).map(o => o.index);
142
+ break;
143
+ case 'range':
144
+ case 'number':
145
+ valueSummary = Number(el.value);
146
+ break;
147
+ default:
148
+ valueSummary = el.value.length; // just length, not content
149
+ name = el.name || null;
150
+ break;
151
+ }
152
+
153
+ const data = {
154
+ tag: el.tagName.toLowerCase(),
155
+ type: fieldType,
156
+ name: el.name || null,
157
+ formName: el.form ? el.form.name : null,
158
+ formId: el.form ? el.form.id : null,
159
+ length: typeof valueSummary === 'number' ? valueSummary : 0,
160
+ value: valueSummary,//typeof valueSummary !== 'number' ? valueSummary : "*",
161
+ timestamp: Date.now(),
162
+ };
163
+
164
+ // Example: send to your analytics endpoint
165
+ // navigator.sendBeacon('/track', JSON.stringify({ event: 'form_input', ...data }));
166
+ AX.instance.wasm.log_event_activity(`FormFill - ${data.tag} - ${data.name}`, data);
167
+ });
@@ -0,0 +1,63 @@
1
+ import AX from '../core/ax.js';
2
+
3
+ // Track fetch
4
+ const originalFetch = window.fetch;
5
+ window.fetch = async function(...args) {
6
+ const [resource, init] = args;
7
+ const request = resource instanceof Request && init == null
8
+ ? resource.clone()
9
+ : new Request(resource, init);
10
+
11
+ // If request headers contain X-App-Key, skip tracking to avoid infinite loop
12
+ if (request.headers.get('X-App-Key'))
13
+ return originalFetch.apply(this, args);
14
+
15
+ const url = request.url;
16
+ const requestTime = BigInt(Date.now());
17
+
18
+ handleApiCallAfterRequest(url, request, requestTime);
19
+
20
+ // Clone response to safely read body
21
+ let response = null;
22
+ try {
23
+ response = await originalFetch.apply(this, args);
24
+ let clonedResponse = response.clone();
25
+ handleApiCallAfterResponse(url, request, requestTime, clonedResponse, BigInt(Date.now()), new Error("Request Location:"));
26
+ } catch (e) {
27
+ handleApiCallOnError(url, request, requestTime, e);
28
+ throw e; // Re-throw the error to maintain the original behavior
29
+ }
30
+ return response;
31
+ };
32
+
33
+ // Track XMLHttpRequest
34
+ const originalOpen = XMLHttpRequest.prototype.open;
35
+ XMLHttpRequest.prototype.open = function(method, url, ...rest) {
36
+ this._apiTrackerUrl = url;
37
+ this._apiTrackerMethod = method;
38
+ let result = originalOpen.apply(this, [method, url, ...rest]);
39
+ // console.ax(`[API TRACKER] XMLHttpRequest opened: ${method} ${url}`);
40
+ return result;
41
+ };
42
+ const originalSend = XMLHttpRequest.prototype.send;
43
+ XMLHttpRequest.prototype.send = function(...args) {
44
+ // if (console.ax && this._apiTrackerUrl && (this._apiTrackerUrl.startsWith('http://') || this._apiTrackerUrl.startsWith('https://')))
45
+ // console.ax(`[API TRACKER] XHR request: ${this._apiTrackerMethod} ${this._apiTrackerUrl} ${JSON.stringify(args[0] || {})}`);
46
+ this.addEventListener('loadend', function() {
47
+ // if (console.ax && this._apiTrackerUrl && (this._apiTrackerUrl.startsWith('http://') || this._apiTrackerUrl.startsWith('https://')))
48
+ // console.ax(`[API TRACKER] XHR response: ${this._apiTrackerMethod} ${this._apiTrackerUrl} status=${this.status} response=${this.response}`);
49
+ });
50
+ return originalSend.apply(this, args);
51
+ };
52
+
53
+ async function handleApiCallAfterResponse(url, request, requestTime, response, responseTime, error) {
54
+ AX.instance.wasm.handle_api_after_response(url, request, requestTime, response, responseTime, error);
55
+ }
56
+
57
+ async function handleApiCallAfterRequest(url, request, requestTime) {
58
+ AX.instance.wasm.handle_api_after_request(url, request, requestTime);
59
+ }
60
+
61
+ async function handleApiCallOnError(url, request, requestTime, error) {
62
+ AX.instance.wasm.handle_api_on_error(url, request, requestTime, error);
63
+ }
package/index.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ export type IssueSeverity = 0 | 1 | 2;
2
+
3
+ export interface AppxiomProperties {
4
+ [key: string]: unknown;
5
+ }
6
+
7
+ export class AX {
8
+ static instance: AX | null;
9
+
10
+ constructor(
11
+ appKey: string,
12
+ platformKey: string,
13
+ version: string,
14
+ buildIdentifier?: string,
15
+ );
16
+
17
+ launch(): Promise<void>;
18
+ startGoal(name: string): void;
19
+ endGoal<TProperties extends object = AppxiomProperties>(
20
+ name: string,
21
+ properties?: TProperties,
22
+ value?: number | null,
23
+ ): void;
24
+ reportCustomIssue<TData extends object = AppxiomProperties>(
25
+ title: string,
26
+ description: string,
27
+ severity: IssueSeverity,
28
+ data?: TData,
29
+ stacktrace?: string | null,
30
+ ): void;
31
+ setCustomId(customId: string): void;
32
+ logActivity<TProperties extends object = AppxiomProperties>(
33
+ name: string,
34
+ properties?: TProperties,
35
+ ): void;
36
+ logEventActivity<TProperties extends object = AppxiomProperties>(
37
+ name: string,
38
+ properties?: TProperties,
39
+ ): void;
40
+ }
41
+
42
+ export class LeakTracker {
43
+ constructor(type?: number);
44
+
45
+ trackInstance(instance: object, snapshot?: unknown): void;
46
+ }
47
+
48
+ export default function init(
49
+ appKey: string,
50
+ platformKey: string,
51
+ version: string,
52
+ buildIdentifier?: string,
53
+ ): Promise<AX>;
package/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import AX from './core/ax.js';
2
+ export { LeakTracker } from './memory/ax-leak.js';
3
+
4
+ export default async function init(appKey, platformKey, version, buildIdentifier) {
5
+ const ax = new AX(appKey, platformKey, version, buildIdentifier);
6
+ await ax.launch();
7
+ return ax;
8
+ }
9
+
10
+ export { AX };
@@ -0,0 +1,77 @@
1
+ import { LeakTracker } from './ax-leak.js';
2
+
3
+ /** Dom Leak Tracker */
4
+ class DomLeakTracker extends LeakTracker {
5
+ // Constructor
6
+ constructor() {
7
+ super(1); // DOM Leak Type
8
+
9
+ // Mutation Observer to track removed nodes
10
+ new MutationObserver(mutations => {
11
+ for (const m of mutations) {
12
+ for (const node of m.removedNodes) {
13
+ if (node.nodeType === Node.ELEMENT_NODE
14
+ && node.tagName.toUpperCase() !== 'SCRIPT'
15
+ && node.tagName.toUpperCase() !== 'STYLE'
16
+ && node.tagName.toUpperCase() !== 'BR'
17
+ && node.tagName.toUpperCase() !== 'HR'
18
+ && node.tagName.toUpperCase() !== 'WBR'
19
+ ) {
20
+ this.trackInstance(node, this.serializeWithParents(node));
21
+ }
22
+ }
23
+ }
24
+ }).observe(document.documentElement, {
25
+ childList: true,
26
+ subtree: true
27
+ });
28
+ }
29
+
30
+ // Check for Leak - Overriding here.
31
+ // A DOM element is considered leaking if it is not connected to the document.
32
+ isLeaking (instanceRecord) {
33
+ if (!super.isLeaking(instanceRecord)) return false;
34
+ const instance = instanceRecord.instanceRef.deref();
35
+ return !instance.isConnected;
36
+ }
37
+
38
+ // Serialize Element
39
+ serializeElement = (el) => {
40
+ if (!el || el.nodeType !== Node.ELEMENT_NODE) return null;
41
+
42
+ return {
43
+ tag: el.tagName.toLowerCase(),
44
+ id: el.id || null,
45
+ classes: el.className || null,
46
+ attributes: [...el.attributes].reduce((acc, attr) => {
47
+ acc[attr.name] = attr.value;
48
+ return acc;
49
+ }, {}),
50
+ isConnected: el.isConnected
51
+ };
52
+ }
53
+
54
+ // Serialize with Parents
55
+ serializeWithParents = (node, maxDepth = 5) => {
56
+ const chain = [];
57
+ let current = node;
58
+ let depth = 0;
59
+
60
+ while (current && depth < maxDepth) {
61
+ const serialized = this.serializeElement(current);
62
+ if (!serialized) break;
63
+
64
+ chain.push(serialized);
65
+ current = current.parentElement;
66
+ depth++;
67
+ }
68
+
69
+ return {
70
+ depth: chain.length,
71
+ chain
72
+ };
73
+ }
74
+ }
75
+
76
+ // Initialize DomLeakTracker
77
+ new DomLeakTracker();
@@ -0,0 +1,107 @@
1
+ import AX from '../core/ax.js';
2
+
3
+ // Instance Record
4
+ class InstanceRecord {
5
+ constructor(id, instance, snapshot) {
6
+ this.id = id;
7
+ this.instanceRef = new WeakRef(instance); // WeakRef
8
+ this.snapshot = snapshot; // Serialized snapshot
9
+ }
10
+ }
11
+
12
+ /** Base Leak Tracker */
13
+ export class LeakTracker {
14
+
15
+ // Leak Type
16
+ static LeakType = 0;
17
+ // LeakAnalyzer instance
18
+ leakAnalyzer = null;
19
+
20
+ // Weak maps for tracking instances
21
+ instanceIds = null;
22
+ instanceRefs = null;
23
+ nextInstanceId = 0;
24
+ type = 0;
25
+
26
+ // FinalizationRegistry for GC epoch signaling
27
+ gcRegistry = new FinalizationRegistry(async () => {
28
+ this.leakAnalyzer.notify_gc_epoch();
29
+ });
30
+
31
+ // Constructor
32
+ constructor(type = 0) {
33
+ if (typeof WeakRef == "undefined" || typeof FinalizationRegistry == "undefined") return;
34
+ this.type = type;
35
+ this.leakAnalyzer = new AX.instance.wasm.LeakAnalyzer();
36
+ this.instanceIds = new WeakMap();
37
+ this.instanceRefs = new Map();
38
+ this.nextInstanceId++;
39
+
40
+ // FinalizationRegistry for GC epoch signaling
41
+ this.gcRegistry = new FinalizationRegistry(async () => {
42
+ this.leakAnalyzer.notify_gc_epoch();
43
+ });
44
+
45
+ // Detection Loop
46
+ setInterval(() => {
47
+ // Schedule a GC observation signal
48
+ this.scheduleGcSignal();
49
+ this.detectLeaks();
50
+
51
+ }, 30_000);
52
+ }
53
+
54
+ // Schedule GC Signal
55
+ scheduleGcSignal = async () => {
56
+ const sentinel = {};
57
+ this.gcRegistry.register(sentinel, null);
58
+ }
59
+
60
+ // Track Instance
61
+ trackInstance = (instance, snapshot) => {
62
+ if (!instance) return;
63
+ let id = this.instanceIds.get(instance);
64
+ if (!id) {
65
+ id = this.nextInstanceId++;
66
+ this.instanceIds.set(instance, id);
67
+ this.instanceRefs.set(id, new InstanceRecord(id, instance, snapshot));
68
+ }
69
+ this.leakAnalyzer.track_instance(id);
70
+ }
71
+
72
+ // Resolve Instance by ID
73
+ resolveInstanceById = (id) => {
74
+ const instanceRecord = this.instanceRefs.get(id);
75
+ if (!instanceRecord) return null;
76
+ const instance = instanceRecord.instanceRef.deref();
77
+ if (!instance) {
78
+ // GC collected → cleanup
79
+ this.instanceRefs.delete(id);
80
+ return null;
81
+ }
82
+ return instanceRecord;
83
+ }
84
+
85
+ // Check for Leak - Override in subclass
86
+ isLeaking(instanceRecord) {
87
+ // Override in subclass
88
+ if (!instanceRecord || !instanceRecord.instanceRef.deref()) return false;
89
+ return true;
90
+ }
91
+
92
+ // Detect Leaks
93
+ detectLeaks = async () => {
94
+ let leaks = await this.leakAnalyzer.evaluate_leaks();
95
+ for (const rec of leaks) {
96
+ const instanceRecord = this.resolveInstanceById(rec.instance_id);
97
+ if (this.isLeaking(instanceRecord)) {
98
+ this.reportLeak(instanceRecord);
99
+ }
100
+ }
101
+ }
102
+
103
+ // Report Leak
104
+ reportLeak = (instanceRecord) => {
105
+ // Override in subclass
106
+ }
107
+ }