@scalebun/react-native 1.2.0 → 1.2.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.
@@ -11423,7 +11423,31 @@ var PerformanceFeature = class _PerformanceFeature {
11423
11423
  static CAPTURE_MAX_RETRIES = 3;
11424
11424
  /** Delay schedule for each attempt (ms) — escalating to let views settle */
11425
11425
  static CAPTURE_DELAYS = [300, 600, 1200];
11426
- // Track event function from SDK pipeline
11426
+ // ─── Early-metric buffering (backend) ───────────────────────────────
11427
+ //
11428
+ // Performance metrics reach the cloud via the SessionManager's backend
11429
+ // transport. But the first metrics — most importantly app_launch — fire
11430
+ // during PerformanceFeature.initialize() (appLaunchCollector.collect()),
11431
+ // which runs from featureRegistry.initializeAll() BEFORE the bootstrap
11432
+ // attaches the backend transport (SDKBootstrapper). In that window
11433
+ // getBackendTransport() is null, so without buffering the metric is sent
11434
+ // only to the desktop debugger and silently dropped from the cloud
11435
+ // ("App start = 0" in Insights). We buffer such metrics locally and drain
11436
+ // them the moment the transport appears (via subsequent events or a
11437
+ // bounded retry). The backend adapter itself buffers again until the
11438
+ // session row is ready, so ordering across the session boundary is safe.
11439
+ /** Metrics captured before the backend transport was available. */
11440
+ _pendingBackendMetrics = [];
11441
+ /** Retry timer that drains the buffer once the transport attaches. */
11442
+ _backendDrainTimer = null;
11443
+ /** Number of drain attempts made for the current buffered batch. */
11444
+ _backendDrainAttempts = 0;
11445
+ /** Hard cap on buffered metrics — bounds memory if a transport never attaches (non-SaaS). */
11446
+ static BACKEND_BUFFER_MAX = 64;
11447
+ /** Max drain retries before giving up (non-SaaS mode never attaches a transport). */
11448
+ static BACKEND_DRAIN_MAX_ATTEMPTS = 6;
11449
+ /** Escalating retry delays (ms); last value repeats. Covers the startup attach race. */
11450
+ static BACKEND_DRAIN_DELAYS = [250, 500, 1e3, 2e3, 4e3];
11427
11451
  constructor(config) {
11428
11452
  this.config = mergePerformanceConfig(config);
11429
11453
  this.transport = new PerformanceTransport(this.config);
@@ -11558,6 +11582,9 @@ var PerformanceFeature = class _PerformanceFeature {
11558
11582
  clearTimeout(this._captureTimer);
11559
11583
  this._captureTimer = null;
11560
11584
  }
11585
+ this._clearBackendDrainTimer();
11586
+ this._backendDrainAttempts = 0;
11587
+ this._pendingBackendMetrics = [];
11561
11588
  }
11562
11589
  get isActive() {
11563
11590
  return this.active;
@@ -11741,24 +11768,83 @@ var PerformanceFeature = class _PerformanceFeature {
11741
11768
  // ─── Internal ───────────────────────────────────────────────────────
11742
11769
  _handleEvent(event) {
11743
11770
  this.transport.sendEvent(event);
11771
+ this._forwardMetricToBackend(this._toBackendMetric(event));
11772
+ }
11773
+ /** Map a raw perf event onto the backend metric envelope. */
11774
+ _toBackendMetric(event) {
11775
+ const e = event;
11776
+ const metadata = {
11777
+ id: event.id
11778
+ };
11779
+ if (event.type === "app_launch") {
11780
+ if (e.launchType) metadata.launchType = e.launchType;
11781
+ if (e.breakdown) metadata.breakdown = e.breakdown;
11782
+ if (e.phaseLabels) metadata.phaseLabels = e.phaseLabels;
11783
+ }
11784
+ return {
11785
+ type: event.type,
11786
+ screenName: e.screenName ?? e.context?.screenKey,
11787
+ duration: e.durationMs,
11788
+ value: e.estimatedFps ?? e.value,
11789
+ metadata
11790
+ };
11791
+ }
11792
+ /**
11793
+ * Hand a metric to the backend transport, or buffer it until one attaches.
11794
+ * Every call first drains anything buffered, so a late-arriving transport
11795
+ * flushes the backlog in the order the metrics were produced.
11796
+ */
11797
+ _forwardMetricToBackend(metric) {
11744
11798
  const backendTransport = SessionManager.getExistingInstance()?.getBackendTransport();
11745
11799
  if (backendTransport) {
11746
- const e = event;
11747
- const metadata = {
11748
- id: event.id
11749
- };
11750
- if (event.type === "app_launch") {
11751
- if (e.launchType) metadata.launchType = e.launchType;
11752
- if (e.breakdown) metadata.breakdown = e.breakdown;
11753
- if (e.phaseLabels) metadata.phaseLabels = e.phaseLabels;
11754
- }
11755
- backendTransport.queuePerformanceMetric({
11756
- type: event.type,
11757
- screenName: e.screenName ?? e.context?.screenKey,
11758
- duration: e.durationMs,
11759
- value: e.estimatedFps ?? e.value,
11760
- metadata
11761
- });
11800
+ this._drainPendingBackendMetrics(backendTransport);
11801
+ backendTransport.queuePerformanceMetric(metric);
11802
+ return;
11803
+ }
11804
+ this._pendingBackendMetrics.push(metric);
11805
+ if (this._pendingBackendMetrics.length > _PerformanceFeature.BACKEND_BUFFER_MAX) {
11806
+ this._pendingBackendMetrics.shift();
11807
+ }
11808
+ this._scheduleBackendDrain();
11809
+ }
11810
+ /** Flush all buffered metrics into the transport, preserving order. */
11811
+ _drainPendingBackendMetrics(backendTransport) {
11812
+ if (this._pendingBackendMetrics.length === 0) return;
11813
+ const buffered = this._pendingBackendMetrics;
11814
+ this._pendingBackendMetrics = [];
11815
+ for (const metric of buffered) {
11816
+ backendTransport.queuePerformanceMetric(metric);
11817
+ }
11818
+ this._clearBackendDrainTimer();
11819
+ this._backendDrainAttempts = 0;
11820
+ }
11821
+ /**
11822
+ * Poll for the backend transport a bounded number of times so the sole
11823
+ * early metric (app_launch) still reaches the cloud even when no later
11824
+ * event arrives to trigger a drain. Gives up after BACKEND_DRAIN_MAX_ATTEMPTS
11825
+ * (non-SaaS mode never attaches a transport — the desktop path already has
11826
+ * the data), leaving the bounded buffer to be GC'd on teardown.
11827
+ */
11828
+ _scheduleBackendDrain() {
11829
+ if (this._backendDrainTimer) return;
11830
+ if (this._backendDrainAttempts >= _PerformanceFeature.BACKEND_DRAIN_MAX_ATTEMPTS) return;
11831
+ const delays = _PerformanceFeature.BACKEND_DRAIN_DELAYS;
11832
+ const delay = delays[Math.min(this._backendDrainAttempts, delays.length - 1)];
11833
+ this._backendDrainAttempts++;
11834
+ this._backendDrainTimer = setTimeout(() => {
11835
+ this._backendDrainTimer = null;
11836
+ const backendTransport = SessionManager.getExistingInstance()?.getBackendTransport();
11837
+ if (backendTransport) {
11838
+ this._drainPendingBackendMetrics(backendTransport);
11839
+ } else if (this._pendingBackendMetrics.length > 0) {
11840
+ this._scheduleBackendDrain();
11841
+ }
11842
+ }, delay);
11843
+ }
11844
+ _clearBackendDrainTimer() {
11845
+ if (this._backendDrainTimer) {
11846
+ clearTimeout(this._backendDrainTimer);
11847
+ this._backendDrainTimer = null;
11762
11848
  }
11763
11849
  }
11764
11850
  };