@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.
- package/android/src/main/java/com/scalebun/core/performance/AppStartCollector.kt +61 -13
- package/android/src/main/java/com/scalebun/rn/performance/PerformanceModule.kt +11 -0
- package/dist/scalebun.full.js +142 -21
- package/dist/scalebun.full.js.map +1 -1
- package/dist/scalebun.slim.js +142 -21
- package/dist/scalebun.slim.js.map +1 -1
- package/lib/commonjs/features/performance/PerformanceFeature.js +121 -23
- package/lib/commonjs/features/performance/PerformanceFeature.js.map +1 -1
- package/lib/commonjs/features/session/BackendSessionAdapter.js +39 -4
- package/lib/commonjs/features/session/BackendSessionAdapter.js.map +1 -1
- package/lib/module/features/performance/PerformanceFeature.js +122 -23
- package/lib/module/features/performance/PerformanceFeature.js.map +1 -1
- package/lib/module/features/session/BackendSessionAdapter.js +39 -4
- package/lib/module/features/session/BackendSessionAdapter.js.map +1 -1
- package/lib/typescript/features/performance/PerformanceFeature.d.ts +31 -0
- package/lib/typescript/features/performance/PerformanceFeature.d.ts.map +1 -1
- package/lib/typescript/features/session/BackendSessionAdapter.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/features/performance/PerformanceFeature.ts +130 -20
- package/src/features/session/BackendSessionAdapter.ts +30 -5
package/dist/scalebun.slim.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
11751
|
-
|
|
11752
|
-
|
|
11753
|
-
|
|
11754
|
-
|
|
11755
|
-
|
|
11756
|
-
|
|
11757
|
-
|
|
11758
|
-
|
|
11759
|
-
|
|
11760
|
-
|
|
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)
|
|
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
|
|
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))
|
|
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
|
-
|
|
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() {
|