@scalebun/react-native 1.2.2-beta.1 → 1.2.3

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.
Files changed (25) hide show
  1. package/android/src/main/java/com/scalebun/replaysdk/ReplaySdkModule.kt +25 -0
  2. package/android/src/main/java/com/scalebun/replaysdk/session/SessionManager.kt +41 -0
  3. package/dist/scalebun.full.js +11 -11
  4. package/dist/scalebun.full.js.map +1 -1
  5. package/dist/scalebun.slim.js +11 -11
  6. package/dist/scalebun.slim.js.map +1 -1
  7. package/ios/ReplaySdk.swift +13 -0
  8. package/ios/Session/SessionManager.swift +43 -0
  9. package/lib/commonjs/features/performance/PerformanceFeature.js +2 -2
  10. package/lib/commonjs/features/performance/PerformanceFeature.js.map +1 -1
  11. package/lib/commonjs/features/performance/collectors/AppLaunchCollector.js +4 -4
  12. package/lib/commonjs/features/performance/collectors/AppLaunchCollector.js.map +1 -1
  13. package/lib/commonjs/features/session/BackendSessionAdapter.js +5 -7
  14. package/lib/commonjs/features/session/BackendSessionAdapter.js.map +1 -1
  15. package/lib/module/features/performance/PerformanceFeature.js +2 -2
  16. package/lib/module/features/performance/PerformanceFeature.js.map +1 -1
  17. package/lib/module/features/performance/collectors/AppLaunchCollector.js +4 -4
  18. package/lib/module/features/performance/collectors/AppLaunchCollector.js.map +1 -1
  19. package/lib/module/features/session/BackendSessionAdapter.js +5 -7
  20. package/lib/module/features/session/BackendSessionAdapter.js.map +1 -1
  21. package/lib/typescript/features/session/BackendSessionAdapter.d.ts.map +1 -1
  22. package/package.json +1 -1
  23. package/src/features/performance/PerformanceFeature.ts +2 -2
  24. package/src/features/performance/collectors/AppLaunchCollector.ts +4 -4
  25. package/src/features/session/BackendSessionAdapter.ts +5 -7
@@ -611,6 +611,19 @@ class ReplaySdkModule(reactContext: ReactApplicationContext) : ReplaySdkSpec(rea
611
611
  @ReactMethod
612
612
  override fun setPrivacyOptions(options: ReadableMap, promise: Promise) {
613
613
  try {
614
+ // Was a no-op stub: the promise resolved but nothing propagated, so
615
+ // `setPrivacy({ disableScreenshots })` did nothing at runtime on Android and only a
616
+ // restart (which re-reads the init config) honoured it. Now each supplied field is
617
+ // merged onto the running config via SessionManager.updatePrivacy, which routes through
618
+ // updateConfig → the capture manager, so the next frame respects it. Absent keys are
619
+ // left null → unchanged (a partial setPrivacy call must not reset the others).
620
+ sessionManager?.updatePrivacy(
621
+ disableScreenshots = if (options.hasKey("disableScreenshots")) options.getBoolean("disableScreenshots") else null,
622
+ maskTextInputs = if (options.hasKey("maskTextInputs")) options.getBoolean("maskTextInputs") else null,
623
+ maskImages = if (options.hasKey("maskImages")) options.getBoolean("maskImages") else null,
624
+ ignoredScreens = if (options.hasKey("ignoredScreens")) readStringList(options.getArray("ignoredScreens")) else null,
625
+ maskedElementIds = if (options.hasKey("maskedElementIds")) readStringList(options.getArray("maskedElementIds")) else null,
626
+ )
614
627
  promise.resolve(null)
615
628
  } catch (e: Exception) {
616
629
  promise.reject("PRIVACY_ERROR", e.message, e)
@@ -620,6 +633,7 @@ class ReplaySdkModule(reactContext: ReactApplicationContext) : ReplaySdkSpec(rea
620
633
  @ReactMethod
621
634
  override fun maskElement(elementId: String, promise: Promise) {
622
635
  try {
636
+ sessionManager?.addMaskedElement(elementId)
623
637
  promise.resolve(null)
624
638
  } catch (e: Exception) {
625
639
  promise.reject("PRIVACY_ERROR", e.message, e)
@@ -629,12 +643,23 @@ class ReplaySdkModule(reactContext: ReactApplicationContext) : ReplaySdkSpec(rea
629
643
  @ReactMethod
630
644
  override fun unmaskElement(elementId: String, promise: Promise) {
631
645
  try {
646
+ sessionManager?.removeMaskedElement(elementId)
632
647
  promise.resolve(null)
633
648
  } catch (e: Exception) {
634
649
  promise.reject("PRIVACY_ERROR", e.message, e)
635
650
  }
636
651
  }
637
652
 
653
+ /** ReadableArray → List<String>, nulls and non-strings dropped. */
654
+ private fun readStringList(arr: ReadableArray?): List<String> {
655
+ if (arr == null) return emptyList()
656
+ val out = ArrayList<String>(arr.size())
657
+ for (i in 0 until arr.size()) {
658
+ arr.getString(i)?.let { out.add(it) }
659
+ }
660
+ return out
661
+ }
662
+
638
663
  // ─── User ───────────────────────────────────────────────────────────
639
664
 
640
665
  @ReactMethod
@@ -226,6 +226,47 @@ class SessionManager(
226
226
  ReplayLogger.verbose = newConfig.verboseLogging
227
227
  }
228
228
 
229
+ /**
230
+ * Runtime privacy update — the missing half of `ScaleBun.replay.setPrivacy(...)`.
231
+ *
232
+ * `captureFrame` gates every capture on `config.disableScreenshots` / `config.ignoredScreens`
233
+ * and passes `config` (with `maskedElementIds`) into `scheduleCapture`, all reading THIS
234
+ * object's `config`. So merging the new privacy fields onto the current config and routing
235
+ * through `updateConfig` makes a runtime toggle take effect on the very next frame — no
236
+ * restart. MERGE, not replace: only the fields the caller supplied change; a null argument
237
+ * leaves that field as it was (the native `setPrivacyOptions` was a no-op stub before this,
238
+ * so the toggle silently did nothing on Android).
239
+ */
240
+ fun updatePrivacy(
241
+ disableScreenshots: Boolean? = null,
242
+ maskTextInputs: Boolean? = null,
243
+ maskImages: Boolean? = null,
244
+ ignoredScreens: List<String>? = null,
245
+ maskedElementIds: List<String>? = null,
246
+ ) {
247
+ updateConfig(
248
+ config.copy(
249
+ disableScreenshots = disableScreenshots ?: config.disableScreenshots,
250
+ maskTextInputs = maskTextInputs ?: config.maskTextInputs,
251
+ maskImages = maskImages ?: config.maskImages,
252
+ ignoredScreens = ignoredScreens ?: config.ignoredScreens,
253
+ maskedElementIds = maskedElementIds ?: config.maskedElementIds,
254
+ )
255
+ )
256
+ }
257
+
258
+ /** Add one element id to the runtime mask set (idempotent). Backs `replay.maskElement(id)`. */
259
+ fun addMaskedElement(elementId: String) {
260
+ if (config.maskedElementIds.contains(elementId)) return
261
+ updateConfig(config.copy(maskedElementIds = config.maskedElementIds + elementId))
262
+ }
263
+
264
+ /** Remove one element id from the runtime mask set. Backs `replay.unmaskElement(id)`. */
265
+ fun removeMaskedElement(elementId: String) {
266
+ if (!config.maskedElementIds.contains(elementId)) return
267
+ updateConfig(config.copy(maskedElementIds = config.maskedElementIds - elementId))
268
+ }
269
+
229
270
  // ─── Query ──────────────────────────────────────────────────────────
230
271
 
231
272
  fun getSessionInfo(): Map<String, Any?>? = session?.toMap()
@@ -7499,13 +7499,13 @@ var init_AppLaunchCollector = __esm({
7499
7499
  try {
7500
7500
  const perfModule = getNativePerformanceModule();
7501
7501
  if (!perfModule?.getAppStartInfo) {
7502
- logger.warn("[perf.trace] AppLaunchCollector: native perf module unavailable \u2014 no app_launch");
7502
+ logger.debug("[perf.trace] AppLaunchCollector: native perf module unavailable \u2014 no app_launch");
7503
7503
  return;
7504
7504
  }
7505
7505
  const jsReadyTimestamp = Date.now();
7506
7506
  const info = await perfModule.getAppStartInfo();
7507
7507
  if (!info || typeof info.durationMs !== "number") {
7508
- logger.warn("[perf.trace] AppLaunchCollector: getAppStartInfo returned no usable duration \u2014 no app_launch", {
7508
+ logger.debug("[perf.trace] AppLaunchCollector: getAppStartInfo returned no usable duration \u2014 no app_launch", {
7509
7509
  hasInfo: !!info,
7510
7510
  durationType: typeof info?.durationMs
7511
7511
  });
@@ -7555,13 +7555,13 @@ var init_AppLaunchCollector = __esm({
7555
7555
  breakdown: hasBreakdown ? breakdown : void 0,
7556
7556
  phaseLabels: hasBreakdown ? phaseLabels : void 0
7557
7557
  };
7558
- logger.warn("[perf.trace] AppLaunchCollector: app_launch built, invoking handler", {
7558
+ logger.debug("[perf.trace] AppLaunchCollector: app_launch built, invoking handler", {
7559
7559
  durationMs: event.durationMs,
7560
7560
  launchType: event.launchType
7561
7561
  });
7562
7562
  this.onLaunch(event);
7563
7563
  } catch (e) {
7564
- logger.warn("[perf.trace] AppLaunchCollector: collect() threw", {
7564
+ logger.debug("[perf.trace] AppLaunchCollector: collect() threw", {
7565
7565
  message: e?.message
7566
7566
  });
7567
7567
  }
@@ -8761,7 +8761,7 @@ var init_PerformanceFeature = __esm({
8761
8761
  _forwardMetricToBackend(metric) {
8762
8762
  const sm = SessionManager.getExistingInstance();
8763
8763
  const backendTransport = sm?.getBackendTransport();
8764
- logger.warn("[perf.trace] forwardMetricToBackend", {
8764
+ logger.debug("[perf.trace] forwardMetricToBackend", {
8765
8765
  type: metric.type,
8766
8766
  hasSessionManager: !!sm,
8767
8767
  hasBackendTransport: !!backendTransport,
@@ -8786,7 +8786,7 @@ var init_PerformanceFeature = __esm({
8786
8786
  for (const metric of buffered) {
8787
8787
  backendTransport.queuePerformanceMetric(metric);
8788
8788
  }
8789
- logger.warn("[perf.trace] drained buffered metrics to backend", {
8789
+ logger.debug("[perf.trace] drained buffered metrics to backend", {
8790
8790
  count: buffered.length
8791
8791
  });
8792
8792
  this._clearBackendDrainTimer();
@@ -15796,7 +15796,7 @@ var BackendSessionAdapter = class _BackendSessionAdapter {
15796
15796
  clientId: this._clientId(),
15797
15797
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
15798
15798
  });
15799
- logger.warn("[perf.trace] queue enqueued performance metric", {
15799
+ logger.debug("[perf.trace] queue enqueued performance metric", {
15800
15800
  type: data.type,
15801
15801
  pending: this.pendingPerformanceMetrics.length
15802
15802
  });
@@ -16368,7 +16368,7 @@ var BackendSessionAdapter = class _BackendSessionAdapter {
16368
16368
  async _flushPerformance() {
16369
16369
  if (this.pendingPerformanceMetrics.length === 0) return;
16370
16370
  if (!this.currentSessionId || !this.config.clientKey) {
16371
- logger.warn("[perf.trace] flush waiting \u2014 not deliverable yet", {
16371
+ logger.debug("[perf.trace] flush waiting \u2014 not deliverable yet", {
16372
16372
  pending: this.pendingPerformanceMetrics.length,
16373
16373
  hasSession: !!this.currentSessionId,
16374
16374
  hasClientKey: !!this.config.clientKey
@@ -16378,13 +16378,13 @@ var BackendSessionAdapter = class _BackendSessionAdapter {
16378
16378
  const batch = this.pendingPerformanceMetrics.splice(0, 50);
16379
16379
  try {
16380
16380
  if (await this._enqueueNative("performance", batch)) {
16381
- logger.warn("[perf.trace] flush handed to native outbox", {
16381
+ logger.debug("[perf.trace] flush handed to native outbox", {
16382
16382
  count: batch.length
16383
16383
  });
16384
16384
  return;
16385
16385
  }
16386
16386
  if (!this._sessionRowReady()) {
16387
- logger.warn("[perf.trace] flush session row not ready \u2014 re-buffering", {
16387
+ logger.debug("[perf.trace] flush session row not ready \u2014 re-buffering", {
16388
16388
  count: batch.length
16389
16389
  });
16390
16390
  this.pendingPerformanceMetrics.unshift(...batch);
@@ -16393,7 +16393,7 @@ var BackendSessionAdapter = class _BackendSessionAdapter {
16393
16393
  await this._post(ENDPOINTS.INGESTION_PERFORMANCE(this.currentSessionId), {
16394
16394
  metrics: batch
16395
16395
  });
16396
- logger.warn("[perf.trace] flush POST performance metrics ok", {
16396
+ logger.debug("[perf.trace] flush POST performance metrics ok", {
16397
16397
  count: batch.length
16398
16398
  });
16399
16399
  } catch (err) {