Is it possible to make Ultra-low-latency Android WebRtc client for web camera?

I’m trying to make web camera with latency 50-100ms for real-time control purposes. The server is a python script, the client is WebRTC application running in Android Google Chrome, directly connected to the server (There is no switch or router between). No audio stream. It would be good if a frame is displayed as soon as received. Lagging and freezing is OK. The resolution is 1024×578 colorsubsampling=’420′, H.264, 50 FPS. My Android device supports hardware accelerated decoding. Hardware acceleration is on in the browser options.

The smallest latency I’ve achieved is 170 ms. I understand that it is called low latency for real live applications? but Is it possible to reduce latency more? The same application running in Desktop Google Chrome has 100 ms latency which is OK. I suppose that Android Google Chrome has minimum jitter buffer limit higher than Desktop one. Am I right?

My current code for establishing WebRTC connection and streaming the following

    public async runWebRTC() {
        const pc = this.pc;

        pc.addTransceiver('video', {direction: 'recvonly'});

        pc.ontrack = event => {
            this.video.nativeElement.srcObject = event.streams[0];

            pc.getReceivers().forEach(receiver => {
                if (receiver.track.kind === "video") {
                    try {
                        receiver.jitterBufferTarget = 10;
                        receiver.track.contentHint = "motion";
                    } catch (e) {
                    }
                }
            });

        };

        pc.onicecandidate = async e => {
            if (e.candidate === null) {
                const offer = pc.localDescription;
                try {

                    const response = await fetch('/offer', {
                        method: 'POST',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify(offer)
                    });

                    const answer = await response.json();

                    await pc.setRemoteDescription(answer);
                } catch (err) {
                    console.error('Error sending offer:', err);
                }
            }
        };

        // Create an offer to send to the server
        const offer = await pc.createOffer();
        await pc.setLocalDescription(offer);
    }

What can be done on client side to reduce latency?

Button value not posting in a Form

(Using Next.js/React)

I have a Form with a custom autocomplete that is used to search a list of Users:

<Form action="">
   <button type="submit">Search</button>
   <input name="search" type="text"/>

   <!-- these are generated with a .map() -->
   <div>
      <button type="submit" name="search" value=1>User1</button>
      <button type="submit" name="search" value=2>User2</button>
      <button type="submit" name="search" value=3>User3</button>
   <div>
</Form>

When I search using the <input>, it correctly navigates the page to (for example, if searching ID = 3) /?search=3 which I then use server side.

I want to be able to use these buttons to select an autocomplete suggestion and send it in the Form action. When I click one of the buttons it does append /?search= to the URL but it completely ignores the value of the clicked button.

I’ve tried adding hidden <input> elements but it ends up grabbing all of the button‘s values and trying to use it in the query like: /?search=1,search=2,search=3.

How can I pass just the value of the clicked button to the Form action?

Is splash:select different from document.querySelector?

example link https://www.anytimemailbox.com/s/new-york-42-broadway

function main(splash)
  local button = splash:select('div[class="t-disc"]>a')
  assert(button) -- fail
end

But in the F12 developer tool console

>> document.querySelector('div[class="t-disc"]>a') 
<a href="#" onclick="thShowFullServicePlan11(4967);return false;">Full Details</a>

The doc https://splash.readthedocs.io/en/stable/scripting-ref.html#splash-select says

Using splash:select you can get the element that matches your specified CSS selector like using document.querySelector in the browser.

Is there some corner case where splash:select is different from document.querySelector? If so, is there one workaround?

Vue 3 Audio Visualizer Not Working | No errors in the console, audio not playing, canvas not updating

I’m building an audio visualizer in Vue 3 using the Web Audio API. My setup uses Pinia for state management. Each track is an object with an audio property (created via new Audio(...)). The visualizer is a separate component (Visualizer.vue) that connects to the current track’s audio element and draws on a <canvas>.

Problems:

  • The audio does not play when I click play.
  • The progress bar in my player does not update, even though the
    store’s values are reactive and update correctly.
  • The visualizer canvas is rendered, but nothing is drawn (no bars, no
    animation).
  • No errors appear in the console.

Link to repo: https://github.com/kubakorniluk/audio-visualizer

Part 1 – Audio not playing

What I’ve tried and checked:

  • The audio file paths are correct.
  • The store’s currentTrack and isPlaying values update as expected.
  • The visualizer code runs, but the canvas often stays blank.
  • No errors in the browser console.
  • Changing the canvas height to a number (not “auto”) doesn’t help.
  • I call togglePlay(store.getCurrentTrack.id), nextTrack() and prevTrack() from a buttons in the Player component.

Store overview (tracks.js):

const tracks = ref([])
const currentTrack = ref(null)

...

 const setTracks = async () => {
    const files = import.meta.glob('@/assets/audio/*.wav');

    for (const path in files) {
      const fileModule = await files[path]();

      const audio = new Audio(fileModule.default)

      const track = {
        id: tracks.value.length,
        name: path.split('/').pop(),
        src: fileModule.default,
        audio: audio,
        isPlaying: false,
        volume: 1.0,
        currentTime: {
          total: 0,
          minutes: 0,
          seconds: 0
        },
        duration: {
          total: 0,
          minutes: 0,
          seconds: 0
        },
      }

      tracks.value.push(track);
      
      console.log(`Loaded: ${track.name}`, track);
      
    }

    currentTrack.value = tracks.value[0];

  }

  const setCurrentTrack = (id) => {
    tracks.value.forEach(track => {
      if (track.id === id) {
        track.isPlaying = !track.isPlaying;
      } else {
        track.isPlaying = false;
      }
    })
    currentTrack.value = tracks.value.find(track => track.id === id);
  }

const togglePlay = (id) => {
  const track = tracks.value.find(t => t.id === id);
  if (!track) return;

  // If the clicked track is already playing, pause it
  if (track.isPlaying) {
    track.audio.pause();
    track.isPlaying = false;
  } else {
    // Pause all other tracks
    tracks.value.forEach(t => {
      if (t.id !== id) {
        t.audio.pause();
        t.isPlaying = false;
      }
    });
    // Play the selected track
    track.audio.play();
    track.isPlaying = true;
    currentTrack.value = track;
  }
};

Next & Prev track functions:

 const nextTrack = () => {
    let id = currentTrack.value.id + 1;
    if (id >= tracks.value.length) {
      id = 0;
    }
    setCurrentTrack(id);
    console.log(currentTrack.value.id)
    togglePlay(id);
  }
  const prevTrack = () => {
    let id = currentTrack.value.id - 1;
    if (id < 0) {
      id = tracks.value.length - 1;
    }
    setCurrentTrack(id);
    console.log(currentTrack.value.id)
    togglePlay(id);
  }

Part 2 – Visualizer & Progress bar
The progress bar in Player.vue does not update as the audio plays, even though the store’s currentTime is reactive and updates in the console. The visualizer in Visualizer.vue (using Web Audio API and <canvas>) does not animate or show any bars, even when audio is playing.

What I’ve checked:

  • The store’s currentTime and duration objects update correctly
    (confirmed by logging).
  • The progress bar’s computed value changes in the console, but the UI
    does not update.
  • The visualizer’s code runs, but the canvas remains blank.

Player.vue:

<script setup>
    
    ...

    const progress = computed(() => {
        const track = store.getCurrentTrack;
        if (track && track.duration.total > 0) {
            return (track.currentTime.total / track.duration.total) * 100;
        }
        return 0;
    });
</script>
<template>
    <footer class="player">
        <div class="progress">
            <Visualizer></Visualizer>
            <div class="progress-bar" :style="{ width: progress + '%' }">
                <FontAwesomeIcon 
                    class="progress-bar__icon" 
                    :icon="faCircle" 
                ></FontAwesomeIcon>
            </div>
        </div>
        <div class="settings">
            <div class="controls">
                <span class="controls__time">{{ time }}</span>
                <button class="controls__button" @click="() => store.prevTrack()">
                    <FontAwesomeIcon 
                        class="controls__button--icon" 
                        :icon="faBackwardStep" 
                        style="font-size: 1.5rem"
                    ></FontAwesomeIcon>
                </button>
                <button class="controls__button" @click="() => store.togglePlay(store.getCurrentTrack.id)">
                    <FontAwesomeIcon 
                        class="controls__button--icon" 
                        :icon="checkIsPlaying() ? faPause : faPlay"
                    ></FontAwesomeIcon>
                </button>
                <button class="controls__button" @click="() => store.nextTrack()">
                    <FontAwesomeIcon 
                        class="controls__button--icon" 
                        :icon="faForwardStep" 
                        style="font-size: 1.5rem"
                    ></FontAwesomeIcon>
                </button>
                <span class="controls__time">{{ checkDuration()  }}</span>
            </div>
        </div>
    </footer>
</template>

tracks.js:

  const setTracks = async () => {
     ...

      //update track duration
      audio.addEventListener("loadedmetadata", (event) => {
        let duration = audio.duration;
        let minutes = Math.floor(duration / 60);
        let seconds = Math.floor(duration % 60);
        track.duration.total = duration;
        track.duration.seconds = seconds;
        track.duration.minutes = minutes;
      });

      // update track position
      audio.addEventListener("timeupdate", (event) => {
        let time = audio.currentTime;
        let minutes = Math.floor(time / 60);
        let seconds = Math.floor(time % 60);
        track.currentTime.total = time;
        track.currentTime.seconds = seconds;
        track.currentTime.minutes = minutes;
      });
      audio.load();

      // Check if metadata is already loaded
      if (audio.readyState >= 1) {
        let duration = audio.duration;
        let minutes = Math.floor(duration / 60);
        let seconds = Math.floor(duration % 60);
        track.duration.seconds = seconds;
        track.duration.minutes = minutes;
      }

      tracks.value.push(track);

       console.log(`Loaded: ${track.name}`, track);
      
    }

    currentTrack.value = tracks.value[0];

  }

CTA button at the nav bar is out of the screen

enter image description here

As you can in the screen Shedule a call button is not shown properly. Following are the code:

CSS:

.call-btn {
  background-color: #FF00C7;
  color: white;
  padding: 10px 20px;
  text-decoration: none;
  border-radius: 6px;
  transition: background 0.3s;
  font-weight: bold;
  display: inline-block;
  white-space: nowrap;
  min-width: 140px;
  text-align: center;
}

index.html code:

<a href="#call" class="call-btn">Schedule a Call</a>

I think this much code is enough. Thank you in advance

How to show full autocomplete suggestions in JavaScript

The Ace editor demo shows how you can show autocomplete for JavaScript, like when writing document.querySelector.

I created the most simple demo using the latest version of ace on codepen since this shows you what I’m seeing in my app as well: I can’t autocomplete or get a suggestion for things like document.querySelector or document.addEventListener which are some of the suggestions that do work fine in the ace tryout demo.

Here are two demo’s for you to compare:

Give it a try by trying to autocomplete document.querySelector

Error when accessing JS package in Dataform

Complete newbie with JavaScript, Dataform and BiqQuery so my current level right now is copy/paste from internet. Now however I have come across a problem I can’t find any suitable tips for.

I have added the package @google-cloud/bigquery to the package.json file, and installed it successfully by clicking “Install packages”:

{
    "dependencies": {
        "@dataform/core": "3.0.0",
        "@google-cloud/bigquery": "8.1.0"
    }
}

To test I have created a simple function in the file includes/columnFunctions.js:

function firstTest() {

    const {BigQuery} = require('@google-cloud/bigquery');
    const bigquery = new BigQuery();
    return "XX";

}

module.exports = { firstTest }

Then calling the function in an SQLX file:

config {type: "view"}


select ${columnFunctions.firstTest()} as X
from ${ref("dummysrc1")}

This returns an error: “Failed to resolve events”

The error is generated by the first statement const {BigQuery} = require('@google-cloud/bigquery'); so I’m suspecting some sort of access or authorization problem, but have no idea where to start looking…

ContentSecurityPolicy for script set to nonce but Ajax request not executing javascript response despite identical nonce

In my web app (based on Hanami 2.2) I have ContentSecurityPolicy enabled to allow inline scripts to run using nonce strategy

config/app.rb

config.actions.content_security_policy[:script_src] = "'self' 'nonce'"

Because of that when the page a loaded in the browser, the CSS and JS tags (included using hanami’s helper methods stylesheet_tag and javascript_tag) in the DOM have a nonce attached (like it can be seen in snippet extracted from the loaded page source)

<html lang='en'>
  <head>
    <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'>
    <meta charset='UTF-8'>
    <meta content='width=device-width, initial-scale=1.0' name='viewport'>
    <meta name="csrf-param" content="_csrf_token"><meta name="csrf-token" content="4111c46fb6a45da9dfea2ccd8521256f7f1c3852fbedc890d2ed56dc4111b7c9">
    <link href="/assets/app.css" type="text/css" rel="stylesheet" nonce="iWzsbPAkiB//CnB0">
  </head>
  <body>
    ...
    ....
    <script src="/assets/app.js" type="text/javascript" nonce="iWzsbPAkiB//CnB0"></script>
   </body>
</html>

Now I have a page on which I have a simple form containing two text-fields.
The form needs to be submitted through jQuery AJAX API and the response needs to render JS code defined in following file.

app/templates/home/create.js.haml

%script{ nonce: content_security_policy_nonce }
  :plain
    alert("Hello");

This is similar to how a JS response is rendered in Rails as illustrated with file app/views/users/create.js.erb at https://guides.rubyonrails.org/v6.0/working_with_javascript_in_rails.html#server-side-concerns .

Note that the nonce set on the script tag in js.haml file above should be the same as set on the <script src="/assets/app.js" tag when the page containing form was loaded. That’s because for generating nonce I am using a request’s session-id.

When the form is submitted through AJAX, the Request contains following values for mentioned headers

Accept */*
Content-Type application/x-www-form-urlencoded; charset=UTF-8

and the Response contains following values for mentioned headers

Content-Security-Policy base-uri 'self';child-src 'self';connect-src 'self';default-src 'none';font-src 'self';form-action 'self';frame-ancestors 'self';frame-src 'self';img-src 'self' https: data:;media-src 'self';object-src 'none';script-src 'self' 'nonce-iWzsbPAkiB//CnB0';style-src 'self' 'unsafe-inline' https:

Content-Type application/javascript; charset=utf-8

and the Response’s body contains following

<script nonce='iWzsbPAkiB//CnB0'>
alert("Hello");
</script>

But that inline script doesn’t execute. In browser’s console I see following error

Content-Security-Policy: The page’s settings blocked an inline script (script-src-elem) from being executed because it violates the following directive: “script-src 'self' 'nonce-iWzsbPAkiB//CnB0'”

Despite the identical nonce in the inline script why it is not executing?

If I change my CSP policy to following (the easiest and frequently seen suggested for the problem I am facing)

config.actions.content_security_policy[:script_src] = "'self' 'unsafe-inline'"

and update app/templates/home/create.js.haml in following manner

:plain
  alert("Hello");

then when I submit form, the response’s body contains

alert("Hello");

and it does gets executed prompting an alert. But using ‘unsafe-inline’ is as good as have no CSP policy set. So I don’t prefer it.

Also if I update the create.js.haml code to following

:javascript
  alert("Hello");

then the response’s body contains

<script>
alert("Hello");
</script>

and for it I see following error in browser’s console:

Uncaught SyntaxError: expected expression, got '<'

In this case why the script didn’t executed?

I found various posts related to my problem few of which I have listed below but trying out solutions based on them didn’t worked for me.

How to execute inline scripts retrieved from a JQuery Ajax request when content security is enabled in php

Inline Javascript not executed

‘self’ scripts being blocked from loading, but when I use the nonce they work?

Injected script doesn’t execute even with script tags “fixed” up

https://security.stackexchange.com/q/240353

https://manikandanmv.wordpress.com/2008/09/26/how-to-execute-the-scripts-in-ajax-responsehtml-with-scripts/

https://webdeveloper.com/community/138830-run-javascript-after-ajax-load/

https://www.sitepoint.com/community/t/basic-javascript-not-working-after-ajax-call/224091

https://support.google.com/tagmanager/thread/35871504/how-to-include-nonce-attribute-in-custom-script?hl=en

I am struggling since hours to get past the problem but nothing I find seems to be helping out.

Thanks.

Programmatically render/destroy components from non-host component (Angular)

The task is to create a fully dynamic and independent component (Tooltip) that could be programmatically rendered/destroyed in a certain place in the DOM (Dashboard) by a non-host component (Calendar). It is a tooltip shown on hover inside of Calendar (or over the calendar to be precise). It can’t be added to the DOM as a child of Calendar though due to certain CSS limitations with position: fixed + overflow: auto.

Here’s the basic structure I want:

<app-dashboard>
  <app-whatever>
    <app-random>
      <app-calendar>I programmatically add a tooltip to my distant ancestor on hover</app-calendar>
    </app-random>
  </app-whatever>

  <app-tooltip>I am programmatically rendered and destroyed</app-tooltip>
<app-dashboard>

After searching the internet and the docs, I now think I have to use createComponent() inside Calendar component.

I’ve tried to use the standalone createComponent() function as well as viewContainer.createComponent(). The latter doesn’t allow to set a host element.

The former allows for that, but it has another issue: when I destroy Tooltip using this.tooltipInstance.destroy(), it deletes the host element too.

Here’s my setup:

<app-dashboard>
  <app-wrapper>
    <app-section>
      <app-calendar>I programmatically add a tooltip to my distant ancestor on hover</app-calendar>
    </app-section>
  </app-wrapper>

  <!-- I am wiped out out on mouse leave :( -->
  <div class="event-tooltip-host">
    <app-tooltip>I am programmatically rendered and destroyed</app-tooltip>
  </div>
<app-dashboard>
eventMouseEnter({ event, el }) {
  this.tooltipInstance = createComponent(TooltipComponent, {
    environmentInjector: this.injector,
    hostElement: document.querySelector('.event-tooltip-host')!,
    bindings: [inputBinding('event', () => event), inputBinding('element', () => el)]
  })

  this.appRef.attachView(this.tooltipInstance.hostView)
  this.tooltipInstance.changeDetectorRef.detectChanges()
}

eventMouseLeave() {
  this.appRef.detachView(this.tooltipInstance.hostView)
  this.tooltipInstance.destroy()
}

It appears impossible to fully control dynamic rendering of a component back and forth from within a totally random component.

My backup plan is to do it from Dashboard component via some app-level actions which I can listen to in any component, but I would really expect Angular to have a better way to do it.

So, the question is how do I avoid destroying the wrapper, <div class="event-tooltip-host">, and only destroy <app-tooltip>?

Chrome Extension: Items Disappear from chrome.storage.local Even Though Storage Limit Not Reached

I’m developing a Chrome extension that stores vocabs into a vocabulary list in chrome.storage.local. My extension currently uses about 2MB of storage (well below the 10MB limit – then i added unlimitedStorage in the permission, still doesnt work), but I’m running into a strange issue:

Currently i have 2692 items in the list. When I add a new item, it appears in the list, and I can see it when i print the entire list: It has 2693 items in the list
After a while (usually 10seconds to 1 minute), the newly added word disappears from the list and from storage.somehow its just gone
The storage usage remains around 2MB, so I don’t think I’m hitting the quota. And i already added unlimitedStorage “permissions”: [“storage”,”unlimitedStorage”,],

I don’t see any errors in the console, and the word is definitely being saved initially. I did not made any change that could have called issue for a while – It has been working and adding fine for like 4~5 months until recently.
I’ve checked my code and confirmed that:

I always use chrome.storage.local.get and chrome.storage.local.set to read and write the vocab list.
I don’t intentionally delete or filter out items after adding them.
The issue happens even if I only have one tab open.

Redirect from Netlify Subdomain to Own Domain

I have a React ViteJS project deployed on Netlify and working fine with the subdomain. Recently thought of adding a custom domain and added Netlify Name Servers to custom DNS in Namecheap.
Let’s say it’s project-name.netlify.app and new domain is mysite.xyz.

I want to redirect the subdomain visitors to the new domain. tried adding a _redirects to the public folder (/public/_redirects).

In the _redirects file, I’ve added the following,

# Redirect only when accessed via Netlify subdomain
/*  https://www.mysite.xyz/:splat  301!  Host=project-name.netlify.app

this seems not working even though the build logs shows as 1 redirect rules processed.
But when I remove the Host=project-name.netlify.app, the browser shows an error page with too many redirects.

tried adding redirect to the netlify.toml too. but neither works. My netlify.toml file is as follows,

[functions]
  directory = "netlify/functions"

[[redirects]]
  from = "/*"
  to = "https://mysite.xyz/:splat"
  status = 301
  force = true
  conditions = { Host = "project-name.netlify.app" }

didn’t add both the _redirects and [[redirects]] in netlify.toml together as it’s not recommended.

Both project-name.netlify.app and mysite.xyz loads the site fine!

How do I solve this redirect issue?

Netlify Domain Management

How to convert a php regex to javascript to fetch urls from string?

I am using regex in php to get urls from string but need to use same regex in Javascript.
I have converted regex to javascript but getting “Invalid Regular Expression”.

PHP REGEX

$regex = '#b[a-zA-Z0-9_-]{3,}+://[^,s()<>]+(?:([wd]+)|([^,[:punct:]s]|/))}?#';
preg_match_all($regex, $message, $matches);
$urls = $matches[0];

Converted to javascript

const pattern = /b[a-zA-Z0-9_-]{3,}+?://[^,s()<>]+(?:([wd]+)|([^,s()<>]|/))/g;
var result = pattern.exec(message);
console.log(result);

I am not sure what is wrong in javascript regex. please help.

How to access component from a custom directive?

The goal is to have some sort of reusable directive, hook, utility, that would allow me to auto-focus on a component. I chose the approach of a custom directive.

What I got:

<!-- MyForm.vue -->
<template>
  <UiInput v-auto-focus>
  <UiSelect>
  <UiRadio>
  ...
</template>

The component has a wrapper:

<!-- UiInput.vue -->
<template>
  <div class="wrapper">
    <input />
  </div>
</template>

Here’s the directive taken straight from the docs:

import type { Directive } from 'vue';

export const vAutoFocus: Directive = {
  mounted: (el) => el.focus?.(),
};

This setup above won’t work, because the directive is trying to focus on a div element, which is, well, not focusable.

  • Making the div focusable with tabindex will reduce a11y and code maintainability and probably behave as expected
  • Using props on the component side like autoFocus might end up in prop drilling if I decide to add more wrappers and functionality to the UiInput.vue component.
  • Several components require something like this, so I’d like to reuse this functionality, like I would with directives in Angular
  • vnode argument of mounted shows the div wrapper, and its component field is null, so I can’t access its properties, even if I defineExpose them

So what do I do?

Is it possible in Plotly to select multiple charts under each other?

I’m trying to select multiple charts under each other with Plotly, in JavaScript. The best I could do so far is to create a stacked chart, but it still does not allow selection of multiple rows.
The idea is to detect in which row, at which point did the user start to drag there mouse and at which row, which point it ended. The charts should be selected from start to end. If there are multiple charts between the starting and ending row, the whole chart should be selected. Is there some sort of built-in solution for this in Plotly? I want to avoid HTML Canvases on top of Plotly at all costs, but so far I can’t see any other solution.
My code so far:

var trace1 = {
  x: [0, 1, 2],
  y: [10, 20, 30],
  type: "scatter",
  xaxis: "x",
  yaxis: "y",
};

var trace2 = {
  x: [2, 3, 4],
  y: [10, 20, 30],
  type: "scatter",
  xaxis: "x",
  yaxis: "y2",
};

var trace3 = {
  x: [3, 4, 5],
  y: [10, 20, 30],
  type: "scatter",
  xaxis: "x",
  yaxis: "y3",
};

var dummyTrace = {
  x: [0],
  y: [0],
  xaxis: "x_overlay",
  yaxis: "y_overlay",
  mode: "markers",
  marker: { opacity: 0 }, // invisible dummy trace
  showlegend: false,
  hoverinfo: "skip",
};

var data = [trace1, trace2, trace3, dummyTrace];

var layout = {
  grid: { rows: 3, columns: 1, pattern: "independent" },

  xaxis: { domain: [0, 1] }, // real xaxis
  yaxis: { domain: [0.66, 1] }, // top subplot
  yaxis2: { domain: [0.33, 0.66] }, // middle
  yaxis3: { domain: [0, 0.33] }, // bottom

  // This is the transparent overlay axis (spanning entire figure)
  xaxis_overlay: { domain: [0, 1], overlaying: "x", visible: false },
  yaxis_overlay: { domain: [0, 1], overlaying: "y", visible: false },

  dragmode: "select",
  selectdirection: "any",
  margin: { l: 50, r: 20, t: 20, b: 50 },
};

Plotly.newPlot("myDiv", data, layout);

// Listen to selection events — this works across entire panel now:
document.getElementById("myDiv").on("plotly_selected", function (eventData) {
  if (eventData) {
    console.log("Selected points:", eventData.points);
    // You can now filter which subplot they came from:
    eventData.points.forEach((pt) => {
      console.log(`subplot: ${pt.yaxis}, x: ${pt.x}, y: ${pt.y}`);
    });
  }
});

It looks like this:

Plotly selection