From c5e3ffe09bf11a8dd79e37c0ba80453fd3e650fa Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Wed, 14 May 2025 20:27:04 -0700 Subject: [PATCH] Safety feature to detect webcam watermark being removed --- src/App.vue | 6 +++-- src/components/VideoFeed.vue | 43 +++++++++++++++++++++++++++++++----- src/lib/WebRTC.js | 37 +++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 7 deletions(-) diff --git a/src/App.vue b/src/App.vue index 1ac7876..4c2a8e1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3409,7 +3409,8 @@ export default { @popout="popoutVideo" @open-profile="showProfileModal" @set-volume="setVideoVolume" - @modal-alert="modalAlert"> + @modal-alert="modalAlert" + @watermark-removed="onDeletedWatermark"> @@ -3430,7 +3431,8 @@ export default { @close-video="expresslyCloseVideo" @set-volume="setVideoVolume" @open-profile="showProfileModal" - @modal-alert="modalAlert"> + @modal-alert="modalAlert" + @watermark-removed="onDeletedWatermark"> diff --git a/src/components/VideoFeed.vue b/src/components/VideoFeed.vue index 6a1f4d0..6d39d66 100644 --- a/src/components/VideoFeed.vue +++ b/src/components/VideoFeed.vue @@ -27,8 +27,17 @@ export default { // Mouse over status mouseOver: false, + + // Mutation Observer, to detect if the user deletes the QR code via dev tools. + observer: null, }; }, + mounted() { + this.initWatermarkObserver(); + }, + beforeUnmount() { + this.observer?.disconnect(); + }, computed: { containerID() { return this.videoID + '-container'; @@ -132,13 +141,37 @@ export default { "to everybody, and a second (subtle) copy of the code spans across the center/middle of the video and pulsates in " + "transparency over time.", }); - } + }, + + // Mutation Observer to detect if the watermark is tampered with by the viewer. + initWatermarkObserver() { + const $container = this.$refs.videoContainer, + $watermark = this.$refs.watermarkImage, + $smallImage = this.$refs.watermarkSmallImage; + + if (!$container || !$watermark || !$smallImage) return; + + this.observer = new MutationObserver(() => { + const stillPresent = $container.contains($watermark) && $container.contains($smallImage); + if (!stillPresent) { + this.onWatermarkRemoved(); + } + }); + + this.observer.observe($container, { + childList: true, + subtree: true, + }); + }, + onWatermarkRemoved() { + this.$emit('watermark-removed', this.username); + }, } }