reCAPTCHA V2 Invisible always executing data-callback before security evaluation or challenge

I’m currently encountering an issue while implementing reCAPTCHA V2 Invisible on a website. I’ve configured the captcha process to trigger after my page has loaded, however, I’ve observed that the data-callback property of the captcha consistently executes before the completion of the challenge or security assessment. As a result, every attempt to retrieve the captcha token or result ends up with a null element.

Remark:
It’s worth noting that when I manually execute the grecaptcha.execute() function from the browser console, everything seems to work correctly. However, when waiting for the execution in the programmatic way outlined in my code, it doesn’t seem to function as expected.

Your insights on resolving this timing issue would be greatly appreciated. Thank you in advance!

My code:

captchaV2.js:

const onLoad = (token) => {
    console.log(`Token Captcha = ${token}`);
    console.log(`respuesta desafio = ${grecaptcha.getResponse()}`);
}

grecaptcha.ready(() => {
    grecaptcha.execute('<SITE-KEY>').then(onLoad).catch(error => console.error('Error al generar token:', error));
});

html file:

<head>
    <script src="https://www.google.com/recaptcha/api.js? 
       render=<SITE-KEY>"></script>
    <script defer type="text/javascript" src='personal/claro/js/captchaV3.js'></script>
</head>
<body>
 <form>
  <div id="recaptcha" class="g-recaptcha" data-sitekey="<SITE-KEY>" 
     data-callback="onLoad" data-size="invisible"></div>
  </form>
</body>

how to extract an icon from an exe file

When I open a dialog window using Electron, select a file, I need to get the icon of this file

    dialog.showOpenDialog({
      properties: ['openFile'],
      filters: [{ name: 'All Files', extensions: ['exe','url'] }]
    }).then(result => {
      if (!result.canceled && result.filePaths.length > 0) {
        const filePath = result.filePaths[0];
        fs.readFile(filePath, async(err) => {
          if (!err) {
            /*get icon*/
          } else {
            console.error(err);
          }
        });
      }
    }).catch(err => {
      console.error(err);
    });
  });

Adding content to popover using JS

I am trying to add a button to my popover using JS. The reason being, I will eventually be generating these buttons using PHP, however it seems I am doing something wrong.

<div class="col-1">
  <a data-toggle="popover" id="bedsButton" data-placement="bottom" data-html="true" title="Number of Bedrooms">Beds</a>
</div>
$('[data-toggle="popover"]').popover();
$('#bedsButton').popover({
  content: `<button value="0" class="">0</button>`,
  trigger: 'click'
});

I’ve tried different ways of doing it with jQuery however I can’t seem to figure it out

three.js how to add Pointcloud as textfile

i try to add Point cloud file “txt” to html page using three.js.
i get alwas Black screen

file content:
0.0802 212.8271 -119.7713 0619
0.0690 212.8270 -119.6854 0629
0.0578 212.8269 -119.5996 0633
0.0839 212.8269 -119.5214 0666
0.1473 212.8269 -119.4507 0646
0.1361 212.8268 -119.3649 0637
0.1324 212.8267 -119.2806 0624
0.1287 212.8267 -119.1962 0604
0.0727 212.8266 -119.1014 0629
0.0765 212.8265 -119.0186 0640
0.0727 212.8265 -118.9343 0671
0.0467 212.8264 -118.8455 0675
0.0802 212.8264 -118.7687 0623
0.0989 212.8263 -118.6888 0612
0.0877 212.8263 -118.6030 0565
0.0318 212.8262 -118.5084 0626
…..

    <script src="https://threejs.org/build/three.js"></script>
    <script>
    
      // Initialisiere Three.js
          const scene = new THREE.Scene();
          const camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
          );
          
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);

        //scene.add(camera);
        // Lade Punktwolken-Daten aus der TXT-Datei
        const loader = new THREE.FileLoader();
        loader.load(
            'pointcloud.txt',
            (data) => {
                // Verarbeite die geladenen Daten
                const points = data.split('n').map(line => {
                    const coordinates = line.split('   ').map(parseFloat);
                    if (coordinates.some(isNaN)) {
                        console.log('Skipping invalid data:', coordinates);
                        return null;
                    }
                    return new THREE.Vector3(coordinates[0], coordinates[1], coordinates[2]);
                }).filter(point => point !== null);

                const geometry = new THREE.BufferGeometry().setFromPoints(points);
                 //create a blue LineBasicMaterial
                const material = new THREE.LineBasicMaterial( { color: 0x0000ff } );
                const pointCloud = new THREE.Points(geometry, material);
                scene.add(pointCloud);


                // Beleuchtung hinzufügen (optional)
                const pointLight = new THREE.PointLight(0xffffff, 1);
                pointLight.position.set(0, 0, 10);
                scene.add(pointLight);
                // Füge die Punktwolke zur Szene hinzu
                
                 camera.position.z = 5;
                 camera.position.y = 1;
                 camera.rotation.x = (3.141 / 180) * -10; // 20 deg
                // Setze die Kamera-Position
                //camera.position.z = 50;

                // Rendere die Szene
                const animate = function () {
                    requestAnimationFrame(animate);
                    renderer.render(scene, camera);
                };

                animate();
            },
            undefined,
            (error) => {
                console.error('Fehler beim Laden der Datei', error);
            }
        );
        
    </script>

How to use Ngrx navigate function?

I came across navigation from NgRx. After reading the documentation my understanding is this effect will be triggered when the component is loaded. But unfortunately this effect is not getting triggered. I have tested with other effects & they are triggering as expected

export class RouterEffects {

  private actions$ = inject(Actions);
  private routerFacade: RouterFacade = inject(RouterFacade);
  private shellFacade: ShellFacade = inject(ShellFacade);
  private appService: AppService = inject(AppService);
  private router = inject(Router);

  getProfile$ = createEffect(() =>
    this.actions$.pipe(
      navigation(LoginComponent, {
        run: (a: ActivatedRouteSnapshot, ...slices): Observable < Action > | Action | void => {
          console.log('hello')
          return this.appService.getProfile()
            .pipe(map((profile) => ({
              type: 'Load',
              todo: profile
            })))
        },
        onError: (a: ActivatedRouteSnapshot, e: any) => {
          console.log(e)
          return null
        }
      })
    ));
}

The effect is also added to the standalone application config

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([httpInterceptor])),
    provideRouter(appRoutes),
    provideStore({
      router: routerReducer
    }),
    provideState({
      name: SHELL_FEATURE_KEY,
      reducer: shellReducer
    }),
    provideEffects([ShellEffects, RouterEffects]),
    provideRouterStore({
      //serializer:RouteSerializer
    }),
    provideStoreDevtools({
      // dev tool config
    })
  ],
};

My question is what I am doing wrong and how I can make navigate function work

Manually set canvas height in Storybook – Canvas is too low and doesn’t fit component

I have this component that I wanted to add to storybook. It worked, just the styling was off a little but I could fix that by adding the inline style position: absolute.
So now it looks like this:

const Template: any = (args: any): any => ({
  components: { Teaser },
  setup() {
    return { args };
  },
  template: '<teaser style="position: absolute" v-bind="args" :image="args.image"/>',
});

However, even though this fixes the style of the component, the canvas height in storybook where the component is displayed dropped to a minimum and most of it got cut off: image

I don’t want to set a higher canvas globally but only for this component and I already tried adding another style like height=400px, which has worked for me in similar cases but not in this one.

Any ideas how to fix this?

How to convert from SVG to base64 – js

THIS WORKS WITH CASE WHEN WE WANT TO ADD SVG FIL To PDF FILE WITH jsPDF

I spend a lot of time to find solution to add external svg file to generated PDF file with jsPDF package. Here is an example below. External svg file, simply a image which you get by url from some external server.

@params url - for this case i am considering for example 'https//www.enter-external-url-link-here.svg';

const convertSvgToBase64 = (url: string | null): string => {
  if (!url) {
    return '';
  }
  let result = null as string | ArrayBuffer | null;

  const xhr = new XMLHttpRequest();
  xhr.onload = () => {
    const reader = new FileReader();
    reader.onloadend = () => {
      // targeting existing img tag
      const img = document.getElementById('pdfCruiseLogo');
      if (reader.result) {
      // replacing src value with new value
        img?.setAttribute('src', reader.result.toString());
      // sending result back to img tag to be able to see new src value in the DOM
        result = reader.result;
      }
    };
    reader.readAsDataURL(xhr.response);
  };
  xhr.open('GET', url);
  xhr.responseType = 'blob';
  xhr.send();

  if (typeof result !== 'string') {
    return '';
  }

  return result;
};

convertSvgToBase64(url);


// this is how it looks img element
// in this case convertSvgToBase64 imported as a helper outside of the component
 <img
   id="pdfCruiseLogo"
   :src="convertSvgToBase64(voyageData.voyage.cruiseLineLogoUrl)"
   :alt="voyageData.voyage.cruiseLineName"
 />

converting plain old jquery and bootstrap too nuxt project

I have a html webpages styled with bootstrap and jquery, with a lot of other js that was included through script tags

I have to convert this to a nuxt project

I’ve been able to import all the styles into App.vue

i tried importing the js using dynamic imports in onMounted hook but it doesn’t seem to work

How can i get the value “i” to work outside for loop

How can I get i to work in habitButton.addEventListener

for (var i = 1; i <= 31; i++) {
    let numberElement = document.createElement("span");
    numberElement.classList.add("number");
      
    numberElement.textContent = i;
    numberBox.appendChild(numberElement);
 }

habitButton.addEventListener("click", function() {
    alert(numberBox)
    if (i === go) {
      numberElement.style.backgroundColor = "red";
    }
    habitButton.classList.toggle("fa-square-check")
    //habitButton.className = "fas fa-check";
});

I have tried declaring i outside the loop but it did not work.

How to delete a TODO item list from DOM when I click delete button using Javascript.?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TODO app</title>
</head>
 <script>

    function deleteDone(id) {
        console.log("Deleted todo ID:", id);
        // Delete the right todo from the list
        // You will need to give each todo an id, and that should be enough to remove it
      

    }
    function deleteTodo(id) {
        fetch("http://localhost:3000/todos/" + id, {
            method: "DELETE",
            headers: {
                "Content-Type": "application/json"
            }
        }).then(deleteDone)
    }


    function getTodoscallBack(data){
        console.log(data);
        var parentElement = document.getElementById("mainArea");
        // parentElement.innerHTML = JSON.stringify(data);
        for(var i=0;i<data.length;++i){
            var childElement = document.createElement("div");

            var grandChildren1 = document.createElement("span");
            grandChildren1.innerHTML= data[i].title;
            var grandChildren2 = document.createElement("span");
            grandChildren2.innerHTML= data[i].description;
            var grandChildren3 = document.createElement("button");
            grandChildren3.innerHTML="Delete";
            grandChildren3.setAttribute("onclick","deleteTodo("+data[i].id+")");
            
            childElement.appendChild(grandChildren1);
            childElement.appendChild(grandChildren2);
            childElement.appendChild(grandChildren3);
            
            parentElement.appendChild(childElement);

        }
        
    }
    function callbackFn2(res){
        res.json().then(getTodoscallBack);
    }
    function getData(){
        fetch("http://localhost:3000/todos",{
            method:"GET",
        }).then(callbackFn2);
            
    }

    getData();
    function parsedResponse(data){
        console.log(data);
        var parentElement = document.getElementById("mainArea");
        var childElement = document.createElement("div");

            var grandChildren1 = document.createElement("span");
            grandChildren1.innerHTML= data.title;
            var grandChildren2 = document.createElement("span");
            grandChildren2.innerHTML= data.description;
            var grandChildren3 = document.createElement("button");
            grandChildren3.innerHTML="Delete";
            
            childElement.appendChild(grandChildren1);
            childElement.appendChild(grandChildren2);
            childElement.appendChild(grandChildren3);
            
            parentElement.appendChild(childElement);
    }
    function callback(res){
        res.json().then(parsedResponse);
    }
  
      function onPress(){
        var  title = document.getElementById("title").value;
        var  description = document.getElementById("description").value;
        
        console.log(title,"n",description);

        fetch("http://localhost:3000/todos",{
            method:"POST",
            body: JSON.stringify({
                title: title,
                description:description
            }),
            headers: {
                "Content-Type": "application/json"
            }
        }).then(callback);
    }


 </script>
<body>
    
    Todo title
    <input type="text" id="title"></input>
    <br><br>
    Todo description
    <input type="text" id="description"></input>
    <br><br>
    <button onclick="onPress()">send todo</button>
    <div id="mainArea">

    </div>
</body>
</html>

In delete function of TODO item I want to delete a sepecific todo item with given id but I am having difficulty in selecting parent i.e. div tag in which I have stored the title description and delete button which I am adding by sending a request using javascript. Problem how to delete div tag of specific delete button having id using removeChild()? How to do that?

Bitbucket and SublimeMerge Compatibility Issue with WSL2 and Git Commits Loading

I use Docker along with WSL2, Bitbucket, and SublimeMerge. However, Bitbucket doesn’t support a virtual machine like WSL2, and that’s a problem because it doesn’t load the commits stored in Git. Loading the localhost takes a long time. Can you give me any solution?

I’ve tried opening VSCode through the local machine;
I searched for documentation on Bitbucket to try to solve it, but I couldn’t find anything about this issue.

vue-router redirects to login. Even with the unguarded paths it redirects me to the login

import {
  createRouter,
  createWebHistory
} from "vue-router";
import Home from "../views/Home.vue";
import PublicPage from "../views/public.vue";

const router = createRouter({
  history: createWebHistory(),
  routes: [{

      path: "/public",
      name: "PublicPage",
      component: PublicPage,
      meta: {
        requiresAuth: false, // No authentication required
        roles: [], // No specific roles required
      },

      path: "/",
      name: "Home",
      component: Home,
      props: true,
      meta: {
        requiresAuth: true,
        roles: ["Super Admin", "Admin", "Viewer"],
      },
    },
    {
      path: "/test",
      component: () => import("../views/Test.vue"),
      meta: {
        requiresAuth: true,
        roles: ["Super Admin", "Admin", "Viewer"],
      },
    },
    {
      path: "/map",
      component: () => import("../views/Map.vue"),
      meta: {
        requiresAuth: true,
        roles: ["Super Admin", "Admin", "Viewer"],
      },
    },
    {
      path: "/contact",
      component: () => import("../views/Contact.vue"),
      meta: {
        requiresAuth: true,
        roles: ["Super Admin", "Admin", "Viewer"],
      },
    },
    {
      path: "/users",
      component: () => import("../views/Users.vue"),
      meta: {
        requiresAuth: true,
        roles: ["Super Admin", "Admin"],
      },
    },
    {
      path: "/support",
      component: () => import("../views/Support.vue"),
      meta: {
        requiresAuth: true,
        roles: ["Super Admin", "Admin", "Viewer"],
      },
    },
    {
      path: "/settings",
      component: () => import("../views/Settings.vue"),
      meta: {
        requiresAuth: true,
        roles: ["Super Admin", "Admin"],
      },
    },
    {
      path: "/assets",
      component: () => import("../views/Assets.vue"),
      meta: {
        requiresAuth: true,
        roles: ["Super Admin", "Admin"],
      },
    },
    {
      path: "/alerts",
      component: () => import("../views/Alerts.vue"),
      meta: {
        requiresAuth: true,
        roles: ["Super Admin", "Admin"],
      },
    },
    {
      path: "/clients",
      component: () => import("../views/Clients.vue"),
      meta: {
        requiresAuth: true,
        roles: ["Super Admin", "Admin"], //only super admin have access to the company list
      },
    },
    {
      path: "/labels",
      component: () => import("../views/Labels.vue"),
      meta: {
        requiresAuth: true,
        roles: ["Super Admin", "Admin"],
      },
    },
    {
      path: "/login",
      name: "Login",
      component: () => import("../views/authentication/Login.vue"),
    },
    {
      path: "/register",
      name: "Register",
      component: () => import("../views/authentication/Activate.vue"),
    },
  ],
});

router.beforeEach((to, from, next) => {
  const token = localStorage.getItem("token");
  if (to.meta.requiresAuth && !token) {
    next({
      name: "Login"
    });
  } else if (token) {
    const userRole = localStorage.getItem("role"); // implement this function to get the user's role from the backend
    const authorizedRoles = to.meta.roles;
    if (authorizedRoles && authorizedRoles.length && authorizedRoles.includes(userRole)) {
      next();
    } else {
      next({
        name: "Home"
      }); // or redirect to a 403 page
    }
  } else {
    next();
  }
});

export default router;

Even if I am entering link as /public or /register which doesn’t need authentication it still redirects to the /login. It was working fine when i develope it first. Recently i updated my project by npm update. console.log(to.meta.requiresAuth) returns undefined and console.log(to.name) return login. There is no other place i have defined any redirect or rewrite rules for this project.

How can i manage only stores based on my clientId? Nestjs

My app used nestJs and mongoose, i have multiple ressources with relations on my client.

For this example i only take Client, and Store.
Each Store is related to a Client, so in my store i have the following data:

{
        "_id": "6582d34c0b52939c2d51402a",
        "clientId": "6556764d7bd208634371bf20",
        "name": "store Name",
}

My route for the two ressources are /clients and /clients/:id/stores.

import { Module } from '@nestjs/common'
import { RouterModule } from '@nestjs/core'

import { PublicClientModule } from '@framework/features/clients/public/client.module'
import { PrivateClientModule } from '@framework/features/clients/private/client.module'
import { PublicStoreModule } from '@framework/features/stores/public/store.module'
import { PrivateStoreModule } from '@framework/features/stores/private/store.module'

const router = [
  {
    path: 'public',
    children: [
      {
        path: '/clients',
        module: PublicClientModule,
        children: [
          {
            path: '/:id',
            children: [
              {
                path: '/stores',
                module: PublicStoreModule,
              },
            ],
          },
        ],
      },
    ],
  },
  {
    path: '',
    children: [
      {
        path: '/clients',
        module: PrivateClientModule,
        children: [
          {
            path: '/:id',
            children: [
              {
                path: '/stores',
                module: PrivateStoreModule,
              },
            ],
          },
        ],
      },
    ],
  },
]

@Module({
  imports: [
    RouterModule.register(router),
    PublicClientModule,
    PrivateClientModule,
    PublicStoreModule,
    PrivateStoreModule,
  ],
})
export class FrameworkRouterModule {}

I have others ressources like Stores related on the clients/:id/... too.

Inside my controller Store i can obviously check if the clientId is the one related too the store, but i will have to do it in each function, for each ressources … so it seems to me that is not the good way to do it.

There is for example my PrivateStoreController:

import { Controller, Res, Post, HttpStatus, Body, Put, Param, Delete } from '@nestjs/common'
import { Response } from 'express'

import { PrivateStoreService } from './store.service'
import { CreateStoreDto } from './dto/createStore.dto'
import { UpdateStoreDto } from './dto/updateStore.dto'

@Controller('')
export class PrivateStoreController {
  constructor(private readonly storeService: PrivateStoreService) {}

  @Post()
  async createStore(@Body() storeDto: CreateStoreDto, @Res() response: Response) {
    const store = await this.storeService.createStore(storeDto)
    return response.status(HttpStatus.CREATED).json(store)
  }

  @Put('/:id')
  async updateStore(@Param('id') id: string, @Body() storeDto: UpdateStoreDto, @Res() response: Response) {
    // I can check here if the client have access to this store
    const store = await this.storeService.updateStore(id, storeDto)
    return response.status(HttpStatus.OK).json(store)
  }

  @Delete('/:id')
  async deleteStore(@Param('id') id: string, @Res() response: Response) {
    const store = await this.storeService.deleteStore(id)
    return response.status(HttpStatus.OK).json(store)
  }
}

So, what is the common way to handle things only on the related scope (client in my case)?

WebRTC does not connect to the local subscriber

I am trying to create a screen demo between 2 conversation partners via WebRTC. But for some reason my connection has a status of “new”. I am using React as frontend and Express JS as signaling server. The demo is sent only by the user who initiated the call.
Here is my frontend code:

socket.on("add-iceCandidate", (data) => {
    peerConnections.current[data.clientID]?.addIceCandidate(
      new RTCIceCandidate(data.iceCandidate)
    );
  })

  socket.on('call', async (data) => {

    if (data.clientID in peerConnections) {
      return warn("Client already in peer connections list")
    }

    peerConnections.current[data.clientID] = new RTCPeerConnection();

peerConnections.current[data.clientID].onicecandidate = event => {
      if (event.candidate) {
        socket.emit("send-ice", {
          clientID: data.clientID,
          iceCandidate: event.candidate.candidate,
        });
      }
    }

    peerConnections.current[data.clientID].ontrack = (event) => {
      const [remoteStream] = event.streams;
      videoRef.current.srcObject = remoteStream;
    }

    localMediaStream.current?.getTracks().forEach(track => {
      peerConnections.current[data.clientID].addTrack(track, localMediaStream.current);
    });

    if (data.offer) {
      const offer = await peerConnections.current[data.clientID].createOffer();
      await peerConnections.current[data.clientID].setLocalDescription(offer);

      const roomOffer = {
        offer: {
          type: offer.type,
          sdp: offer.sdp
        }
      }

      socket.emit("add-offer", {
        clientID: data.clientID,
        offer: roomOffer
      })
    }

  })

socket.on('add-offer', async (data) => {
    await peerConnections.current[data.clientID]?.setRemoteDescription(new RTCSessionDescription(data.offer.offer))
    const answer = await peerConnections.current[data.clientID].createAnswer();
    await peerConnections.current[data.clientID].setLocalDescription(answer);

    const roomAnswer = {
      answer: {
        type: answer.type,
        sdp: answer.sdp
      }
    }

    socket.emit("add-answer", {
      clientID: data.clientID,
      answer: roomAnswer
    })
  })

  socket.on('add-answer', async (data) => {
    if (peerConnections.current[data.clientID]?.currentRemoteDescription === null) {
      const answer = new RTCSessionDescription(data.answer.answer)
      await peerConnections.current[data.clientID]?.setRemoteDescription(answer);
    }
  })

async function callFunction() {
    const mediaStream = await navigator.mediaDevices.getDisplayMedia({
      audio: true,
      video: { mediaSource: "screen" }
    })
    console.log("Media Stream", mediaStream);
    localMediaStream.current = mediaStream;
    localVideoRef.current.srcObject = localMediaStream.current;
    console.log("Call called");
    socket.emit('call', "5e94a33d-8133-43a0-adc3-3ef44adc2b3b");
  }

My signaling server code is:

io.on('connection', socket => {
    socket.on('join', (roomID) => {
        console.log(roomID);

        const { rooms } = socket;
        if (!Array.from(rooms).includes(roomID)) {
            socket.join(roomID);
            const rooms = Array.from(io.sockets.adapter.rooms).map(room => room[0]);
            io.emit('share-rooms', rooms.filter(room => validate(room)));
        }
    })

    socket.on('call', (roomID) => {
        let clients = Array.from(io.sockets.adapter.rooms).find(x => x[0] === roomID);
        if (clients === undefined)
            return;
        clients = clients[1];
        console.log("Clients", clients);
        clients = Array.from(clients).filter(clientID => clientID !== socket.id);
        clients.map((client) => {
            io.to(client).emit("call", {
                clientID: socket.id,
                offer: false
            });

            console.log(client);
            socket.emit("call", {
                clientID: client,
                offer: true
            })
        })
    })

    socket.on('add-offer', ({ clientID, offer }) => {
        console.log("Add offer emitted", clientID, offer);
        io.to(clientID).emit('add-offer', {
            offer: offer,
            clientID: socket.id
        });
    })

    socket.on('add-answer', (data) => {
        console.log(`Add answer`, socket.id, data);
        console.log("Rooms", Array.from(io.sockets.adapter.rooms))
        io.to(data.clientID).emit('add-answer', {
            clientID: socket.id,
            answer: data.answer
        });
        console.log(`Sending answer to ${data.clientID} from ${socket.id}`)
    })

    socket.on('join-room', (roomId, userId) => {
        socket.join(roomId)
        socket.to(roomId).broadcast.emit('user-connected', userId)

        socket.on('disconnect', () => {
            socket.to(roomId).broadcast.emit('user-disconnected', userId)
        })
    })

    socket.on("send-ice", (data) => {
        console.log("Send ice emitted", data);
        const clientID = data.clientID;
        console.log(io.sockets.adapter.rooms);
        io.to(clientID).emit('add-iceCandidate', {
            clientID: socket.id,
            iceCandidate: data.iceCandidate
        });
    })
})

According to the WebRTC Internals tab, my clients are successfully exchanging the offer and answer. Also icecadndidate. But the fields have the following statuses:
ICE connection state: new
Connection state: new
Signaling state: new => have-remote-offer => stable => have-remote-offer => stable
ICE Candidate pair: (not connected)

The call takes place between 2 browsers on the same computer.

What could be the error?

I tried different versions of the implementation with different expiration dates, but the result was always that I didn’t get the deleted image. Now, my implementation according to the documentation from the Webrtc website is as follows