@flotrace/runtime-core 2.0.0 → 2.2.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/dist/index.mjs CHANGED
@@ -176,6 +176,7 @@ function getChangedKeys(prev, next) {
176
176
  }
177
177
 
178
178
  // src/websocketClient.ts
179
+ import React from "react";
179
180
  var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
180
181
  constructor(config = {}) {
181
182
  this.ws = null;
@@ -223,7 +224,10 @@ var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
223
224
  appUrl: this.config.getAppUrl?.(),
224
225
  platform: this.config.platform,
225
226
  appId: this.config.appId,
226
- appVersion: this.config.appVersion
227
+ appVersion: this.config.appVersion,
228
+ frameworkName: this.config.frameworkName,
229
+ frameworkVersion: this.config.frameworkVersion,
230
+ reactNativeVersion: this.config.reactNativeVersion
227
231
  });
228
232
  this.flush();
229
233
  };
@@ -407,12 +411,17 @@ var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
407
411
  return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
408
412
  }
409
413
  /**
410
- * Get React version if available
414
+ * Get React version if available.
415
+ *
416
+ * Historical note: an earlier implementation read `globalThis.React?.version` —
417
+ * but React is an ES-module import in modern bundles (Vite/webpack/Next.js) and
418
+ * is never placed on `globalThis`, so the probe returned undefined for every
419
+ * typical bundled app. Reading `React.version` via a direct import is
420
+ * authoritative across web (both CJS and ESM bundles), React Native, and SSR.
411
421
  */
412
422
  getReactVersion() {
413
423
  try {
414
- const React = globalThis.React;
415
- return React?.version;
424
+ return React.version;
416
425
  } catch {
417
426
  return void 0;
418
427
  }
@@ -1542,8 +1551,8 @@ function runAnalysis(tree, fiberRefMap2) {
1542
1551
  }
1543
1552
  function schedulePropDrillingAnalysis(tree, fiberRefMap2, client2) {
1544
1553
  if (analyzeTimer) clearTimeout(analyzeTimer);
1545
- const now = Date.now();
1546
- const elapsed = now - lastAnalysisTime;
1554
+ const now2 = Date.now();
1555
+ const elapsed = now2 - lastAnalysisTime;
1547
1556
  const delay = elapsed >= ANALYZE_INTERVAL_MS ? 0 : ANALYZE_INTERVAL_MS - elapsed;
1548
1557
  analyzeTimer = setTimeout(() => {
1549
1558
  analyzeTimer = null;
@@ -2570,7 +2579,7 @@ function executeSnapshot(root) {
2570
2579
  }
2571
2580
  previousFlatTree = currentFlatTree;
2572
2581
  if (pendingLocalStateCorrelations.length > 0) {
2573
- const now = Date.now();
2582
+ const now2 = Date.now();
2574
2583
  const toSend = pendingLocalStateCorrelations.splice(0);
2575
2584
  for (const corr of toSend) {
2576
2585
  try {
@@ -2579,7 +2588,7 @@ function executeSnapshot(root) {
2579
2588
  requestId: corr.requestId,
2580
2589
  componentName: corr.componentName,
2581
2590
  hookIndex: corr.hookIndex,
2582
- timestamp: now
2591
+ timestamp: now2
2583
2592
  });
2584
2593
  } catch {
2585
2594
  }
@@ -3024,6 +3033,7 @@ var activeUnsubscribers = [];
3024
3033
  var isInstalled4 = false;
3025
3034
  var debounceTimers = /* @__PURE__ */ new Map();
3026
3035
  var DEBOUNCE_MS = 200;
3036
+ var trackedStores = /* @__PURE__ */ new Map();
3027
3037
  function installZustandTracker(stores, client2) {
3028
3038
  if (isInstalled4) {
3029
3039
  console.warn("[FloTrace] Zustand tracker already installed, reinstalling");
@@ -3039,6 +3049,7 @@ function installZustandTracker(stores, client2) {
3039
3049
  continue;
3040
3050
  }
3041
3051
  try {
3052
+ trackedStores.set(storeName, store);
3042
3053
  const initialState = store.getState();
3043
3054
  sendStoreUpdate(storeName, initialState, Object.keys(initialState), client2);
3044
3055
  const unsubscribe = store.subscribe((newState, prevState) => {
@@ -3068,9 +3079,20 @@ function uninstallZustandTracker() {
3068
3079
  }
3069
3080
  }
3070
3081
  activeUnsubscribers = [];
3082
+ trackedStores.clear();
3071
3083
  isInstalled4 = false;
3072
3084
  console.log("[FloTrace] Zustand tracker uninstalled");
3073
3085
  }
3086
+ function getZustandSnapshot() {
3087
+ const snapshot = /* @__PURE__ */ new Map();
3088
+ for (const [name, store] of trackedStores) {
3089
+ try {
3090
+ snapshot.set(name, store.getState());
3091
+ } catch {
3092
+ }
3093
+ }
3094
+ return snapshot;
3095
+ }
3074
3096
  function scheduleStoreUpdate(storeName, prevState, newState, client2) {
3075
3097
  let changedKeys;
3076
3098
  try {
@@ -3109,6 +3131,7 @@ var isInstalled5 = false;
3109
3131
  var debounceTimer = null;
3110
3132
  var previousState = null;
3111
3133
  var DEBOUNCE_MS2 = 200;
3134
+ var trackedStore = null;
3112
3135
  function isReduxStore(obj) {
3113
3136
  return typeof obj === "object" && obj !== null && typeof obj.getState === "function" && typeof obj.subscribe === "function" && typeof obj.dispatch === "function";
3114
3137
  }
@@ -3120,6 +3143,7 @@ function installReduxTracker(store, client2) {
3120
3143
  isInstalled5 = true;
3121
3144
  console.log("[FloTrace] Installing Redux tracker");
3122
3145
  try {
3146
+ trackedStore = store;
3123
3147
  const initialState = store.getState();
3124
3148
  previousState = initialState;
3125
3149
  sendReduxUpdate(initialState, Object.keys(initialState), client2);
@@ -3151,9 +3175,18 @@ function uninstallReduxTracker() {
3151
3175
  activeUnsubscribe = null;
3152
3176
  }
3153
3177
  previousState = null;
3178
+ trackedStore = null;
3154
3179
  isInstalled5 = false;
3155
3180
  console.log("[FloTrace] Redux tracker uninstalled");
3156
3181
  }
3182
+ function getReduxSnapshot() {
3183
+ if (!trackedStore) return null;
3184
+ try {
3185
+ return trackedStore.getState();
3186
+ } catch {
3187
+ return null;
3188
+ }
3189
+ }
3157
3190
  function scheduleReduxUpdate(newState, client2) {
3158
3191
  let changedKeys;
3159
3192
  try {
@@ -3191,6 +3224,7 @@ var queryUnsubscribe = null;
3191
3224
  var mutationUnsubscribe = null;
3192
3225
  var debounceTimer2 = null;
3193
3226
  var DEBOUNCE_MS3 = 300;
3227
+ var trackedClient = null;
3194
3228
  var MAX_EVENTS_PER_QUERY = 50;
3195
3229
  var queryTracking = /* @__PURE__ */ new Map();
3196
3230
  var CORRELATION_WINDOW_MS = 500;
@@ -3213,6 +3247,7 @@ function installTanStackQueryTracker(queryClient, client2) {
3213
3247
  isInstalled6 = true;
3214
3248
  console.log("[FloTrace] Installing TanStack Query tracker");
3215
3249
  try {
3250
+ trackedClient = queryClient;
3216
3251
  const queryCache = queryClient.getQueryCache();
3217
3252
  const mutationCache = queryClient.getMutationCache();
3218
3253
  for (const query of queryCache.getAll()) {
@@ -3277,9 +3312,24 @@ function uninstallTanStackQueryTracker() {
3277
3312
  clearTimeout(pending.timeoutId);
3278
3313
  }
3279
3314
  pendingCorrelations.clear();
3315
+ trackedClient = null;
3280
3316
  isInstalled6 = false;
3281
3317
  console.log("[FloTrace] TanStack Query tracker uninstalled");
3282
3318
  }
3319
+ function getTanstackSnapshot() {
3320
+ const snapshot = /* @__PURE__ */ new Map();
3321
+ if (!trackedClient) return snapshot;
3322
+ try {
3323
+ const queryCache = trackedClient.getQueryCache();
3324
+ for (const query of queryCache.getAll()) {
3325
+ if (query.state.data !== void 0) {
3326
+ snapshot.set(query.queryHash, { queryKey: query.queryKey, data: query.state.data });
3327
+ }
3328
+ }
3329
+ } catch {
3330
+ }
3331
+ return snapshot;
3332
+ }
3283
3333
  function computeDataHash(data) {
3284
3334
  if (data === null || data === void 0) return "__null__";
3285
3335
  try {
@@ -3342,11 +3392,11 @@ function updateQueryTracking(query, eventType) {
3342
3392
  }
3343
3393
  }
3344
3394
  if (tracking.prevFetchStatus === "idle" && currentFetchStatus === "fetching") {
3345
- const now = Date.now();
3395
+ const now2 = Date.now();
3346
3396
  for (const pending of pendingCorrelations.values()) {
3347
3397
  if (pending.idleQueryHashes.has(query.queryHash)) {
3348
3398
  pending.affectedQueries.set(query.queryHash, {
3349
- fetchStartedAt: now,
3399
+ fetchStartedAt: now2,
3350
3400
  queryKey: query.queryKey
3351
3401
  });
3352
3402
  }
@@ -3358,7 +3408,7 @@ function updateQueryTracking(query, eventType) {
3358
3408
  }
3359
3409
  function openCorrelationWindow(mutation, queryCache, mutationCache, client2) {
3360
3410
  const correlationId = `corr-${++correlationCounter}`;
3361
- const now = Date.now();
3411
+ const now2 = Date.now();
3362
3412
  const idleQueryHashes = /* @__PURE__ */ new Set();
3363
3413
  for (const query of queryCache.getAll()) {
3364
3414
  if (query.state.fetchStatus === "idle") {
@@ -3372,7 +3422,7 @@ function openCorrelationWindow(mutation, queryCache, mutationCache, client2) {
3372
3422
  correlationId,
3373
3423
  mutationId: mutation.mutationId,
3374
3424
  mutationKey: mutation.options.mutationKey,
3375
- completedAt: now,
3425
+ completedAt: now2,
3376
3426
  idleQueryHashes,
3377
3427
  affectedQueries: /* @__PURE__ */ new Map(),
3378
3428
  timeoutId
@@ -3571,12 +3621,344 @@ function safeCall(fn, fallback) {
3571
3621
  return fallback;
3572
3622
  }
3573
3623
  }
3624
+
3625
+ // src/valueTraceResolver.ts
3626
+ var FIBER_TAG_CONTEXT_PROVIDER = 10;
3627
+ var BUDGET_MS = 50;
3628
+ var SCAN_DEPTH = 3;
3629
+ var MAX_PROP_CHAIN_DEPTH = 30;
3630
+ function now() {
3631
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
3632
+ }
3633
+ function walkPath(root, path) {
3634
+ let cur = root;
3635
+ for (const key of path) {
3636
+ if (cur === null || cur === void 0 || typeof cur !== "object") return void 0;
3637
+ cur = cur[key];
3638
+ }
3639
+ return cur;
3640
+ }
3641
+ function getHookValueAt(fiber, hookIndex) {
3642
+ let hook = fiber.memoizedState;
3643
+ let i = 0;
3644
+ while (hook && i < hookIndex) {
3645
+ hook = hook.next;
3646
+ i++;
3647
+ }
3648
+ if (!hook) return void 0;
3649
+ return hook.memoizedState;
3650
+ }
3651
+ function valuesMatch(target, targetFp, candidate) {
3652
+ const targetIsObject = target !== null && typeof target === "object";
3653
+ const candidateIsObject = candidate !== null && typeof candidate === "object";
3654
+ if (targetIsObject && candidateIsObject && target === candidate) return "exact";
3655
+ if (!shouldFlagRename(target) || !shouldFlagRename(candidate)) return null;
3656
+ if (valueFingerprint(candidate) === targetFp) return "fingerprint-match";
3657
+ return null;
3658
+ }
3659
+ function findMatchingPathInObject(target, targetFp, container, currentPath, depth, deadline) {
3660
+ if (now() > deadline) return null;
3661
+ if (depth > SCAN_DEPTH) return null;
3662
+ if (container === null || typeof container !== "object") return null;
3663
+ const selfMatch = valuesMatch(target, targetFp, container);
3664
+ if (selfMatch) return { path: [...currentPath], confidence: selfMatch };
3665
+ if (Array.isArray(container)) {
3666
+ for (let i = 0; i < Math.min(container.length, 50); i++) {
3667
+ const child = container[i];
3668
+ const directMatch = valuesMatch(target, targetFp, child);
3669
+ if (directMatch) return { path: [...currentPath, String(i)], confidence: directMatch };
3670
+ const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, String(i)], depth + 1, deadline);
3671
+ if (nested) return nested;
3672
+ }
3673
+ } else {
3674
+ for (const key of Object.keys(container)) {
3675
+ const child = container[key];
3676
+ const directMatch = valuesMatch(target, targetFp, child);
3677
+ if (directMatch) return { path: [...currentPath, key], confidence: directMatch };
3678
+ const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, key], depth + 1, deadline);
3679
+ if (nested) return nested;
3680
+ }
3681
+ }
3682
+ return null;
3683
+ }
3684
+ function buildFiberToNodeIdMap() {
3685
+ const reverse = /* @__PURE__ */ new Map();
3686
+ for (const [nodeId, fiber] of getFiberRefMap()) {
3687
+ reverse.set(fiber, nodeId);
3688
+ }
3689
+ return reverse;
3690
+ }
3691
+ function resolveValueTrace(input) {
3692
+ const startedAt = now();
3693
+ const deadline = startedAt + BUDGET_MS;
3694
+ const steps = [];
3695
+ const base = {
3696
+ rootNodeId: input.nodeId,
3697
+ rootPropPath: input.propPath,
3698
+ rootHookPath: input.hookPath,
3699
+ steps,
3700
+ resolvedAtMs: now()
3701
+ };
3702
+ const fiber = getFiberRefMap().get(input.nodeId);
3703
+ if (!fiber) {
3704
+ return { ...base, error: "no-fiber", resolvedAtMs: now() };
3705
+ }
3706
+ let rootValue;
3707
+ if (input.propPath && input.propPath.length > 0) {
3708
+ if (!fiber.memoizedProps) return { ...base, error: "value-not-found", resolvedAtMs: now() };
3709
+ rootValue = walkPath(fiber.memoizedProps, input.propPath);
3710
+ } else if (input.hookPath) {
3711
+ const hookValue = getHookValueAt(fiber, input.hookPath.hookIndex);
3712
+ rootValue = input.hookPath.subPath && input.hookPath.subPath.length > 0 ? walkPath(hookValue, input.hookPath.subPath) : hookValue;
3713
+ } else {
3714
+ return { ...base, error: "value-not-found", resolvedAtMs: now() };
3715
+ }
3716
+ if (rootValue === void 0) {
3717
+ return { ...base, error: "value-not-found", resolvedAtMs: now() };
3718
+ }
3719
+ const rootFp = valueFingerprint(rootValue);
3720
+ const fiberToNodeId = buildFiberToNodeIdMap();
3721
+ const rootComponentName = getComponentNameFromFiber(fiber) ?? "Unknown";
3722
+ if (input.propPath) {
3723
+ steps.push({
3724
+ kind: "prop",
3725
+ nodeId: input.nodeId,
3726
+ componentName: rootComponentName,
3727
+ propPath: input.propPath,
3728
+ confidence: "exact"
3729
+ });
3730
+ } else if (input.hookPath) {
3731
+ steps.push({
3732
+ kind: "hook-state",
3733
+ nodeId: input.nodeId,
3734
+ componentName: rootComponentName,
3735
+ hookIndex: input.hookPath.hookIndex,
3736
+ hookType: "unknown",
3737
+ // Cheap: full hook classification is expensive; caller can fetch separately.
3738
+ subPath: input.hookPath.subPath,
3739
+ confidence: "exact"
3740
+ });
3741
+ }
3742
+ if (input.propPath) {
3743
+ let current = fiber.return;
3744
+ let hops = 0;
3745
+ while (current && hops < MAX_PROP_CHAIN_DEPTH) {
3746
+ if (now() > deadline) return { ...base, steps, truncated: true, resolvedAtMs: now() };
3747
+ if (current.tag !== FIBER_TAG_CONTEXT_PROVIDER) {
3748
+ const props = current.memoizedProps;
3749
+ if (props) {
3750
+ const match = findMatchingPathInObject(rootValue, rootFp, props, [], 0, deadline);
3751
+ if (match) {
3752
+ const ancestorNodeId = fiberToNodeId.get(current);
3753
+ const ancestorName = getComponentNameFromFiber(current) ?? "Unknown";
3754
+ if (ancestorNodeId) {
3755
+ steps.push({
3756
+ kind: "prop",
3757
+ nodeId: ancestorNodeId,
3758
+ componentName: ancestorName,
3759
+ propPath: match.path,
3760
+ confidence: match.confidence
3761
+ });
3762
+ }
3763
+ }
3764
+ }
3765
+ }
3766
+ current = current.return;
3767
+ hops++;
3768
+ }
3769
+ }
3770
+ if (input.hookPath) {
3771
+ const origin = findFetchOrigin(rootValue);
3772
+ if (origin) {
3773
+ steps.push({
3774
+ kind: "api",
3775
+ requestId: origin,
3776
+ method: "UNKNOWN",
3777
+ urlPath: "",
3778
+ ageMs: 0
3779
+ });
3780
+ return { ...base, steps, resolvedAtMs: now() };
3781
+ }
3782
+ }
3783
+ const derivedMatch = findDerivationMatch(fiber, rootValue, rootFp, rootComponentName);
3784
+ if (derivedMatch) {
3785
+ steps.push({ ...derivedMatch, nodeId: input.nodeId });
3786
+ return { ...base, steps, resolvedAtMs: now() };
3787
+ }
3788
+ const contextMatch = findContextMatch(fiber, rootValue, rootFp, fiberToNodeId);
3789
+ if (contextMatch) {
3790
+ steps.push(contextMatch.step);
3791
+ const providerStoreMatch = findStoreMatch(contextMatch.providerValue, valueFingerprint(contextMatch.providerValue), deadline);
3792
+ if (providerStoreMatch) {
3793
+ steps.push({
3794
+ kind: "store",
3795
+ source: providerStoreMatch.source,
3796
+ storeName: providerStoreMatch.storeName,
3797
+ keyPath: providerStoreMatch.keyPath,
3798
+ confidence: providerStoreMatch.confidence
3799
+ });
3800
+ const origin = findFetchOrigin(providerStoreMatch.matchedValue);
3801
+ if (origin) {
3802
+ steps.push({ kind: "api", requestId: origin, method: "UNKNOWN", urlPath: "", ageMs: 0 });
3803
+ }
3804
+ } else {
3805
+ const origin = findFetchOrigin(contextMatch.providerValue);
3806
+ if (origin) {
3807
+ steps.push({ kind: "api", requestId: origin, method: "UNKNOWN", urlPath: "", ageMs: 0 });
3808
+ }
3809
+ }
3810
+ return { ...base, steps, resolvedAtMs: now() };
3811
+ }
3812
+ const storeMatch = findStoreMatch(rootValue, rootFp, deadline);
3813
+ if (storeMatch) {
3814
+ steps.push({
3815
+ kind: "store",
3816
+ source: storeMatch.source,
3817
+ storeName: storeMatch.storeName,
3818
+ keyPath: storeMatch.keyPath,
3819
+ confidence: storeMatch.confidence
3820
+ });
3821
+ const origin = findFetchOrigin(storeMatch.matchedValue);
3822
+ if (origin) {
3823
+ steps.push({
3824
+ kind: "api",
3825
+ requestId: origin,
3826
+ method: "UNKNOWN",
3827
+ urlPath: "",
3828
+ ageMs: 0
3829
+ });
3830
+ }
3831
+ }
3832
+ return { ...base, steps, resolvedAtMs: now() };
3833
+ }
3834
+ function findContextMatch(consumer, target, targetFp, fiberToNodeId) {
3835
+ const deps = consumer.dependencies?.firstContext;
3836
+ if (!deps) return null;
3837
+ let dep = deps;
3838
+ while (dep) {
3839
+ const match = valuesMatch(target, targetFp, dep.memoizedValue);
3840
+ if (match) {
3841
+ const provider = findNearestProvider(consumer, dep.context);
3842
+ const step = {
3843
+ kind: "context",
3844
+ contextName: dep.context.displayName || "Context",
3845
+ providerNodeId: provider ? fiberToNodeId.get(provider) : void 0,
3846
+ confidence: match
3847
+ };
3848
+ const providerValue = provider?.memoizedProps?.value ?? dep.memoizedValue;
3849
+ return { step, providerValue };
3850
+ }
3851
+ dep = dep.next;
3852
+ }
3853
+ return null;
3854
+ }
3855
+ function findDerivationMatch(fiber, target, targetFp, componentName) {
3856
+ let hook = fiber.memoizedState;
3857
+ let index = 0;
3858
+ while (hook) {
3859
+ const ms = hook.memoizedState;
3860
+ if (Array.isArray(ms) && ms.length === 2 && Array.isArray(ms[1])) {
3861
+ const [computed, deps] = ms;
3862
+ const match = valuesMatch(target, targetFp, computed);
3863
+ if (match) {
3864
+ const hookType = typeof computed === "function" ? "useCallback" : "useMemo";
3865
+ return {
3866
+ kind: "derived",
3867
+ nodeId: "",
3868
+ componentName,
3869
+ hookIndex: index,
3870
+ hookType,
3871
+ depCount: deps.length,
3872
+ confidence: match
3873
+ };
3874
+ }
3875
+ }
3876
+ hook = hook.next;
3877
+ index++;
3878
+ }
3879
+ return null;
3880
+ }
3881
+ function findNearestProvider(consumer, contextObj) {
3882
+ let current = consumer.return;
3883
+ let hops = 0;
3884
+ while (current && hops < MAX_PROP_CHAIN_DEPTH) {
3885
+ if (current.tag === FIBER_TAG_CONTEXT_PROVIDER) {
3886
+ const providerType = current.type;
3887
+ if (providerType && providerType._context === contextObj) return current;
3888
+ }
3889
+ current = current.return;
3890
+ hops++;
3891
+ }
3892
+ return null;
3893
+ }
3894
+ function findStoreMatch(target, targetFp, deadline) {
3895
+ for (const [storeName, state] of getZustandSnapshot()) {
3896
+ if (now() > deadline) return null;
3897
+ const hit = findMatchingPathInObject(target, targetFp, state, [], 0, deadline);
3898
+ if (hit) {
3899
+ return {
3900
+ source: "zustand",
3901
+ storeName,
3902
+ keyPath: hit.path,
3903
+ confidence: hit.confidence,
3904
+ matchedValue: walkPath(state, hit.path)
3905
+ };
3906
+ }
3907
+ }
3908
+ const redux = getReduxSnapshot();
3909
+ if (redux) {
3910
+ if (now() > deadline) return null;
3911
+ const hit = findMatchingPathInObject(target, targetFp, redux, [], 0, deadline);
3912
+ if (hit) {
3913
+ return {
3914
+ source: "redux",
3915
+ storeName: "redux",
3916
+ keyPath: hit.path,
3917
+ confidence: hit.confidence,
3918
+ matchedValue: walkPath(redux, hit.path)
3919
+ };
3920
+ }
3921
+ }
3922
+ for (const [queryHash, entry] of getTanstackSnapshot()) {
3923
+ if (now() > deadline) return null;
3924
+ const hit = findMatchingPathInObject(target, targetFp, entry.data, [], 0, deadline);
3925
+ if (hit) {
3926
+ return {
3927
+ source: "tanstack-query",
3928
+ storeName: queryHash,
3929
+ keyPath: hit.path,
3930
+ confidence: hit.confidence,
3931
+ matchedValue: walkPath(entry.data, hit.path)
3932
+ };
3933
+ }
3934
+ }
3935
+ return null;
3936
+ }
3937
+
3938
+ // src/frameworkDetect.ts
3939
+ function detectWebFramework() {
3940
+ if (typeof document === "undefined") return {};
3941
+ const hasNextData = !!globalThis.__NEXT_DATA__ || !!document.querySelector("script#__NEXT_DATA__");
3942
+ if (!hasNextData) return { frameworkName: "plain-react" };
3943
+ let version;
3944
+ try {
3945
+ const req = globalThis.require;
3946
+ if (typeof req === "function") {
3947
+ const id = "next/package.json";
3948
+ const pkg = req(id);
3949
+ version = pkg?.version;
3950
+ }
3951
+ } catch {
3952
+ }
3953
+ return { frameworkName: "next", frameworkVersion: version };
3954
+ }
3574
3955
  export {
3575
3956
  DEFAULT_CONFIG,
3576
3957
  FloTraceWebSocketClient,
3577
3958
  buildAncestorChain,
3578
3959
  clearFetchOriginTags,
3579
3960
  detectServerComponent,
3961
+ detectWebFramework,
3580
3962
  disposeWebSocketClient,
3581
3963
  findFetchOrigin,
3582
3964
  getChangedKeys,
@@ -3587,8 +3969,11 @@ export {
3587
3969
  getNodeEffects,
3588
3970
  getNodeHooks,
3589
3971
  getNodeProps,
3972
+ getReduxSnapshot,
3973
+ getTanstackSnapshot,
3590
3974
  getTimeline,
3591
3975
  getWebSocketClient,
3976
+ getZustandSnapshot,
3592
3977
  hasActiveTags,
3593
3978
  inspectEffects,
3594
3979
  inspectHooks,
@@ -3605,6 +3990,7 @@ export {
3605
3990
  requestFullSnapshot,
3606
3991
  requestTreeSnapshot,
3607
3992
  resetNextjsDetection,
3993
+ resolveValueTrace,
3608
3994
  serializeProps,
3609
3995
  serializeValue,
3610
3996
  tagFetchData,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flotrace/runtime-core",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Platform-agnostic core for FloTrace runtime — fiber walker, analyzers, trackers. Shared by @flotrace/runtime (web) and @flotrace/runtime-native (React Native).",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",