@scalebun/react-native 1.2.2 → 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.
|
@@ -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()
|
package/ios/ReplaySdk.swift
CHANGED
|
@@ -483,18 +483,31 @@ class ReplaySdk: RCTEventEmitter {
|
|
|
483
483
|
@objc func setPrivacyOptions(_ options: NSDictionary,
|
|
484
484
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
485
485
|
reject: @escaping RCTPromiseRejectBlock) {
|
|
486
|
+
// Was a no-op stub — the runtime toggle silently did nothing (only a restart, which
|
|
487
|
+
// re-reads the init config, honoured it). Each supplied key merges onto the running
|
|
488
|
+
// config via SessionManager.updatePrivacy → the capture manager; absent keys stay nil →
|
|
489
|
+
// unchanged, so a partial setPrivacy call never resets the other fields.
|
|
490
|
+
sessionManager?.updatePrivacy(
|
|
491
|
+
disableScreenshots: options["disableScreenshots"] as? Bool,
|
|
492
|
+
maskTextInputs: options["maskTextInputs"] as? Bool,
|
|
493
|
+
maskImages: options["maskImages"] as? Bool,
|
|
494
|
+
ignoredScreens: options["ignoredScreens"] as? [String],
|
|
495
|
+
maskedElementIds: options["maskedElementIds"] as? [String]
|
|
496
|
+
)
|
|
486
497
|
resolve(NSNull())
|
|
487
498
|
}
|
|
488
499
|
|
|
489
500
|
@objc func maskElement(_ elementId: String,
|
|
490
501
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
491
502
|
reject: @escaping RCTPromiseRejectBlock) {
|
|
503
|
+
sessionManager?.addMaskedElement(elementId)
|
|
492
504
|
resolve(NSNull())
|
|
493
505
|
}
|
|
494
506
|
|
|
495
507
|
@objc func unmaskElement(_ elementId: String,
|
|
496
508
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
497
509
|
reject: @escaping RCTPromiseRejectBlock) {
|
|
510
|
+
sessionManager?.removeMaskedElement(elementId)
|
|
498
511
|
resolve(NSNull())
|
|
499
512
|
}
|
|
500
513
|
|
|
@@ -21,6 +21,49 @@ class SessionManager {
|
|
|
21
21
|
var onLimitReached: ((String, Int, Int, String) -> Void)? // limitType, current, max, sessionId
|
|
22
22
|
var onScreenChanged: ((String) -> Void)? // screenName — forwarded to JS for backend upload
|
|
23
23
|
|
|
24
|
+
/// Runtime config update — propagates to the capture manager, which applies masking +
|
|
25
|
+
/// screenshot settings on the next frame. `config` is `private(set)`, so this is the one
|
|
26
|
+
/// sanctioned way to change it after init.
|
|
27
|
+
func updateConfig(_ newConfig: ReplayConfig) {
|
|
28
|
+
config = newConfig
|
|
29
|
+
captureManager.updateConfig(newConfig)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Runtime privacy update — the missing half of `ScaleBun.replay.setPrivacy(...)`.
|
|
33
|
+
///
|
|
34
|
+
/// `captureFrame` gates on `config.disableScreenshots` and the capture manager applies
|
|
35
|
+
/// `PrivacyMaskProcessor` from `config`, all reading THIS object's config. Merging the
|
|
36
|
+
/// supplied fields and routing through `updateConfig` makes a runtime toggle take effect on
|
|
37
|
+
/// the next frame with no restart. Merge, not replace: a nil argument leaves that field.
|
|
38
|
+
/// (`setPrivacyOptions` was a no-op stub before this, so the toggle did nothing at runtime.)
|
|
39
|
+
func updatePrivacy(disableScreenshots: Bool? = nil,
|
|
40
|
+
maskTextInputs: Bool? = nil,
|
|
41
|
+
maskImages: Bool? = nil,
|
|
42
|
+
ignoredScreens: [String]? = nil,
|
|
43
|
+
maskedElementIds: [String]? = nil) {
|
|
44
|
+
var next = config
|
|
45
|
+
if let v = disableScreenshots { next.disableScreenshots = v }
|
|
46
|
+
if let v = maskTextInputs { next.maskTextInputs = v }
|
|
47
|
+
if let v = maskImages { next.maskImages = v }
|
|
48
|
+
if let v = ignoredScreens { next.ignoredScreens = v }
|
|
49
|
+
if let v = maskedElementIds { next.maskedElementIds = v }
|
|
50
|
+
updateConfig(next)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func addMaskedElement(_ elementId: String) {
|
|
54
|
+
guard !config.maskedElementIds.contains(elementId) else { return }
|
|
55
|
+
var next = config
|
|
56
|
+
next.maskedElementIds = config.maskedElementIds + [elementId]
|
|
57
|
+
updateConfig(next)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func removeMaskedElement(_ elementId: String) {
|
|
61
|
+
guard config.maskedElementIds.contains(elementId) else { return }
|
|
62
|
+
var next = config
|
|
63
|
+
next.maskedElementIds = config.maskedElementIds.filter { $0 != elementId }
|
|
64
|
+
updateConfig(next)
|
|
65
|
+
}
|
|
66
|
+
|
|
24
67
|
init(config: ReplayConfig) {
|
|
25
68
|
self.config = config
|
|
26
69
|
self.captureManager = ScreenshotCaptureManager(config: config)
|