|
8 | 8 | //
|
9 | 9 | #if os(iOS)
|
10 | 10 | import Foundation
|
| 11 | + import PhotosUI |
11 | 12 | import SwiftUI
|
12 | 13 | import UIKit
|
13 | 14 | import WebKit
|
|
82 | 83 |
|
83 | 84 | private let reactNativeTextView: AnyClass? = NSClassFromString("RCTTextView")
|
84 | 85 | private let reactNativeImageView: AnyClass? = NSClassFromString("RCTImageView")
|
| 86 | + // These are usually views that don't belong to the current process and are most likely sensitive |
| 87 | + private let systemSandboxedView: AnyClass? = NSClassFromString("_UIRemoteView") |
85 | 88 |
|
86 | 89 | static let dispatchQueue = DispatchQueue(label: "com.posthog.PostHogReplayIntegration",
|
87 | 90 | target: .global(qos: .utility))
|
|
283 | 286 | }
|
284 | 287 | }
|
285 | 288 |
|
| 289 | + // detect any views that don't belong to the current process (likely system views) |
| 290 | + if config.sessionReplayConfig.maskAllSandboxedViews, |
| 291 | + let systemSandboxedView, |
| 292 | + view.isKind(of: systemSandboxedView) |
| 293 | + { |
| 294 | + maskableWidgets.append(view.toAbsoluteRect(window)) |
| 295 | + return |
| 296 | + } |
| 297 | + |
286 | 298 | // if its a generic type and has subviews, subviews have to be checked first
|
287 | 299 | let hasSubViews = !view.subviews.isEmpty
|
288 | 300 |
|
|
380 | 392 | image.imageAsset?.value(forKey: "_containingBundle") != nil
|
381 | 393 | }
|
382 | 394 |
|
| 395 | + // Photo library images have a UUID identifier as _assetName (e.g 64EF5A48-2E96-4AB2-A79B-AAB7E9116E3D) |
| 396 | + // SF symbol and bundle images have the actual symbol name as _assetName (e.g chevron.backward) |
| 397 | + private func isPhotoLibraryImage(_ image: UIImage) -> Bool { |
| 398 | + guard config.sessionReplayConfig.maskPhotoLibraryImages else { |
| 399 | + return false |
| 400 | + } |
| 401 | + |
| 402 | + guard let assetName = image.imageAsset?.value(forKey: "_assetName") as? String else { |
| 403 | + return false |
| 404 | + } |
| 405 | + |
| 406 | + if assetName.isEmpty { return false } |
| 407 | + if image.isSymbolImage { return false } |
| 408 | + if isAssetsImage(image) { return false } |
| 409 | + |
| 410 | + return true |
| 411 | + } |
| 412 | + |
383 | 413 | private func isAnyInputSensitive(_ view: UIView) -> Bool {
|
384 | 414 | isTextInputSensitive(view) || config.sessionReplayConfig.maskAllImages
|
385 | 415 | }
|
|
429 | 459 | }
|
430 | 460 |
|
431 | 461 | private func isImageViewSensitive(_ view: UIImageView) -> Bool {
|
432 |
| - var isAsset = false |
433 |
| - if let image = view.image { |
434 |
| - isAsset = isAssetsImage(image) |
435 |
| - } else { |
436 |
| - // if there's no image, there's nothing to mask |
437 |
| - return false |
| 462 | + // if there's no image, there's nothing to mask |
| 463 | + guard let image = view.image else { return false } |
| 464 | + |
| 465 | + // sensitive, regardless |
| 466 | + if view.isNoCapture() { |
| 467 | + return true |
438 | 468 | }
|
439 |
| - return (config.sessionReplayConfig.maskAllImages && !isAsset) || view.isNoCapture() |
| 469 | + |
| 470 | + if config.sessionReplayConfig.maskAllImages { |
| 471 | + // asset images are probably not sensitive |
| 472 | + return !isAssetsImage(image) |
| 473 | + } |
| 474 | + |
| 475 | + // try to detect user photo images |
| 476 | + return isPhotoLibraryImage(image) |
440 | 477 | }
|
441 | 478 |
|
442 | 479 | private func toWireframe(_ view: UIView) -> RRWireframe? {
|
|
0 commit comments