AngularJS & Angular 18 hybrid application: How to inject AngularJS constants/values into Angular2?

I am trying to create an AngularJS and Angular hybrid app from a legacy AngularJS app, and I’ve managed to get it working to some extent. However, I am stuck at dependency injection of AngularJS contents into Angular components/services, specifically with AngularJS constants and values defined like this:

var app = angular.module("app", ['ngComponentRouter', ...]);
app.value("$routerRootComponent", "root");
app.constant("configuration", configuration);

I’ve tried following this guide(making AngularJS dependencies injectable to angular), but it only mentions services, no information about values and constants. Do they work in similar ways like services? How do I inject values/constants from AngularJS into Angular?

How to properly create chunks using splitChunks with Webpack 5

I am trying to split my chunks into main and components chunks. I want main to contain everything except components and then components chunk to hold everything from src/js/components. Right now this is including everything I want into main.bundle.hash.js and is properly excluding the src/js/components dir + all sub dirs BUT I don’t see a components.chunk.[contenthadsh].js chunk being created. It is excluded completely when running webpack analyze.

How do I fix this?

 optimization: {
                splitChunks: {
                    cacheGroups: {
                        main: {
                            test: /[\/]src[\/]js[\/](?!components[\/]).+/,
                            name: 'main',
                            chunks: 'all',
                            enforce: true,
                            priority: 10
                        },
                        components: {
                            test: /[\/]src[\/]js[\/]components[\/].+/,
                            name: 'components',
                            chunks: 'all',
                            enforce: true,
                            priority: 5
                        }
                    }
                },
                minimize: true,
                minimizer: [new TerserPlugin()]
            },
            output: {
                path: outputPath,
                filename: 'main.bundle.[contenthash].js',
                publicPath,
                chunkFilename: '[name].chunk.[contenthash].js',
            }

Is there a way to use playwright annotations to run a specific test step

I’m exploring options to modify my existing test so that certain steps execute only when I include a specific annotation/tag in Playwright. The test currently performs several assertions, and I want to extend it to add a few more without repeating the same actions. Specifically, I’m checking the previews of a message editor. I’d like to enhance it to also send the message, while still verifying the previews independently.

test('Test for some feature 1', async ({page}) => {
   test.step('Step 1', async() => {
     //Few assertions here
    });
  
  test.step('Step 2', async() => {
    //Few assertions here
    });
 
//This step should only run if I pass a certain annotation/tag
 test.step('Step 3', {tag: ['@featureX']}, async() => {
 //This should run only if I pass an annotation say @featureX
 });
});

SHA1 Hashing diffrenece between .net and Js

I am sha1 hashing in js like that:

Js(crypto-js’)

var requestString = generateRequestString(obj);
var hashSha1 =     CryptoJS.SHA1(test+apiKey+requestString);
var hashInBase64 =     CryptoJS.enc.Base64.stringify(hashSha1); 
return hashInBase64

I am try to sha1 hashing in .net but results are diffrent.

using var sha1 = SHA1.Create();  
byte[] hashBytes =   sha1.ComputeHash(Encoding.UTF8.GetBytes(test+apiKey+requestString);
string hashInBase64 = Convert.ToBase64String(hashBytes);

What can i do to getting same result from .net

Input fields created using React are not editable

I’m learning React and have created a two page application. The first presents the user with a list of customers from a database, with Add, Edit, Delete, View buttons, my ‘App.js’. The second presents a “dialog box” using a CustomerForm component, which is visible when one of four variables is set to true (isAdding, isEditing, isDeleting, isViewing).

CustomerForm.js presents fields from the Customer recor, as input boxes, and should allow the user to perform the appropriate operation on them. I’ve got as far as displaying the data when the user clicks edit, but the fields appear uneditable. The issue relates to the value attribute below:

value={customer != null ? customer[field.name] : ""}

When I omit this, the text is editable, however the customer record is not shown. The full listing of App.css, CustomerForm.css and CustomerForm.js follow:

App.css:

.App {
  text-align: center;
}

.App-logo {
  height: 40vmin;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

button {
  border: none; /* Remove the border */
  border-radius: 0; /* Make the corners square (0 radius) */
  box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.3); /* Shadow to the bottom-right */
  padding: 5px 10px; /* vert, horiz. Add some padding */
  background-color: darkgray; /* Button background color */
  color: #FFFFFF; /* Button text color */
  font-size: small; /* Font size */
  cursor: pointer; /* Show a pointer cursor on hover */
  outline: none; /* Remove the default outline on focus */
  margin: 4px 4px; /* very, horiz. Add horizontal space between buttons */
}

h1 {
  color: black;
  font-weight: bold;
  font-family: 'Verdana', 'Arial', 'Helvetica', sans-serif;
  font-size: x-large;
}

th {
  background-color: darkgray;
  color: white;
  font-weight: normal;
  font-family: 'Verdana', 'Arial', 'Helvetica', sans-serif;
  font-size: small; /* Font size */
  margin: 0 4px; /* Add horizontal space between buttons */
  text-align: left;
}

td {
  /* background-color: lightgray;*/
  font-family: 'Verdana', 'Arial', 'Helvetica', sans-serif;
  font-size: small;
  text-align: left;
}

.note {
  color: black;
  font-style: italic;
  font-family: 'Verdana', 'Arial', 'Helvetica', sans-serif;
  font-size: x-small;
}

.selectedCustomer {
  background-color: blue; /* Color for the highlighted row */
  color: white;
  cursor: pointer;
}

.customer {
  background-color: lightgray;
  color: black;
  cursor: pointer;
}

CustomerForm.css:

.modal-overlay { /* this is for a window-sized <div> that stops interaction with the items below it */
    position: fixed; /* the overlay stays in place regardless of scrolling */
    top: 0; /* top-left of overlay */
    left: 0;
    width: 100%; /* from top-left, this covers the entire window, no matter what is done to the window */
    height: 100%;
    background: rgba(0, 0, 0, 0.5); /* drop the visibility of everything behind by 50% */
    display: flex; /* These flexbox properties center the modal dialog horizontally and vertically within the overlay. The combination of justify-content: center; and align-items: center; ensures that the .modal-content (the dialog) is always in the middle of the screen, providing a focused area for user interaction. */
    justify-content: center; 
    align-items: center;
  }
  
.modal-content {
    background: white;
    padding: 20px;
    border-radius: 5px;
    width: auto;
    height: auto;
    max-width: 500px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
 }

 .buttonBar {
  text-align: right;
 }

 .editableInput {
  background-color: white;
 }

 .nonEditableInput {
  background-color: lightBlue;
 }

 td {
  padding-right: 10px; /* Adds padding to the right side of the cell */
}

CustomerForm.js:

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import FormModes from './FormModes';
import './CustomerForm.css'; // Include CSS for modal styling

const CustomerForm = ({ customer, mode, onClose }) => 
{
    const [formData, setFormData] = useState((customer != null) ? 
        customer : 
        {
            CustomerID: '',
            CustomerName: '',
            Address1: '',
            Address2: '',
            Address3: '',
            Town: '',
            Postcode: '',
            Country: '',
            PhoneNumber: '',
            CustomerID: '',
        });
    const [formMode, setFormMode] = useState(mode);
    const [error, setError] = useState(null);
    const formFields = // array of fields to show
    [
        {label:'Customer Name', type:'text', name:'CustomerName'},
        {label:'Address 1', type:'text', name:'Address1'},
        {label:'Address 2', type:'text', name:'Address2'},
        {label:'Address 3', type:'text', name:'Address3'},
        {label:'Town', type:'text', name:'Town'},
        {label:'Postcode', type:'text', name:'Postcode'},
        {label:'Country', type:'text', name:'Country'},
        {label:'Phone Number', type:'text', name:'PhoneNumber'},
    ];

    const buttonText = () => 
    {
        switch (formMode) 
        {
            case FormModes.Add: return "Add";
            case FormModes.Edit: return "Update";
            case FormModes.Delete: return "DELETE";
            case FormModes.View: return "Close";
            default: throw new Error("Unknown formMode: ${formMode}")
        }
    };

    const isReadOnly = () =>
    {
        return ((formMode==FormModes.Delete) || (formMode==FormModes.View));
    };
  
    const handleSubmit = async (event) => 
    {
        try
        {
            event.preventDefault(); // this stops the default submit action
            switch (formMode)
            {
                case FormModes.Add:
                    break;

                case FormModes.Edit:
                    const response = await axios.post('/api/customers', formData);
                    break;

                case FormModes.Delete:
                    break;

                case FormModes.View:
                    break;

                default:
                    throw new Error('Unknown form Mode ${formMode}.')
            
            }
            onClose();
        } catch (err) {
            setError(err.message);
        }
      
    };
  
    return (
      <div className="modal-overlay">
        <div className="modal-content">
          <h2>{formMode} Customer</h2>
          <form onSubmit={handleSubmit}>
            <table border="0">
                {formFields.map((field) =>
                    <tr key={field.CustomerID}>
                        <td padding-right="10px"><label>{field.label}</label></td>
                        <td><input type={field.type}
                        name={field.name}
                        value={(customer != null) ? customer[field.name] : ""}
                        placeholder={field.label}
                        readOnly={isReadOnly()}
                        className={isReadOnly() ? "nonEditableInput" : "editableInput"}
                        />
                        </td>
                    </tr>
                )}

            </table>

            <div className="buttonBar">
                <button type="submit">{buttonText()}</button>
                <button type="button" onClick={onClose}>Cancel</button>
                {error && <p>Error: {error}</p>}
            </div>
          </form>
        </div>
      </div>
    );
  };
  
  export default CustomerForm;

My question is, why does including this line:

value={customer != null ? customer[field.name] : ""}

lock the input field to that value? I simply want to set it when the field is shown and be editable.

“TypeError: Failed due to illegal value in property: 0” when using a Map object in JavaScript (with Apps Script)

I’m working on a Google Apps Script project bound to a Spreadsheet. For a certain functionality, I display a modal dialog as follows: Modal dialog. So, a series of selectors, variable in number, that are created and loaded dynamically depending on some data. Each selector is contained within a <li> list element of a <menu> unordered list: <menu id="categories"></menu>.

Upon clicking on the ‘Ok’ button, a certain function is called; here is the HTML element:

<!-- An 'Ok' button to continue. -->
<input type="button" value="Ok" onclick="endDialog()" />

Now, this function endDialog extracts the data from the selectors, and builds a Map<number, number> object and an array, which in turn are passed as arguments to a server-side Apps Script function.

And here comes the problem: the endDialog function does everything correctly until the google.script.run.applyColouring(rowPairs, colourAvailabilities);, at which point I get the uncaught error described in the title: Error

Here are some insights:

  • The problem lies within the Map object part (rowPairs), since when only using the array argument (colourAvailabilities) in the server-side function, everything works nicely.
  • The server-side function is never called: it never shows in the Apps Script ‘Executions’ tab. Moreover, whenever I run this server-side function on the Apps Script side directly, with the exact same parameters, it works as expected.
  • The Map object is correctly built, or at least that is what I deduce when I log it into the console: Map object
  • When I modify the code to use an Object instead of a Map, it suddenly works. So I can make it work, but at this point I’m just curious and I want to know what on earth is going on. Besides, a Map – instead of a plain Object – is exactly what I need, since (among others) I am dealing with numbers.
  • I wonder if the problem is the impossibility of passing a Map as a parameter to the server-side function, but the documentation doesn’t seem to suggest that:

Legal parameters and return values are JavaScript primitives like a Number, Boolean, String, or null, as well as JavaScript objects and arrays that are composed of primitives, objects and arrays. A form element within the page is also legal as a parameter, but it must be the function’s only parameter, and it is not legal as a return value. Requests fail if you attempt to pass a Date, Function, DOM element besides a form, or other prohibited type, including prohibited types inside objects or arrays. Objects that create circular references will also fail, and undefined fields within arrays become null.

  • I’m not sure anymore, but I would swear that everything worked fine two days ago when I tested the finalised thing. I’m not sure what I could have modified since then.

Here is the whole HTML-side function endDialog:

  /**
   * Calls a server-side function to apply the changes 
   * to the categories' colours and closes the dialog.
   * 
   * @return {void} Nothing.
   */
  function endDialog() {
    // Hide the error messages.
    document.getElementById("emptyColour").style = "display:none;";
    document.getElementById("repeatedColour").style = "display:none;";
    // Get the list of categories.
    const categories = Array.from(document.getElementById("categories").children);
    // Create a map to contain the list of category-colour pairs.
    const rowPairs = new Map();
    // Create an array to contain the column of boolean values
    // corresponding to the availabilities of each colour palette.
    // Initialise all colours as being available.
    const colourAvailabilities = Array.from({length:10}).map(() => [true]);
    // A variable to see if all colours have been selected properly.
    var validSelects = true;
    // A variable to see if all colours are different from each other.
    var uniqueSelects = true;
    // Initialise an index variable to go through the categories.
    var i=0;
    // While the colours are validly selected,
    // and unique, go through the categories.
    while (validSelects && uniqueSelects && i<categories.length) {
      // Get the corresponding row number,
      let catRow = Number(categories[i].value);
      // and its chosen colour's row number.
      let colourRow = Number(document.getElementById(i+1)
                                     .value
                                     .split(',')[0]);
      // Check if the selected option is a valid colour.
      validSelects = colourRow != 0;
      // If the selected option is a valid colour,
      if (validSelects) {
        // set the corresponding colour to being unavailable.
        colourAvailabilities[colourRow-6][0] = false;
        // Add the rows pair to the map.
        // Use the colour row number as the key, so as to easily check whether the
        // number of categories and the number of key-value pairs in the map are equal.
        // Indeed, if this colour has already been used, no new key-value pair will
        // be added, instead just updating the value associated to the colour's row key.
        rowPairs.set(colourRow, catRow);
        // Check if all selected colours are different from each other.
        uniqueSelects = rowPairs.size == i+1;
      }
      // Go to the next category.
      i++;
    }
    // If the user has made a valid and unique colour selection,
    if (validSelects && uniqueSelects) {
      console.log(rowPairs, colourAvailabilities);
      // call a server-side function to apply the changes.
      google.script.run.applyColouring(rowPairs, colourAvailabilities);
      // Close the dialog.
      google.script.host.close();
    }
    // If one of the selected colours is invalid,
    else if (!validSelects) {
      // display an error message.
      document.getElementById("emptyColour").style = "display:block;";
    }
    // If some colour has been selected more than once,
    else {
      // display an error message.
      document.getElementById("repeatedColour").style = "display:block;";
    }
  }

I don’t believe my sharing of the rest of the code is necessary, but if you want/need either the server-side function code, or the whole HTML file, I can post them.

If anyone knows what the problem is, I’m all ears! Thanks in advance.

Is there a way to get the nth sibling (element) of and element? (NOT the nth child of the parent element)

I have a grid with an unknown number of rows and a variable number of columns. I set the number of columns by declaring a constant.* I detect a key combo to move focus from an element in the grid whose position is unknown; it could be any of the grid’s child elements (images in this case). The idea is to move focus to the image either directly above or below.

To do this, I need to get the nth sibling element, either previous or next, from the focused element, where n is the number of columns.

I can’t use parentNode.querySelector(':nth-child[]') since I don’t know the relationship of the focused element to the starting index (nth-child[0].

*Since this is a web component, the constant is set by declaring an attribute on the component.

Is there a JavaScript method to handle such cases?

NGINX Proxy Pass Not Forwarding POST Requests to Node.js Backend

I’m working on a learning project that uses the following stack:

  • Frontend: React (hosted via NGINX 1.26.2 )
  • Backend: Node.js with Express, PostgreSQL database
  • Reverse Proxy: NGINX

My goal is to proxy API requests from the frontend through NGINX to my Node.js backend. Specifically, I’m submitting a contact form using a POST request i created.

Part of my frontend code which handles this on sumbit is this:

        try {
        const response = await fetch('http://localhost:5000/api/contact', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(formData),
        });

I’m using NGINX to serve my static files and proxy API requests to the Node.js backend, which runs on port 5000. Here’s my nginx.conf:

server {
listen 80;
server_name localhost;

# Serve static files for the React frontend
location / {
    root   C:/Users/witte/Documents/Projects/effectief.tech/frontend/build;
    index  index.html index.htm;
    try_files $uri /index.html;
}

# Proxy API requests to Node.js
location /api/ {
    proxy_pass http://localhost:5000;  # Proxy to backend
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

}

error_log logs/error.log debug;

}

Backend Setup:
In my server.js backend, I’m handling the /contact route like this:

app.post('/api/contact', async (req, res) => {

Issue:

When I submit the form, the API call gets registered in my backend and the data enters my database. However, I can’t find the post request in my access log of nginx.

All frontend related calls that use GET to retrieve images or other css do register in the access.log and my frontend is reachable via localhost after i build it so the “location /” part of my nginx.conf works.

So it seems that nginx is not correctly proxying requests to my backend based on the “location /api/” part of the config.

What am I missing to make this work?

Quicksort using Javascript

I’m trying to implement the quicksort algorithm using recursion in Javascript and while I’m return when I meet the base case it’s not happening. Please help me. Following is the code I’ve written.

function randomArray(){
  let array = [];
  let length = Math.floor(Math.random()*22);
  for(let i=0;i<length;i++){
    array.push(Math.floor(Math.random()*50));
  }
  return array;
} // IT RETURNS THE RANDOM ARRAY WITH RANDOM SERIES OF NUMBERS;


function swapper(array, indexNumber, bigNumber){
  let temp = array[bigNumber];
  if(bigNumber-indexNumber>1){
    array[bigNumber]=array[indexNumber+1];
    array[indexNumber+1]=array[indexNumber];
    array[indexNumber]=temp;
    indexNumber =  indexNumber+1;
  }else{
    array[bigNumber]=array[indexNumber];
    array[indexNumber]=temp;
    indexNumber = bigNumber;
  }
} // HELPER FUNCTION WHICH HELPS TO PUT THE ELEMENT IN CORRECT PLACE

let hull =0;

function quickSort(array, index, object, length){ //MAIN FUNCTION 
  
  if(object[index]!==undefined){
    hull++;
    if(hull<array.length){
    quickSort(array,hull, object,length);
    }else{
      console.log(array, "from the heaven", hull); //CONSOLES AFTER HULL IS GREATER THAN OR EQUAL TO LENGTH OF ARRAY BUT STILL CONSOLING EVEN IT'S GREATER THAN ARRAY'S LENGTH
      return array;
    }
    
  }else{
    for(let i=index+1;i<array.length;i++){
      if(array[i]<array[index]){
        swapper(array, index, i);
        index = index+1;
      }
    }
    object[index]=true;
  }
  quickSort(oneArray, hull, object, length);
}
console.log(quickSort(randomArray(),0,{},0));

I tried the base case, the code should return when the variable hull is greater than length of an array but the code running even after the hull increments length of an array. I don’t know what’s happening there please explain

Why doesn’t the infobox change when I first click left after clicking right a few times? (The same problem occurs when going from left to right)

nextBtn.addEventListener('click', () => {
    indexSlider++;
    imgSlider.style.transform = `rotate(${indexSlider * -90}deg)`;

    index++;
    if (index > imgFruits.length - 1) {
        index = 0;
    }

    updateActiveItems(1);
    updateInfo(index); 
});

prevBtn.addEventListener('click', () => {
    indexSlider--;
    imgSlider.style.transform = `rotate(${indexSlider * -90}deg)`;

    index--;
    if (index < 0) {
        index = imgFruits.length - 1;
    }

    updateActiveItems(-1);
    
    updateInfo(index);
});

function updateActiveItems(direction) {
    document.querySelector('.fruit.active').classList.remove('active');
    imgFruits[index].classList.add('active');

    document.querySelector('.bg.active').classList.remove('active');
    bgs[index].classList.add('active');

    infoSlider.style.transition = 'none';
    infoSlider.style.transform = 'translateY(0)';

    if (direction === 1) {
        infoSlider.appendChild(infoSlider.firstElementChild);
    } else {
        infoSlider.prepend(infoSlider.lastElementChild);
    }

    infoSlider.offsetHeight; // Force reflow
    infoSlider.style.transition = 'cubic-bezier(0.645, 0.045, 0.355, 1)';
    infoSlider.style.transform = direction === 1 ? 'translateY(-25%)' : 'translateY(25%)';

    infoBox.style.justifyContent = direction === 1 ? 'flex-start' : 'flex-end';
}

infoSlider.addEventListener('transitionend', () => {
    infoSlider.style.transition = 'none';
    infoSlider.style.transform = 'translateY(0)';

    setTimeout(() => {
        infoSlider.style.transition = 'cubic-bezier(0.645, 0.045, 0.355, 1)';
    }, 0);
});

initialize();

I just want a circle of changing fruit names. forward and backward. Arranging queque is the main problem. I also tried this:

nextBtn.addEventListener('click', () => {

    indexSlider++;
    imgSlider.style.transform = rotate(${indexSlider * -90}deg);

    index++;
    if (index > imgFruits.length - 1) {
        index = 0;
    }

    document.querySelector('.fruit.active').classList.remove('active');
    imgFruits[index].classList.add('active');

    document.querySelector('.bg.active').classList.remove('active');
    bgs[index].classList.add('active');

    if (direction == 1) {
        infoSlider.prepend(infoSlider.lastElementChild);
    }
    
    direction = -1;

    infoBox.style.justifyContent = 'flex-start';
    infoSlider.style.transform = 'translateY(-25%)';

});

prevBtn.addEventListener('click', () => {

    indexSlider--;
    imgSlider.style.transform = rotate(${indexSlider * -90}deg);

    index--;
    if (index < 0){
        index = imgFruits.length - 1;
    }

    document.querySelector('.fruit.active').classList.remove('active');
    imgFruits[index].classList.add('active');

    document.querySelector('.bg.active').classList.remove('active');
    bgs[index].classList.add('active');

    if (direction == -1) {
        infoSlider.appendChild(infoSlider.firstElementChild);
    }
    
    direction = 1;

    infoBox.style.justifyContent = 'flex-end';
    infoSlider.style.transform = 'translateY(25%)';

});

infoSlider.addEventListener('transitionend', () => {

    if (direction == -1) {
        infoSlider.appendChild(infoSlider.firstElementChild);
    }
    else if (direction == 1) {
        infoSlider.prepend(infoSlider.lastElementChild);
    }

    infoSlider.style.transition = 'none';
    infoSlider.style.transform = 'translateY(0)';

    setTimeout(() => {
        infoSlider.style.transition = 'cubic-bezier(0.645, 0.045, 0.355, 1)';
    })

})

This one starts good but stops at 3rd fruit and previous button doesn’t work properly.

const infoData = [
    { title: 'Blueberry', description: 'Blueberries are sweet and nutritious.' },
    { title: 'Orange', description: 'Oranges are rich in vitamin C.' },
    { title: 'Green Apple', description: 'Green apples are tart and crisp.' },
    { title: 'Strawberry', description: 'Strawberries are juicy and delicious.' },
];

I tried these kind of solutions but I did not like these style and animation. My current style in css is good but js part doesn’t work well.

Stream sent via FFMPEG (NodeJS) to RTMP (YouTube) not being received

I am writing a very basic chrome extension that captures and sends video stream to a nodeJS server, which in turns sends it to Youtube live server.

Here is my implementation of the backend which receives data via WebRTC and send to YT using FFMPEG:

const express = require('express');
const cors = require('cors');
const { RTCPeerConnection, RTCSessionDescription } = require('@roamhq/wrtc');
const { spawn } = require('child_process');

const app = express();
app.use(express.json());
app.use(cors());

app.post('/webrtc', async (req, res) => {
  const peerConnection = new RTCPeerConnection();

  // Start ffmpeg process for streaming
  const ffmpeg = spawn('ffmpeg', [
    '-f', 'flv',
    '-i', 'pipe:0',
    '-c:v', 'libx264',
    '-preset', 'veryfast',
    '-maxrate', '3000k',
    '-bufsize', '6000k',
    '-pix_fmt', 'yuv420p',
    '-g', '50',
    '-f', 'flv',
    'rtmp://a.rtmp.youtube.com/live2/MY_KEY'
  ]);

  ffmpeg.on('error', (err) => {
    console.error('FFmpeg error:', err);
  });

  ffmpeg.stderr.on('data', (data) => {
    console.error('FFmpeg stderr:', data.toString());
  });

  ffmpeg.stdout.on('data', (data) => {
    console.log('FFmpeg stdout:', data.toString());
  });

  // Handle incoming tracks
  peerConnection.ontrack = (event) => {
    console.log('Track received:', event.track.kind);
    const track = event.track;

    // Stream the incoming track to FFmpeg
    track.onunmute = () => {
      console.log('Track unmuted:', track.kind);
      const reader = track.createReadStream();
      reader.on('data', (chunk) => {
        console.log('Forwarding chunk to FFmpeg:', chunk.length);
        ffmpeg.stdin.write(chunk);
      });
      reader.on('end', () => {
        console.log('Stream ended');
        ffmpeg.stdin.end();
      });
    };

    track.onmute = () => {
      console.log('Track muted:', track.kind);
    };
  };

  // Set the remote description (offer) received from the client
  await peerConnection.setRemoteDescription(new RTCSessionDescription(req.body.sdp));

  // Create an answer and send it back to the client
  const answer = await peerConnection.createAnswer();
  await peerConnection.setLocalDescription(answer);

  res.json({ sdp: peerConnection.localDescription });
});

app.listen(3000, () => {
  console.log('WebRTC to RTMP server running on port 3000');
});

This is the output I get, but nothing gets sent to YouTube:

FFmpeg stderr: ffmpeg version 7.0.2 Copyright (c) 2000-2024 the FFmpeg developers
  built with Apple clang version 15.0.0 (clang-1500.3.9.4)

FFmpeg stderr:   configuration: --prefix=/opt/homebrew/Cellar/ffmpeg/7.0.2_1 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags='-Wl,-ld_classic' --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libaribb24 --enable-libbluray --enable-libdav1d --enable-libharfbuzz --enable-libjxl --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libssh --enable-libsvtav1 --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox --enable-audiotoolbox --enable-neon

FFmpeg stderr:   libavutil      59.  8.100 / 59.  8.100
  libavcodec     61.  3.100 / 61.  3.100
  libavformat    61.  1.100 / 61.  1.100
  libavdevice    61.  1.100 / 61.  1.100

FFmpeg stderr:   libavfilter    10.  1.100 / 10.  1.100
  libswscale      8.  1.100 /  8.  1.100
  libswresample   5.  1.100 /  5.  1.100
  libpostproc    58.  1.100 / 58.  1.100

I do not understand what I am doing wrong. Any help would be appreciated.


Optionally Here’s the frontend code from the extension, which (to me) appears to be recording and sending the capture:

popup.js & popup.html

document.addEventListener('DOMContentLoaded', () => {
  document.getElementById('openCapturePage').addEventListener('click', () => {
    chrome.tabs.create({
      url: chrome.runtime.getURL('capture.html')
    });
  });
});
<!DOCTYPE html>
<html lang="en">

<head>
  <title>StreamSavvy Popup</title>
  <script src="popup.js"></script>
</head>

<body>
  <h2>StreamSavvy</h2>
  <button id="openCapturePage">Open Capture Page</button>
</body>

</html>

capture.js & capture.html

let peerConnection;

async function startStreaming() {
  try {
    const stream = await navigator.mediaDevices.getDisplayMedia({
      video: {
        cursor: "always"
      },
      audio: false
    });

    peerConnection = new RTCPeerConnection({
      iceServers: [{
        urls: 'stun:stun.l.google.com:19302'
      }]
    });

    stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));

    const offer = await peerConnection.createOffer();
    await peerConnection.setLocalDescription(offer);

    const response = await fetch('http://localhost:3000/webrtc', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        sdp: peerConnection.localDescription
      })
    });

    const {
      sdp
    } = await response.json();
    await peerConnection.setRemoteDescription(new RTCSessionDescription(sdp));

    console.log("Streaming to server via WebRTC...");
  } catch (error) {
    console.error("Error starting streaming:", error.name, error.message);
  }
}

async function stopStreaming() {
  if (peerConnection) {
    // Stop all media tracks
    peerConnection.getSenders().forEach(sender => {
      if (sender.track) {
        sender.track.stop();
      }
    });

    // Close the peer connection
    peerConnection.close();
    peerConnection = null;
    console.log("Streaming stopped");
  }
}

document.addEventListener('DOMContentLoaded', () => {
  document.getElementById('startCapture').addEventListener('click', startStreaming);
  document.getElementById('stopCapture').addEventListener('click', stopStreaming);
});
<!DOCTYPE html>
<html lang="en">

<head>
  <title>StreamSavvy Capture</title>
  <script src="capture.js"></script>
</head>

<body>
  <h2>StreamSavvy Capture</h2>
  <button id="startCapture">Start Capture</button>
  <button id="stopCapture">Stop Capture</button>
</body>

</html>

background.js (service worker)

chrome.runtime.onInstalled.addListener(() => {
  console.log("StreamSavvy Extension Installed");
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'startStreaming') {
    chrome.tabs.create({
      url: chrome.runtime.getURL('capture.html')
    });
    sendResponse({
      status: 'streaming'
    });
  } else if (message.type === 'stopStreaming') {
    chrome.tabs.query({
      url: chrome.runtime.getURL('capture.html')
    }, (tabs) => {
      if (tabs.length > 0) {
        chrome.tabs.sendMessage(tabs[0].id, {
          type: 'stopStreaming'
        });
        sendResponse({
          status: 'stopped'
        });
      }
    });
  }
  return true; // Keep the message channel open for sendResponse
});

Passing a prop from parent component to a child component in vue does not work

I currently have a small Vue project with the CompositionAPI where I now have a problem.
I have one parent component and two child components. A child component is there to display all users (where there is also the option to delete and edit the user). The other child component is there to edit the user.

The flow goes something like this: The user presses edit user, then a method is triggered in the parent component, where the ID of the user to be edited is also passed and then the ID should be passed as prop to the second child component (where an API request is then made to load and insert all user data).
This all works as it should, but the ID of the user in the parent component is still the correct one and then no longer in the child component (it is always undefined).

I have tried everything with v-bind and ‘:’, but I don’t know what else to do. The funny thing is that if I just enter a number like 10 in :edit-user-id then everything works perfectly, but only until I want to use the variable again. I hope someone can help me. The code is here:

Userlistview.vue

<script setup>
  const emit = defineEmits(['update-user']);

  function pressEdit(id) {
    emit('update-user', id);
  }

  function fetchAllUsers(){
    // all User get fetched here
  }
</script>

<template>
  <button class="button" v-on:click="pressEdit(user.userid)">Edit</button>
</template>

Adminview.vue

<script setup>
  let isEditActive = ref(false);
  let editUserId = ref();

  function userUpdateDone(){
    isEditActive.value = false;
    userlistBinding.value.fetchAllUsers();
  }

  function userUpdatePress(id) {
    editUserId.value = id;
    isEditActive.value = true;
  }
</script>

<template>
 <Userlistview ref="userlistBinding" @update-user="userUpdatePress($event)"/>

 <Usereditview v-if="isEditActive" :edit-user-id="editUserId.value" @update-user= "userUpdateDone()"/>
</template>

Usereditview.vue

<script setup>
  const props = defineProps({editUserId: Number});
  const emit = defineEmits(['update-user']);

  function loadUser() {
    // here get the user loaded with the specific id from the props
  }

  function updateUser() {
    // here gets the user updated -> if the respone is ok then this line of code will be executed
    emit('update-user');
  }
</script>

How to mock class implementation in vitest

We have existing code that’s structured similar to following which I want to test. The challenging part here is the fact that instance of Two is created at module root level in Three.ts file. This makes mocks not work. I also want to use mockImplementation inside test case because I want to manipulate the return value of runOne from test to test.

// One.ts

export class One {
  runOne() {
    return "hello";
  }
}
// Two.ts

import { One } from "./One";

export class Two {
  private one: One = new One();

  runTwo() {
    console.log(this.one);
    return console.log(this.one.runOne());
  }
}
// Three.ts

import { Two } from "./Two";

const two: Two = new Two();

export const run = () => {
  two.runTwo();
};
// main.test.ts

import { describe, Mock, test, vi } from "vitest";
import { run } from "./Three";
import { One } from "./One";

vi.mock("./One", async () => {
  return {
    One: vi.fn(),
  };
});

describe("suite", () => {
  test("test", async () => {
    const MOne = One as unknown as Mock;

    MOne.mockImplementation(() => {
      return { runOne: () => "world" };
    });

    run();
  });
});

Output:

spy {}

TypeError: this.one.runOne is not a function
 ❯ Two.runTwo __test__/Two.ts:8:33
      6|   runTwo() {
      7|     console.log(this.one);
      8|     return console.log(this.one.runOne());
       |                                 ^
      9|   }
     10| }

I want to have two test scripts where runOne returns two different values in the same test file.

describe('suite', () => {
  test('first', () => {
    // mock runOne to return "first"
    // should print "first"
    run()
  })

  test('second', () => {
    // mock runOne to return "two"
    // should print "two"
    run()
  })
})

Firefox browser does not display pdf with version 1.7 [closed]

Please, I would like to know how to resolve this issue.

The external user uploaded a PDF file of version 1.7 to the system, but our system does not display this PDF in Firefox, it only displays a blank page, in Chrome and Edge the file appears correctly.

Does anyone know what it could be? And how can I resolve this issue, so that this incompatibility is resolved in Firefox?

Do I have to change or add something related to JavaScript in the .tpl?

Thank you

I identified the PDF versions, but I don’t know where to act to resolve this impasse.

Timer in React Component Keeps Increasing State of the timerRef a useRef variable on Re-mount Despite Cleanup

I am developing a multiplayer quiz game using React. The game includes a CountdownTimer component that tracks the time left for answering each question and displays it using a circular countdown UI. However, I have encountered an issue where the timer’s internal reference (timerRef) continues to accumulate state and does not clean up properly when the component unmounts and remounts. This behavior leads to performance issues and potential memory leaks.

I am using the following code for the CountdownTimer component:

Here is a codesandbox link: https://codesandbox.io/p/devbox/7jwt9h

/* eslint-disable react/prop-types */
import { useState, useEffect, useRef, useCallback } from "react";
import { useGameContext } from "../context/GameContext";

const CountdownTimer = ({
  onTimeUp,
  question,
  isLeaderboardTrue,
  leaderboardTime,
}) => {
  const { timeLeft, setTimeLeft, settings } = useGameContext();
  const [isActive, setIsActive] = useState(false);
  const timerRef = useRef(null);
  const hasCalledTimeUp = useRef(false);

  const calculateRemainingTime = useCallback(() => {
    if (isLeaderboardTrue) {
      return leaderboardTime;
    }
    const remainingTime = Math.max(
      0,
      Math.floor((question.endTime - Date.now()) / 1000)
    );
    return remainingTime;
  }, [question, leaderboardTime, isLeaderboardTrue]);

  const startTimer = useCallback(() => {
    if (timerRef.current) {
      console.log(`Timer already started, ${timerRef.current}`);
      return;
    }

    timerRef.current = setInterval(() => {
      setTimeLeft((prevSeconds) => {
        if (prevSeconds > 0) {
          console.log(`Updating time: ${prevSeconds - 1}`);
          return prevSeconds - 1;
        } else {
          clearInterval(timerRef.current);
          console.log("Clearing timer:", timerRef.current);
          timerRef.current = null;
          if (!hasCalledTimeUp.current) {
            hasCalledTimeUp.current = true;
            onTimeUp();
          }
          return 0;
        }
      });
    }, 1000);
    console.log("Timer started:", timerRef.current);
  }, [setTimeLeft, onTimeUp]);

  useEffect(() => {
    if (isActive) {
      startTimer();
    }

    return () => {
      if (timerRef.current) {
        console.log(
          "Cleaning up timer before starting a new one:",
          timerRef.current
        );
        clearInterval(timerRef.current);
        timerRef.current = null;
      }
    };
  }, [isActive, startTimer]);

  useEffect(() => {
    const remainingTime = calculateRemainingTime();
    console.log(`Calculating remaining time: ${remainingTime}`);

    setTimeLeft(remainingTime);
    hasCalledTimeUp.current = false;
    setIsActive(true);

    // Cleanup timer on component unmount or when dependencies change
    return () => {
      setIsActive(false);
      if (timerRef.current) {
        clearInterval(timerRef.current);
        console.log("Cleaning up timer on unmount:", timerRef.current);
        timerRef.current = null;
      }
    };
  }, [
    isLeaderboardTrue,
    calculateRemainingTime,
    leaderboardTime,
    settings,
    setTimeLeft,
    question,
  ]);

  const calculateCircleDashArray = () => {
    const radius = 45;
    const circumference = 2 * Math.PI * radius;
    const totalTime = isLeaderboardTrue ? leaderboardTime : question.timeLimit;
    const percentage = timeLeft / totalTime;
    return `${circumference * percentage} ${circumference}`;
  };

  return (
    <div className="relative flex items-center justify-center p-10">
      <svg className="w-20 h-20" viewBox="0 0 100 100">
        <circle
          className="text-gray-300"
          strokeWidth="5"
          stroke="currentColor"
          fill="transparent"
          r="45"
          cx="50"
          cy="50"
        />
        <circle
          className="text-purple-500"
          strokeWidth="5"
          strokeDasharray={calculateCircleDashArray()}
          strokeLinecap="round"
          stroke="currentColor"
          fill="transparent"
          r="45"
          cx="50"
          cy="50"
          transform="rotate(-90 50 50)"
        />
      </svg>
      <div className="absolute text-2xl text-purple-500">{timeLeft}</div>
    </div>
  );
};
export default CountdownTimer;

QuestionPage.jsx, I import the countdown timer in this page

import { useCallback } from "react";
import CountdownTimer from "../../components/CountdownTimer";
import Question from "../../components/questionComponents/Question";
import { useGameContext } from "../../context/GameContext";
import { useNavigate, useOutletContext, useParams } from "react-router-dom";

function QuestionPage() {
  const [questionData] = useOutletContext();
  const {
    scores,
    currentQuestionIndex,
    totalQuestions,
    setShowResults,
    setShowCorrectAnswer,
    isLeaderboard,
  } = useGameContext();
  const navigate = useNavigate();
  const { gameId } = useParams();

  const updateStateAndNavigate = useCallback(async () => {
    setShowResults(true);
    setShowCorrectAnswer(true);

    // Ensure state updates are applied before navigating
    await new Promise((resolve) => setTimeout(resolve, 0));

    navigate(`/game/${gameId}/results`);
  }, [setShowResults, setShowCorrectAnswer, gameId, navigate]);

  const handleTimeUp = useCallback(async () => {
    try {
      console.log("object");
      await updateStateAndNavigate();
    } catch (error) {
      console.error("Failed to handle time up:", error);
    }
  }, [updateStateAndNavigate]);

  return (
    <>
      {!isLeaderboard && (
        <>
          <CountdownTimer onTimeUp={handleTimeUp} question={questionData} />
          <div className="absolute top-20 left-4 text-2xl text-purple-500">
            Score: {scores}
          </div>
          <div className="absolute top-20 right-4 text-2xl text-purple-500">
            {currentQuestionIndex}/{totalQuestions}
          </div>

          <Question
            question={questionData.question}
            // totalQuestions={totalQuestions}
            isFirstQuestion={currentQuestionIndex === 1}
          />
        </>
      )}
    </>
  );
}

export default QuestionPage;

LeaderBoardPage.jsx, I also import it on this leaderboard page.

import { useCallback } from "react";
import CountdownTimer from "../../components/CountdownTimer";
import Leaderboard from "../../components/LeadersBoard.jsx";
import { useGameContext } from "../../context/GameContext";
import { useWebSocketContext } from "../../context/WebSocketContext";
import { useNavigate, useParams } from "react-router-dom";

function LeadersBoardPage() {
  const navigate = useNavigate();
  const { gameId } = useParams();
  const {
    setIsLeaderboard,
    setShowResults,
    updateScore,
    setUserAnswer,
    setCurrentQuestionIndex,
    totalQuestions,
    currentQuestionIndex,
  } = useGameContext();
  const { sendMessage } = useWebSocketContext();

  const handleTimeUp = useCallback(async () => {
    setIsLeaderboard(false);
    setUserAnswer("");
    setShowResults(false);
    updateScore();

    const nextIndex = currentQuestionIndex + 1;

    if (nextIndex < totalQuestions) {
      setCurrentQuestionIndex(nextIndex);
      sendMessage(JSON.stringify({ type: "requestNextQuestion", gameId }));
      navigate(`/game/${gameId}`);
    } else {
      setCurrentQuestionIndex(0);
      navigate(`/game/${gameId}/podium`);
    }
  }, [
    setIsLeaderboard,
    setShowResults,
    navigate,
    setCurrentQuestionIndex,
    setUserAnswer,
    gameId,
    totalQuestions,
    currentQuestionIndex,
    updateScore,
    sendMessage,
  ]);

  return (
    <>
      <CountdownTimer
        onTimeUp={handleTimeUp}
        isLeaderboardTrue={true}
        leaderboardTime={7}
      />
      <Leaderboard />
    </>
  );
}

export default LeadersBoardPage;

The Problem:

  1. The timerRef continues to increase even after the component unmounts and remounts.

  2. I am trying to ensure that the timer is cleaned up properly when the component unmounts, so there are no state updates or memory leaks when the component is re-rendered.

  3. I’ve implemented a cleanup function in the useEffect to clear the interval, but it seems to be ineffective.
    What I’ve Tried:

    1. Used useRef to track the timer and avoid unnecessary re-renders.

    2. Added cleanup in the useEffect to clear the timer when the component unmounts.

    3. Checked the logs — the clearInterval function runs, but the timer keeps accumulating.

My Question:

How can I ensure that the timer is properly cleaned up and reset when the component unmounts and remounts without causing state to accumulate or memory issues?

Any help is appreciated. Thank you!

These are the game state from devtools component and console logs

1 State:
2 Ref:1467
3 Ref:false
4 Callback:ƒ () {}
5 Callback:ƒ () {}
6 Effect:ƒ () {}
7 Effect:ƒ () {}
And here are the console logs
 Updating time: 5
CountdownTimer.jsx:52 Updating time: 5
CountdownTimer.jsx:52 Updating time: 4
CountdownTimer.jsx:52 Updating time: 4
CountdownTimer.jsx:52 Updating time: 3
CountdownTimer.jsx:52 Updating time: 3
CountdownTimer.jsx:52 Updating time: 2
CountdownTimer.jsx:52 Updating time: 2
CountdownTimer.jsx:52 Updating time: 1
CountdownTimer.jsx:52 Updating time: 1
CountdownTimer.jsx:52 Updating time: 0
CountdownTimer.jsx:52 Updating time: 0
GamePage.jsx:30 {type: 'answer', data: {…}}
CountdownTimer.jsx:56 Clearing timer: 79
QuestionPage.jsx:32 object
CountdownTimer.jsx:25 Calculating remaining time: 7
CountdownTimer.jsx:25 Calculating remaining time: 7
CountdownTimer.jsx:66 Timer started: 88
CountdownTimer.jsx:52 Updating time: 6
CountdownTimer.jsx:52 Updating time: 6
CountdownTimer.jsx:75 Cleaning up timer: 88
CountdownTimer.jsx:66 Timer started: 90
CountdownTimer.jsx:52 Updating time: 5
CountdownTimer.jsx:52 Updating time: 5
CountdownTimer.jsx:75 Cleaning up timer: 90
CountdownTimer.jsx:66 Timer started: 91
CountdownTimer.jsx:52 Updating time: 4
CountdownTimer.jsx:52 Updating time: 4
CountdownTimer.jsx:75 Cleaning up timer: 91
CountdownTimer.jsx:66 Timer started: 92
CountdownTimer.jsx:52 Updating time: 3
CountdownTimer.jsx:52 Updating time: 3
CountdownTimer.jsx:75 Cleaning up timer: 92
CountdownTimer.jsx:66 Timer started: 93
CountdownTimer.jsx:52 Updating time: 2
CountdownTimer.jsx:52 Updating time: 2
CountdownTimer.jsx:75 Cleaning up timer: 93
CountdownTimer.jsx:66 Timer started: 94
CountdownTimer.jsx:52 Updating time: 1
CountdownTimer.jsx:52 Updating time: 1
CountdownTimer.jsx:75 Cleaning up timer: 94
CountdownTimer.jsx:66 Timer started: 95
CountdownTimer.jsx:52 Updating time: 0
CountdownTimer.jsx:52 Updating time: 0
CountdownTimer.jsx:75 Cleaning up timer: 95
CountdownTimer.jsx:66 Timer started: 96
CountdownTimer.jsx:56 Clearing timer: 96
CountdownTimer.jsx:56 Clearing timer: null
CountdownTimer.jsx:66 Timer started: 98
CountdownTimer.jsx:35 Cleaning up timer on unmount: 98
GamePage.jsx:30 {type: 'leaderboard', data: Array(1)}
GamePage.jsx:30 {type: 'leaderboard', data: Array(1)}
GamePage.jsx:30 {type: 'question', data: {…}}
CountdownTimer.jsx:25 Calculating remaining time: 9
CountdownTimer.jsx:25 Calculating remaining time: 9
CountdownTimer.jsx:66 Timer started: 103
CountdownTimer.jsx:52 Updating time: 8
CountdownTimer.jsx:52 Updating time: 8
CountdownTimer.jsx:52 Updating time: 7
CountdownTimer.jsx:52 Updating time: 7
CountdownTimer.jsx:52 Updating time: 6
CountdownTimer.jsx:52 Updating time: 6
CountdownTimer.jsx:52 Updating time: 5
CountdownTimer.jsx:52 Updating time: 5
CountdownTimer.jsx:52 Updating time: 4
CountdownTimer.jsx:52 Updating time: 4
CountdownTimer.jsx:52 Updating time: 3
CountdownTimer.jsx:52 Updating time: 3
CountdownTimer.jsx:52 Updating time: 2
CountdownTimer.jsx:52 Updating time: 2
CountdownTimer.jsx:52 Updating time: 1
CountdownTimer.jsx:52 Updating time: 1
CountdownTimer.jsx:52 Updating time: 0
CountdownTimer.jsx:52 Updating time: 0
GamePage.jsx:30 {type: 'answer', data: {…}}
CountdownTimer.jsx:56 Clearing timer: 103
QuestionPage.jsx:32 object
CountdownTimer.jsx:25 Calculating remaining time: 7
CountdownTimer.jsx:25 Calculating remaining time: 7
CountdownTimer.jsx:66 Timer started: 112
CountdownTimer.jsx:52 Updating time: 6
CountdownTimer.jsx:52 Updating time: 6
CountdownTimer.jsx:75 Cleaning up timer: 112
CountdownTimer.jsx:66 Timer started: 114
CountdownTimer.jsx:52 Updating time: 5
CountdownTimer.jsx:52 Updating time: 5
CountdownTimer.jsx:75 Cleaning up timer: 114
CountdownTimer.jsx:66 Timer started: 115
CountdownTimer.jsx:52 Updating time: 4
CountdownTimer.jsx:52 Updating time: 4
CountdownTimer.jsx:75 Cleaning up timer: 115
CountdownTimer.jsx:66 Timer started: 116
CountdownTimer.jsx:52 Updating time: 3
CountdownTimer.jsx:52 Updating time: 3
CountdownTimer.jsx:75 Cleaning up timer: 116
CountdownTimer.jsx:66 Timer started: 117
CountdownTimer.jsx:52 Updating time: 2
CountdownTimer.jsx:52 Updating time: 2
CountdownTimer.jsx:75 Cleaning up timer: 117
CountdownTimer.jsx:66 Timer started: 121
CountdownTimer.jsx:52 Updating time: 1
CountdownTimer.jsx:52 Updating time: 1
CountdownTimer.jsx:75 Cleaning up timer: 121
CountdownTimer.jsx:66 Timer started: 124
CountdownTimer.jsx:52 Updating time: 0
CountdownTimer.jsx:52 Updating time: 0
CountdownTimer.jsx:75 Cleaning up timer: 124
CountdownTimer.jsx:66 Timer started: 128
CountdownTimer.jsx:56 Clearing timer: 128
CountdownTimer.jsx:56 Clearing timer: null
CountdownTimer.jsx:66 Timer started: 133
CountdownTimer.jsx:35 Cleaning up timer on unmount: 133
GamePage.jsx:30 {type: 'leaderboard', data: Array(1)}
GamePage.jsx:30 {type: 'leaderboard', data: Array(1)}
GamePage.jsx:30 {type: 'question', data: {…}}
CountdownTimer.jsx:25 Calculating remaining time: 9
CountdownTimer.jsx:25 Calculating remaining time: 9
CountdownTimer.jsx:66 Timer started: 138
backendManager.js:1 Could not find Fiber with id "306"
overrideMethod @ console.js:288
findCurrentFiberUsingSlowPathById @ renderer.js:2960
inspectElementRaw @ renderer.js:3235
inspectElement @ renderer.js:3738
(anonymous) @ agent.js:420
emit @ events.js:37
(anonymous) @ bridge.js:292
listener @ backendManager.js:1
postMessage
handleMessageFromDevtools @ proxy.js:1
Show 8 more frames
Show lessUnderstand this warning
CountdownTimer.jsx:52 Updating time: 8