@scalebun/react-native 1.2.0 → 1.2.2-beta.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.
@@ -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
  };
@@ -12462,12 +12548,23 @@ var BackendSessionAdapter = class _BackendSessionAdapter {
12462
12548
  this._triggerNativeFlush();
12463
12549
  }
12464
12550
  queuePerformanceMetric(data) {
12465
- if (this.destroyed || !this.config.clientKey) return;
12551
+ if (this.destroyed || !this.config.clientKey) {
12552
+ logger.warn("[perf.queue] dropped \u2014 no clientKey or adapter destroyed", {
12553
+ type: data.type,
12554
+ destroyed: this.destroyed,
12555
+ hasClientKey: !!this.config.clientKey
12556
+ });
12557
+ return;
12558
+ }
12466
12559
  this.pendingPerformanceMetrics.push({
12467
12560
  ...data,
12468
12561
  clientId: this._clientId(),
12469
12562
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
12470
12563
  });
12564
+ logger.debug("[perf.queue] enqueued performance metric", {
12565
+ type: data.type,
12566
+ pending: this.pendingPerformanceMetrics.length
12567
+ });
12471
12568
  if (this.pendingPerformanceMetrics.length >= 20) {
12472
12569
  this._flushPerformance().catch(() => {
12473
12570
  });
@@ -13034,19 +13131,43 @@ var BackendSessionAdapter = class _BackendSessionAdapter {
13034
13131
  }
13035
13132
  }
13036
13133
  async _flushPerformance() {
13037
- if (this.pendingPerformanceMetrics.length === 0 || !this.currentSessionId || !this.config.clientKey) return;
13134
+ if (this.pendingPerformanceMetrics.length === 0) return;
13135
+ if (!this.currentSessionId || !this.config.clientKey) {
13136
+ logger.debug("[perf.flush] waiting \u2014 not deliverable yet", {
13137
+ pending: this.pendingPerformanceMetrics.length,
13138
+ hasSession: !!this.currentSessionId,
13139
+ hasClientKey: !!this.config.clientKey
13140
+ });
13141
+ return;
13142
+ }
13038
13143
  const batch = this.pendingPerformanceMetrics.splice(0, 50);
13039
13144
  try {
13040
- if (await this._enqueueNative("performance", batch)) return;
13145
+ if (await this._enqueueNative("performance", batch)) {
13146
+ logger.debug("[perf.flush] handed to native outbox", {
13147
+ count: batch.length
13148
+ });
13149
+ return;
13150
+ }
13041
13151
  if (!this._sessionRowReady()) {
13152
+ logger.debug("[perf.flush] session row not ready \u2014 re-buffering", {
13153
+ count: batch.length
13154
+ });
13042
13155
  this.pendingPerformanceMetrics.unshift(...batch);
13043
13156
  return;
13044
13157
  }
13045
13158
  await this._post(ENDPOINTS.INGESTION_PERFORMANCE(this.currentSessionId), {
13046
13159
  metrics: batch
13047
13160
  });
13161
+ logger.info("[perf.flush] POST performance metrics ok", {
13162
+ count: batch.length
13163
+ });
13048
13164
  } catch (err) {
13049
- if (!_BackendSessionAdapter._isPermanentHttpError(err)) this.pendingPerformanceMetrics.unshift(...batch);
13165
+ const permanent = _BackendSessionAdapter._isPermanentHttpError(err);
13166
+ logger.warn("[perf.flush] POST performance metrics failed", {
13167
+ count: batch.length,
13168
+ permanent
13169
+ });
13170
+ if (!permanent) this.pendingPerformanceMetrics.unshift(...batch);
13050
13171
  }
13051
13172
  }
13052
13173
  async _flushLogs() {