Refresh Token Rotation on Next.js 15

What I want

I am developing an app in Next.js 15, and for authorization, I want to use JWT and Refresh Token, following this process:

  • When a user visits a restricted page, the system checks whether they have an access_token cookie (a JWT with a validity of 7 minutes), which contains their userId, and verifies if it is valid.
    • If valid, it recreates this JWT, renewing its validity for another 7 minutes, sends it back to the client as a cookie, retrieves the user session (for subsequent validation) e continue.
    • If invalid, it checks whether they have a refresh_token cookie (just a token with a validity of 7 days) and:
      • If not, redirects them to the login page.
      • If they do, it validates in the database (in my case, KV or Redis) whether this token exists, retrieves the userId, recreates both the access_token (JWT) and the refresh_token, updates the cookies, retrieves the user session, and continue.

The problem

The issue I’m facing is with this Refresh Token Rotation:
When accessing the page (e.g., app/dashboard/page.js), I can read the cookies for verification, but I cannot update the cookies because they can only be “written” via Server Actions or Route Handlers.

  • I’ve tried creating a Server Component (UpdateAccessCookies) to place on the page that would solely be responsible for sending the cookies, but doing this causes the system to enter a loop.
  • I’ve also tried using an API, but when making a POST request, the system didn’t receive the cookies—and I’m not sure if this would be the best option. I’d prefer to find a way to use Server Actions.

Here are some example codes

// Register Page
import { getCurrentSession } from '@/services/session';
import { RegisterForm } from './Form';

export default async function Register() {
  const { session, data } = await getCurrentSession();

  return (
    <>
      <h1>Register</h1>
      <RegisterForm/>
    </>
  );
}
//session.js
'use server'

//...imports...

export async function getCurrentSession() {
  const accessToken = await getCookie('access_token');
  let data = {};

  if (accessToken) {
    const jwtValidation = await validarJWT(accessToken);
    if (jwtValidation) {
      data.userId = jwtValidation.userId;
      data.jwt = {
        token: await createJWT({ userId: jwtValidation.userId }),
        expiraEm: JWT_EXPIRES_IN,
      };
    }
  }

  if (!data.userId) {
    const refreshToken = await getCookie('refresh_token');
    if (!refreshToken) {
      return { session: null, data: null };
    }

    data.userId = await getStorageAdapterData('refresh_token', refreshToken);
    if (!data.userId) {
      return { session: null, data: null }; 
    }

    data.jwt = {
      token: await createJWT({ userId: data.userId }),
      expiraEm: JWT_EXPIRES_IN,
    };
    data.refreshToken = {
      token: await createRefreshToken(data.userId, refreshToken),
      expiraEm: SESSION_EXPIRES_IN,
    };
  }

  const session = await getStorageAdapterData('session', data.userId);
  if (!session) {
    return { session: null, data: null };
  }

  return { session, data };
}

As I mentioned, I have already tried updating the cookies:

  • On the page, right after getCurrentSession (would be ideal)
  • In session.js, right after renewing the tokens
  • In a Refresh-cookies component (like the code below)
// Register Page 2
import { getCurrentSession } from '@/services/session';
import { RegisterForm } from './Form';
import RefreshCookies from '@/components/refresh-cookies';

export default async function Register() {
  const { session, data } = await getCurrentSession();

  return (
    <>
      <h1>Register</h1>
      <RegisterForm/>
      <RefreshCookies
        jwt={data?.jwt}
        refreshToken={data?.refreshToken}
      />
    </>
  );
}
//Refresh-cookies.js
'use client';

import { useEffect, useState } from 'react';
import { createCookie } from '@/services/cookies';

async function updateCookies(jwt, refreshToken) {
  if (jwt) {
    await createCookie('access_token', jwt.token, jwt.expires);
  }
  if (refreshToken) {
    await createCookie(
      'refresh_token',
      refreshToken.token,
      refreshToken.expires
    );
  }
}

export default function RefreshCookies({ jwt, refreshToken }) {
  const [Updatedcookies, setUpdatedCookies] = useState(false);

  useEffect(() => {
    async function update() {
      if (!Updatedcookies && (jwt || refreshToken)) {
        await updateCookies(jwt, refreshToken);
      }
    }

    update();
  }, [jwt, refreshToken, Updatedcookies]);
}

What would you suggest?

Note: I don’t want to use an external library. I also don’t want to perform verification using middleware because it would check on every request to restricted pages, which would significantly increase request time.

`querySelector` traversal by order of the number of child nodes?

In the MDN documentation, it says

The matching is done using depth-first pre-order traversal of the document’s nodes starting with the first element in the document’s markup and iterating through sequential nodes by order of the number of child nodes.

But I can’t find any other information about this behavior on the Internet. And when asking ChatGPT, it says this wording must be an error.

I’m totally new to javascript and don’t know who is right, but I do think an official reference such as the MDN can’t be wrong, so can someone explains why it uses this specific order?

Canvas animation shows differently on Safari?

I’ve been unable to find someone discussing the same issue so far, but I have a canvas animation that displays on the background of my webpage that displays perfectly on firefox but is extremely simplified on Safari.

The animation is supposed to be subtle, blurry orbs that morph into one another and change colour while floating around the screen in a jellyfish-like rhythm; this works perfectly on Firefox. However, on Safari the animation just shows as different coloured circles bouncing around the screen.

I was wondering why this happens and if it’s possible to fix it?

Here is a link to the page in question:
https://k1ssch4se.neocities.org/

Here is the code for the animation:

const rand = function(min, max) {
  return Math.random() * ( max - min ) + min;
}

let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

window.addEventListener('resize', function() {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  ctx = canvas.getContext('2d');
  ctx.globalCompositeOperation = 'lighter';
});
let backgroundColors = [ '#000', '#000' ];
let colors = [
  [ '#69ffa5', "#d0ff26" ],
  [ '#00eeff', '#27e49b' ], 
  [ '#ffc800', '#f2ff00' ],
  [ '#00e1ff', '#26ffed' ],
  [ '#3cff00', '#0346ff' ]
];
let count = 15;
let blur = [ 15, 20 ];
let radius = [ 10, 80 ];

ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.globalCompositeOperation = 'lighter';

let grd = ctx.createLinearGradient(0, canvas.height, canvas.width, 0);
grd.addColorStop(0, backgroundColors[0]);
grd.addColorStop(1, backgroundColors[1]);
ctx.fillStyle = grd;
ctx.fillRect(0, 0, canvas.width, canvas.height);

let items = [];

while(count--) {
    let thisRadius = rand( radius[0], radius[1] );
    let thisBlur = rand( blur[0], blur[1] );
    let x = rand( -100, canvas.width + 100 );
    let y = rand( -100, canvas.height + 100 );
    let colorIndex = Math.floor(rand(0, 299) / 100);
    let colorOne = colors[colorIndex][0];
    let colorTwo = colors[colorIndex][1];
    
    ctx.beginPath();
    ctx.filter = `blur(${thisBlur}px)`;
    let grd = ctx.createLinearGradient(x - thisRadius / 2, y - thisRadius / 2, x + thisRadius, y + thisRadius);
  
    grd.addColorStop(0, colorOne);
    grd.addColorStop(1, colorTwo);
    ctx.fillStyle = grd;
    ctx.fill();
    ctx.arc( x, y, thisRadius, 0, Math.PI * 2 );
    ctx.closePath();
    
    let directionX = Math.round(rand(-99, 99) / 100);
    let directionY = Math.round(rand(-99, 99) / 100);
  
    items.push({
      x: x,
      y: y,
      blur: thisBlur,
      radius: thisRadius,
      initialXDirection: directionX,
      initialYDirection: directionY,
      initialBlurDirection: directionX,
      colorOne: colorOne,
      colorTwo: colorTwo,
      gradient: [ x - thisRadius / 2, y - thisRadius / 2, x + thisRadius, y + thisRadius ],
    });
}


function changeCanvas(timestamp) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  let adjX = 2;
  let adjY = 2;
  let adjBlur = 1;
  items.forEach(function(item) {
    
      if(item.x + (item.initialXDirection * adjX) >= canvas.width && item.initialXDirection !== 0 || item.x + (item.initialXDirection * adjX) <= 0 && item.initialXDirection !== 0) {
        item.initialXDirection = item.initialXDirection * -1;
      }
      if(item.y + (item.initialYDirection * adjY) >= canvas.height && item.initialYDirection !== 0 || item.y + (item.initialYDirection * adjY) <= 0 && item.initialYDirection !== 0) {
        item.initialYDirection = item.initialYDirection * -1;
      }
      
      if(item.blur + (item.initialBlurDirection * adjBlur) >= radius[1] && item.initialBlurDirection !== 0 || item.blur + (item.initialBlurDirection * adjBlur) <= radius[0] && item.initialBlurDirection !== 0) {
        item.initialBlurDirection *= -1;
      }
    
      item.x += (item.initialXDirection * adjX);
      item.y += (item.initialYDirection * adjY);
      item.blur += (item.initialBlurDirection * adjBlur);
      ctx.beginPath();
      ctx.filter = `blur(${item.blur}px)`;
      let grd = ctx.createLinearGradient(item.gradient[0], item.gradient[1], item.gradient[2], item.gradient[3]);
      grd.addColorStop(0, item.colorOne);
      grd.addColorStop(1, item.colorTwo);
      ctx.fillStyle = grd;
      ctx.arc( item.x, item.y, item.radius, 0, Math.PI * 2 );
      ctx.fill();
      ctx.closePath();
    
  });
  window.requestAnimationFrame(changeCanvas);
  
}

window.requestAnimationFrame(changeCanvas);
canvas {
  bottom: 0;
  left: 0;
  position: fixed;
  right: 0;
  top: 0;
}
<canvas id="canvas" style="opacity: 0.5;" ></canvas>

I’m Having Trouble with a Google Apps Script Array to Update Sheet Cells

I need to update the values of cells. Whatever I try, it either coughs up an error, or only updates the value in the script temporarily, but doesn’t update the cell.

Here is my array:

// A looping function that goes down the rows, starting with row 2, and adds +1 to +5 depending on the priority of the meal.
// Stops and continues the main script once the array finishes going through all filled rows.
function iterateThroughRows() {
  var activePage = SpreadsheetApp.getActive().getSheetByName("Meal List");
  var data = activePage.getDataRange().getValues();
  data.shift(); // Skips the first row header.

  data.forEach(function (row) { 
    console.log("Logger Row -----------------------------------------------------------------------------------------------------"); // DEBUGGING LOGS
    Logger.log(row); // DEBUGGING LOGS

    var mealSkip = row[2];
    var mealPickedChance = row[3];
    var mealPriority = row[4];

    console.log("mealSkip = " + mealSkip); // DEBUGGING LOGS
    console.log("mealPickedChance = " + mealPickedChance); // DEBUGGING LOGS
    console.log("mealPriority = " + mealPriority); // DEBUGGING LOGS

    if (mealSkip === true) {
      //mealSkip.push(false);
      //mealSkip.setValue(false);
      //mealSkip = [false];
      //data.setValue[2](false);
      console.log("Set mealSkip to equal FALSE = " + mealSkip); // DEBUGGING LOGS
    }
    else {
      //mealPickedChance.setValue(mealPickedChance+mealPriority);
      console.log("Update mealPickedChance = " + mealPickedChance); // DEBUGGING LOGS
    }
  });
}

I want to make two changes:

  • If mealSkip equals TRUE, set it to FALSE and continue to the next row.
  • Else, take the int value from mealPriority and add/sum it to int value of mealPickedChance, then continue to the next row.

How do I get an array to update the value of a cell here?

Why is CodeSandbox not showing the correct preview to how my index.css is styled?

I am just recently learning React, and I wanted to change the background of the body, so I switched out the image pdf file. The background was showing perfectly up until I changed the file.

I pushed the commit for the change to GitHub, but the preview still doesn’t look correct.

This is the CodeSandbox link:
https://codesandbox.io/p/github/alissa-17/stickyNoteUi/main

Hide a div generated dynamically via SCSS

I am working on hiding certain elements of an Angular page. I cannot change the source code, however I am able to upload an SCSS stylesheet which is applied to the page. The page I am working on has 2 dynamically generated buttons. The buttons look like
enter image description here

and the html/angular for the two buttons is as follows, from dev tools inspect.

<div ng-class="['gen-home-content', isSingleTile ? 'single-tile' : null]" class="ng-scope gen-home-content">
    <div ng-repeat="tile in tiles" class="ng-scope">
        <landing-page-app-tile tile-data="tile" class="ng-isolate-scope">
            <button ng-click="goApp(tileData.url)" class="gen-app-tile">
                <div class="tile-icon">
                    <img ng-src="../assets/personal_info.svg" aria-hidden="true" src="../assets/personal_info.svg">
                </div>
                <div class="tile-content">
                    <div class="tile-title ng-binding">
                        Personal Information
                    </div>
                    <div class="tile-desc ng-binding">
                        View and update your biographical and demographic information.
                    </div>
                </div>
            </button>
        </landing-page-app-tile>
    </div>
    <div ng-repeat="tile in tiles" class="ng-scope">
        <landing-page-app-tile tile-data="tile" class="ng-isolate-scope">
            <button ng-click="goApp(tileData.url)" class="gen-app-tile">
                <div class="tile-icon">
                    <img ng-src="../assets/direct_deposit.svg" aria-hidden="true" src="../assets/direct_deposit.svg">
                </div>
                <div class="tile-content">
                    <div class="tile-title ng-binding">
                        Direct Deposit
                    </div>
                    <div class="tile-desc ng-binding">
                        Create, view and update your direct deposit allocation(s).
                    </div>
                </div>
            </button>
        </landing-page-app-tile>
    </div>
</div>

My first thought was that since the containing div has the ng-class isSingleTile ? ternary expression but not the single-tile class, that the expression must be evaluating to null, and if I could apply the isSingleTile class to the div with the gen-home-content class, perhaps that would force a single-tile view (which I assume would be helpful to me). Using scss I tried these options

.gen-home-content div:not(:first-child) {
    display: none !important;
}

and

.gen-home-content {
    @extend .single-tile;
}

but these two styles did not resolve the issue. I do not know exactly what the single-tile class is, I tried searching for it but it was not in the existing stylesheet. I think it is related to mat-grid-list but I could not find specific documentation on it.

Any advice on how to hide the direct deposit button is appreciated.

Issue in Google OAuth flow when using PKCE

I am trying to make a drive client for my college project and I am not very experienced in it. That’s why I am building the application using electron so that I can develop the application as if it was a website, but it will work as a Desktop Client. I want it to be completely client-side which means no interaction with any web server other that google for authentication purposes.

Right now, I am facing an issue with the PKCE flow in Google OAuth2.0
I am getting the following error:

GaxiosError: invalid_grant
    at Gaxios._request (MY_PROJECT_PATHdrive-encrypt_(Pre-production)node_modulesgaxiosbuildsrcgaxios.js:142:23)       
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async OAuth2Client.getTokenAsync (MY_PROJECT_PATHdrive-encrypt_(Pre-production)node_modulesgoogle-auth-librarybuildsrcauthoauth2client.js:158:21)
    at async file:///MY_PROJECT_PATH/drive-encrypt_(Pre-production)/index.js:154:35 {
  config: {
    retry: true,
    retryConfig: {
      httpMethodsToRetry: [Array],
      currentRetryAttempt: 0,
      retry: 3,
      noResponseRetries: 2,
      retryDelayMultiplier: 2,
      timeOfFirstRequest: 1743095188566,
      totalTimeout: 9007199254740991,
      maxRetryDelay: 9007199254740991,
      statusCodesToRetry: [Array]
    },
    method: 'POST',
    url: 'https://oauth2.googleapis.com/token',
    data: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'User-Agent': 'google-api-nodejs-client/9.14.0',
      'x-goog-api-client': 'gl-node/20.10.0'
    },
    paramsSerializer: [Function: paramsSerializer],
    body: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
    validateStatus: [Function: validateStatus],
    responseType: 'unknown',
    errorRedactor: [Function: defaultErrorRedactor]
  },
  response: {
    config: {
      retry: true,
      retryConfig: [Object],
      method: 'POST',
      url: 'https://oauth2.googleapis.com/token',
      data: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
      headers: [Object],
      paramsSerializer: [Function: paramsSerializer],
      body: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
      validateStatus: [Function: validateStatus],
      responseType: 'unknown',
      errorRedactor: [Function: defaultErrorRedactor]
    },
    data: {
      error: 'invalid_grant',
      error_description: 'Missing code verifier.'
    },
    headers: {
      'alt-svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000',
      'cache-control': 'no-cache, no-store, max-age=0, must-revalidate',
      'content-encoding': 'gzip',
      'content-type': 'application/json; charset=utf-8',
      date: 'Thu, 27 Mar 2025 17:06:28 GMT',
      expires: 'Mon, 01 Jan 1990 00:00:00 GMT',
      pragma: 'no-cache',
      server: 'scaffolding on HTTPServer2',
      'transfer-encoding': 'chunked',
      vary: 'Origin, X-Origin, Referer',
      'x-content-type-options': 'nosniff',
      'x-frame-options': 'SAMEORIGIN',
      'x-xss-protection': '0'
    },
    status: 400,
    statusText: 'Bad Request',
    request: { responseURL: 'https://oauth2.googleapis.com/token' }
  },
  error: undefined,
  status: 400,
  [Symbol(gaxios-gaxios-error)]: '6.7.1'
}

The main part to focus here is:-

data: {
error: ‘invalid_grant’,
error_description: ‘Missing code verifier.’
},

I have tried and checked the following things:

  1. I am using Google’s Desktop Client ID (So, I only have the default redirect URI)
  2. I have also verified that the generated code verifier matches the code challenge using online PKCE Code Generator
  3. I have also checked that the authorization code is only used once, and never repeated again.

This the my relevant server code (index.js):

import dotenv from "dotenv"
dotenv.config()

import fs from "fs"
import path from "path"
import { fileURLToPath } from "url"
import { google } from "googleapis"
import express from "express"
import bodyParser from "body-parser"
import mime from "mime-types"
import multer from "multer"
import cors from "cors"
import crypto from "node:crypto"
import { Readable } from "stream"
const winregModule = await import('winreg');
const Registry = winregModule.default;
import { generateAndStoreKEK, retrieveKEK, encryptData, decryptData } from "./frontend/scripts/keyManagement.js"
import { recoverPassword, generateAndStorePasswordRecovery, recoveryDataExists } from './frontend/scripts/passwordRecovery.js';
import dns from "dns"
import session from 'express-session';

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

const app = express()
app.use(bodyParser.json())
app.use(
  cors({
    origin: "http://localhost:8000",
    credentials: true, // Allow cookies to be sent with requests
  }),
)

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: true,
  cookie: {
    secure: process.env.NODE_ENV === "production",
    httpOnly: true,
    sameSite: "lax",
  }
}));

const isDev = process.env.NODE_ENV === "development"

const frontendDirectoryPath = isDev ? path.join(__dirname, "../frontend") : path.join(__dirname, "frontend")

// Serve static files from the frontend directory
app.use(express.static(frontendDirectoryPath))

// Serve login.html at the root URL
app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "frontend/login.html"))
})

const oauth2Client = new google.auth.OAuth2(
  process.env.CLIENT_ID,
  process.env.NODE_ENV === "production" ? "" : process.env.CLIENT_SECRET,
  "http://localhost:8000/google/redirect"
);


let tokens

try {
  const tokenData = fs.readFileSync(path.join(__dirname, "tokens.json"), "utf8")
  tokens = JSON.parse(tokenData)
  oauth2Client.setCredentials(tokens)
} catch (err) {
  console.log("No tokens file found. User will need to authenticate.")
}

const PORT = process.env.PORT || 8000

const USER_PREFERENCES_FILE = path.join(__dirname, "userPreferences.json")

function generateCodeVerifier() {
  return crypto.randomBytes(32).toString('base64url');
}

function generateCodeChallenge(codeVerifier) {
  const hash = crypto.createHash("sha256").update(codeVerifier).digest("base64");
  return hash
    .replace(/+/g, "-") // Replace '+' with '-'
    .replace(///g, "_") // Replace '/' with '_'
    .replace(/=+$/, ""); // Remove '=' padding
}

app.get("/auth/google", (req, res) => {
  const pkceVerifier = generateCodeVerifier();
  req.session.pkceCodeVerifier = pkceVerifier; // Store it in the session
  console.log("The pkce code verifier is: ", req.session.pkceCodeVerifier);
  const codeChallenge = generateCodeChallenge(pkceVerifier);

  console.log("Code Challenge:", codeChallenge);
  console.log("Code Verifier:", pkceVerifier);
  
  const authUrl = oauth2Client.generateAuthUrl({
    access_type: "offline",
    scope: [
      "https://www.googleapis.com/auth/userinfo.profile",
      "https://www.googleapis.com/auth/userinfo.email",
      "https://www.googleapis.com/auth/contacts.readonly",
      "https://www.googleapis.com/auth/drive"
    ],
    code_challenge: codeChallenge,
    code_challenge_method: "S256",
  });
  console.log("Generated Auth URL: ", authUrl);
  res.redirect(authUrl);
});

app.get("/google/redirect", async (req, res) => {
  try {
    const { code } = req.query;
    const pkceVerifier = req.session.pkceCodeVerifier;
    console.log("PKCE Code Verifier in redirect: ", pkceVerifier);
    if (!pkceVerifier) {
      throw new Error("PKCE code verifier not found in session");
    }

    console.log("Payload to getToken:", {
      code,
      code_verifier: pkceVerifier,
    });
    const { tokens: newTokens } = await oauth2Client.getToken({
      code,
      code_verifier: pkceVerifier,
    });
    oauth2Client.setCredentials(newTokens)
    tokens = newTokens

    // Save tokens to file
    fs.writeFileSync(path.join(__dirname, "tokens.json"), JSON.stringify(newTokens))

    // Set a cookie to indicate the user is authenticated
    res.cookie("authenticated", "true", { httpOnly: true, secure: true, sameSite: "Lax" })
    req.session.pkceCodeVerifier = null;

    // Check if a password has been set in the registry
    const passwordSet = await isPasswordSet()
    console.log("Is password set?", passwordSet)

    if (!passwordSet) {
      console.log("Redirecting to password creation page")
      res.redirect("/password-creation.html")
    } else {
      console.log("Redirecting to password verification page")
      res.redirect("/password-verification.html")
    }
  } catch (err) {
    console.error("Error in /google/redirect:", err)
    res.redirect("/?error=" + encodeURIComponent("Authentication failed. Please try again."))
  }
})

Sorry in advance for the poor code formatting and if I am doing something extremely stupid here.

Let me know if you need anything else for context. And Thanks in advance

JavaScript- requestAnimationFrame() slows down and then stops when using translate with rotate

const box = document.querySelector('.box');
let id = undefined;

function animateTest() {
  const style = window.getComputedStyle(box);
  const matrix = new DOMMatrixReadOnly(style.transform);
  const translateY = matrix.m42 + 2;
  box.style.transform = `rotate(12deg) translateY(${translateY}px)`;
  id = requestAnimationFrame(() => animateTest());
}
id = requestAnimationFrame(() => animateTest());

let button = document.querySelector('.button');
button.addEventListener('click', function() {
  cancelAnimationFrame(id);
  box.style.transform = `rotate(12deg) translateY(0px)`;
  id = requestAnimationFrame(() => animateTest());
}, false);
.box {
  margin-left: 300px;
  width: 10px;
  height: 10px;
  background-color: black;
  transform: rotate(15deg);
}
<button class='button'>rerun</button>
<div class="box"></div>

Here is jsfiddle demonstrating the problem:-

jsfiddle

Reload or save to rerun animation.

I am trying to move a box 12 degrees straight using translateY but when I add rotate to make it go that direction, the animation runs for a while and then it stops moving.

I think the requestAnimationFrame is called but the translateY property stops working.

If i remove the rotate(..) it works indefinitely as normal.

Why it slows and and stops when using rotate and how to make it not do that? thank you.

More simplified example-

My Html:

<div class="box">
 
</div>

My CSS:

.box {
  margin-left: 300px;
  width: 10px;
  height: 10px;
  background-color: black;
  transform: rotate(15deg);
}

My Script:

let box = document.querySelector('.box');
function animate() {
  const style = window.getComputedStyle(box);
  const matrix = new DOMMatrixReadOnly(style.transform);
  const translateY = matrix.m42 + 2;
  box.style.transform = `rotate(12deg) translateY(${translateY}px)`;
  requestAnimationFrame(() => animate());
}
requestAnimationFrame(() => animate());

How to Use Codemod with TS-Morph?

I want to use Codemod with TS-Morph to refactor TypeScript code, but I couldn’t find any example code demonstrating how to do this.

I found this article from Codemod stating that TS-Morph is supported, but it doesn’t provide a basic example.

How can I use Codemod with TS-Morph to perform a simple transformation, like renaming a variable or modifying a function signature?

An example of a basic setup would be very helpful.

How can I dynamically display and update form fields in Symfony EasyAdmin?

I’m working on a Symfony 7 project with EasyAdmin, and I’m trying to allow users to build article pages with different types of content, such as text blocks, image blocks, blocks with links, etc. To achieve this, I want to dynamically modify the form using the Symfony documentation on dynamic form modification: Symfony Documentation.

Here’s what I’ve done so far:

In my ArticleCrudController, I’m using a CollectionField to handle adding and removing blocks:

CollectionField::new('blocks', 'Blocs')
    ->renderExpanded(true)
    ->setEntryIsComplex()
    ->setEntryType(BlockType::class),

In the BlockType.php file, I add EventListeners to adjust the fields based on the selected type (text, link, etc.):

$builder
    ->add('type', ChoiceType::class, [
        'choices' => [
            'Text' => 'text',
            'Link' => 'link',
        ],
    ]);

$formModifier = function (FormInterface $form, $data = null) {
    if (is_array($data) && $data['type'] === 'text') {
        $form->add('text', TextareaType::class);
    } elseif (is_array($data) && $data['type'] === 'link') {
        $form->add('url', UrlType::class);
    }
};

$builder->addEventListener(
    FormEvents::PRE_SET_DATA,
    function (FormEvent $event) use ($formModifier) {
        $data = $event->getData();
        $formModifier($event->getForm(), $data);
    }
);

$builder->get('type')->addEventListener(
    FormEvents::POST_SUBMIT,
    function (FormEvent $event) use ($formModifier) {
        $type = $event->getForm()->getData();
        $formModifier($event->getForm()->getParent(), $type);
    }
);

$builder->setAction($options['action']);

I’m also adding some JavaScript inspired by the documentation to handle the form modification on the client side. Here’s the JS code I’m using to listen for changes and update the form:

document.addEventListener('DOMContentLoaded', function() {
    document.querySelector('.btn.btn-link.field-collection-add-button').addEventListener('click', function() {
        setTimeout(() => {
            const form_select_type = document.getElementById('Article_blocks_0_type');
            if (form_select_type) {
                const form = document.getElementById('new-Article-form');
                const block_0 = document.getElementById('Article_blocks_0');

                const updateForm = async (data, url, method) => {
                    const req = await fetch(url, {
                        method: method,
                        body: data,
                        headers: {
                            'Content-Type': 'application/x-www-form-urlencoded',
                            'charset': 'utf-8'
                        }
                    });

                    const text = await req.text();
                    return text;
                };

                const parseTextToHtml = (text) => {
                    const parser = new DOMParser();
                    const html = parser.parseFromString(text, 'text/html');
                    return html;
                };

                const changeOptions = async (e) => {
                    const requestBody = e.target.getAttribute('name') + '=' + e.target.value;
                    const updateFormResponse = await updateForm(requestBody, form.getAttribute('action'), form.getAttribute('method'));
                    const html = parseTextToHtml(updateFormResponse);

                    const new_block_0 = html.getElementById('Article_blocks_0');
                    block_0.innerHTML = new_block_0.innerHTML;
                };

                form_select_type.addEventListener('change', (e) => changeOptions(e));
            }
        }, 500);
    });
});

The problem I’m facing is that, currently, when I change the value of the select dropdown, the new fields don’t appear as expected. This is due to the errors that show up in the console. Specifically,
the 405 Method Not Allowed error
and the
TypeError: Cannot read properties of null (reading 'innerHTML').

I tried to manually force the choice of the method, but it didn’t work.

I also tried to follow the JavaScript example from this post: (Dynamic form modification for CollectionField in EasyAdmin) since the author was trying to do the same thing as me, but I don’t fully understand the code and it didn’t work either.

As a workaround, I’ve displayed all possible fields for the block (such as text, link, etc.) without worrying about the selected type, and made every fields not required. Then, I hide the fields that don’t match the selected type. While this approach works initially, it’s not an ideal solution, and it leads to issues with maintainability and performance. I really want to find a proper solution where only the relevant field for the selected block type is shown, making the form more optimized and dynamic.

Has anyone encountered a similar issue or implemented a dynamic form modification like this before? If anyone has any suggestions or can help me resolve the errors , I’d greatly appreciate it!

Web Components: set properties before or after ConnectedCallBack

I have been using Web Components for quite some time but now and then I keep running into some issues that I can’t explain.

Some context:

  • Attributes are used to pass text variables into a web component.
  • Properties can be used to pass complex variables into a web component.
  • A web component has a lifecycle method called ConnectedCallBack() that is called when our element gets added to the DOM.

What I am trying to do:

  • I created two web components: parent-element and child-element
  • Both have a property that you can use to pass in an object holding a text property that I want to display on screen; for these properties, I created a getter and setter method.
  • Each component has a render() method responsible for creating some new HTML elements holding the property value and adding them to the DOM; these methods only produce output if the property is set.

I added some example code to demonstrate this. Mind the output that is generated in the browser but also the console log demonstrating the order in which the various methods are called on both components.

Please run the code snippets in “Full page” as you might not see the full browser output when you run them inline.

<html>
<head></head>
<body>
    <app id="app"></app>
    <script>
        customElements.define('parent-element', class extends HTMLElement {
            set parentProperty(value) {
                console.log('PARENT: SET PROPERTY');
                this._parentProperty = value;
                this.render();
            }

            get parentProperty() {
                return this._parentProperty;
            }

            constructor() {
                super();
                console.log('parent: constructor');
            }

            connectedCallback() {
                console.log('parent: connectedCallback');
                this.render();
            }

            render() {
                console.log(`parent: render ${(this.parentProperty ? 'with' : 'no')} property`);
                if (this.parentProperty) {
                    let templateEl = document.createElement("template");
                    templateEl.innerHTML = `<p>${this.parentProperty.value}</p><child-element id="child"></child-element>`;
                    this.appendChild(templateEl.content.cloneNode(true));

                    let childEl = this.querySelector('#child');
                    childEl.childProperty = { value: 'child text' };
                }
            }
        });

        customElements.define('child-element', class extends HTMLElement {
            set childProperty(value) {
                console.log('CHILD: SET PROPERTY');
                this._childProperty = value;
                this.render();
            }

            get childProperty() {
                return this._childProperty;
            }

            constructor() {
                super();
                console.log('child: constructor');
            }

            connectedCallback() {
                console.log('child: connectedCallback');
                this.render();
            }

            render() {
                console.log(`child: render ${(this.childProperty ? 'with' : 'no')} property`);
                if (this.childProperty) {
                    let templateEl = document.createElement("template");
                    templateEl.innerHTML = `<p>${this.childProperty.value}</p>`;
                    this.appendChild(templateEl.content.cloneNode(true));
                }
            }
        });

        let appEl = document.querySelector('#app');
        let parentEl = document.createElement('parent-element');
        appEl.appendChild(parentEl);
        parentEl.parentProperty = { value: 'parent text' };
    </script>
</body>
</html>

Please focus on the part at the end:

let parentEl = document.createElement(‘parent-element’); — I create an instance of my parent-element
appEl.appendChild(parentEl); — I add it to the DOM by calling the appendChild() method
parentEl.parentProperty = { value: ‘parent text’ }; — I set the property using an object

The browser output looks like this:

parent text
child text

And my console log looks like this:

parent: constructor
parent: connnectedCallback
parent: render no property
PARENT: SET PROPERTY
parent: render with property
child: constructor
child: connnectedCallback
child: render no property
CHILD: SET PROPERTY
child: render with property

Notice that when calling the appendChild() method, the respective connectedCallback() method is triggered which in turn calls the render() method; as the property hasn’t been set yet, no output is produced.

After I set the property, the property setter is triggered which in turn calls the render() method again; as the property has now been set, actual output is generated.

So far, so good.

Now lets see what happens when I set the property on the parent element before adding it to the DOM.

<html>
<head></head>
<body>
    <app id="app"></app>
    <script>
        customElements.define('parent-element', class extends HTMLElement {
            set parentProperty(value) {
                console.log('PARENT: SET PROPERTY');
                this._parentProperty = value;
                this.render();
            }

            get parentProperty() {
                return this._parentProperty;
            }

            constructor() {
                super();
                console.log('parent: constructor');
            }

            connectedCallback() {
                console.log('parent: connectedCallback');
                this.render();
            }

            render() {
                console.log(`parent: render ${(this.parentProperty ? 'with' : 'no')} property`);
                if (this.parentProperty) {
                    let templateEl = document.createElement("template");
                    templateEl.innerHTML = `<p>${this.parentProperty.value}</p><child-element id="child"></child-element>`;
                    this.appendChild(templateEl.content.cloneNode(true));

                    let childEl = this.querySelector('#child');
                    childEl.childProperty = { value: 'child text' };
                }
            }
        });

        customElements.define('child-element', class extends HTMLElement {
            set childProperty(value) {
                console.log('CHILD: SET PROPERTY');
                this._childProperty = value;
                this.render();
            }

            get childProperty() {
                return this._childProperty;
            }

            constructor() {
                super();
                console.log('child: constructor');
            }

            connectedCallback() {
                console.log('child: connectedCallback');
                this.render();
            }

            render() {
                console.log(`child: render ${(this.childProperty ? 'with' : 'no')} property`);
                if (this.childProperty) {
                    let templateEl = document.createElement("template");
                    templateEl.innerHTML = `<p>${this.childProperty.value}</p>`;
                    this.appendChild(templateEl.content.cloneNode(true));
                }
            }
        });

        let appEl = document.querySelector('#app');
        let parentEl = document.createElement('parent-element');
        parentEl.parentProperty = { value: 'parent text' };
        appEl.appendChild(parentEl);
    </script>
</body>
</html>

So all I did was swap these lines:

parentEl.parentProperty = { value: ‘parent text’ }; — I set the property using an object
appEl.appendChild(parentEl); — I add it to the DOM by calling the appendChild() method

Now, my browser output looks like this:

parent text
child text
parent text

And my console output looks like this:

parent: constructor
PARENT: SET PROPERTY
parent: render with property
parent: connectedCallback
parent: render with property
child: constructor
child: connectedCallback
child: render no property
child: constructor
child: connectedCallback
child: render with property

It looks like the parent element has been rendered twice as the property on the parent element was available on both calls to the render method.

So finally, after all of this, my questions:

  1. Why does the second rendering of the child element contain no text ?
  2. How can the first rendering of the child element contain text when the setter of the property on the child element has NOT been called (“CHILD: SET PROPERTY” is missing from the console output) ?
  3. As a rule of thumb, should I always call appendChild() before setting my properties on a web component ?
  4. What is the correct time to call the render() method ? On connectedCallback, in the property setter or on both like I do now ?

So grateful for your feedback.

Express variable not passing to POST route

I cannot get the delete functionality to work in a to do list app because my JavaScript file is not recognizing the variable (from postgres DB) I’m passing from my Express file.

Full description

I am building my own app that has to do list functionality using Express, JavaScript, and Postgres for the database). I am trying to pass the unique task id from the index.ejs file to the index.js file using req.body to ultimately be deleted from the database. I expected the id to come through with req.body.deleteTaskId, I would store in taskToDelete, and then use that variable in my SQL code to delete the item from the database (Note: I’ve followed these steps for a different project from a tutorial and did not have this issue). When I log the variable that I expect to include the id, it comes out blank. There are 3 additional columns in the table (task_description, color, section). I can successfully pass task_description and color to the index.js file (for example if I change it to task.color in the same spot in the ejs file it goes through), section comes through blank similar to id. I confirmed that the data type of each column is a string but I cannot figure out why id and section will not pass through correctly (parseInt returns NaN).

SQL Data

  • Column id – integer automatically generated by postgre using SERIAL
  • Column task_description – TEXT added by a separate form in the app
  • Column color – CHARVAR added by a separate form in the app
  • Column section – CHARVAR currently added as the same string each time (not in the form)
CREATE TABLE tasks (
    id SERIAL NOT NULL UNIQUE,
    task_description TEXT NOT NULL,
    color VARCHAR(7) NOT NULL,
    section VARCHAR(50),
    PRIMARY KEY (id)
)

Any thoughts on why id and section are not passing through? (I am most concerned about id at the moment)

import express from "express";
import bodyParser from "body-parser";
import pg from "pg";

const app = express();
const port = 3000;

app.use(bodyParser.urlencoded({ extended: true}));
app.use(express.static("public"));

app.post("/delete", (req, res) => {
    const taskToDelete = req.body.deleteTaskId;
    console.log(taskToDelete);
// Did not include code to delete from DB
    res.redirect("/");
});
<% for (let task of tasks)  {%>                                         
  <form action="/delete" method="post">
    <input type="submit" id="trashImg" name="deleteTaskId" value="<%= task.id %>">
  </form>
<% } %>

P.S. This is my first time posting a question and I’m open to feedback on ways to improve how I presented the information.

Display Chart.js chart(s) depending on Livewire form submission

I am trying to display one or more charts using Chart.js and Laravel Livewire. My issue is that the chart isn’t displayed, even if the chart object receive data (when I console.log(chart) I can see the correct data)

I currently don’t bother to get actual form data, as I can’t even display a chart with dummy data (which was working fine before I tried to introduce Livewire).

Here is some code:

filter-graphs.blade.php (filter view)

<div>
    <form wire:submit="onGraphsChange" class="text-xl w-3/4 mx-auto">
        <div class="flex items-center justify-center">
            <div class="mx-4 flex-1 justify-center">
                <x-input-label for="graphs" value="Graphs à afficher" />
                <select id="graphs" name="graphs" multiple="multiple" class="mt-1 block w-full py-2 bg-gray-200 text-gray-800 rounded focus:border-[#F73F3A] focus:ring-[#F73F3A]">
                    <option class="py-1 bg-gray-200 text-gray-800" value="4">dummy 1</option>
                    <option class="py-1 bg-gray-200 text-gray-800" value="2">dummy 2</option>
                </select>
            </div>
            <div class="mx-4 flex-1 justify-center text-center">
                <button type="submit" class="px-4 py-2 border-2 border-gray-800 text-gray-800 rounded hover:text-gray-200 hover:bg-gray-800 hover:cursor-pointer">Afficher les graphs</button>
            </div>
        </div>
    </form>
</div>

FilterGraphs.php (filter component)

namespace AppLivewire;

use LivewireComponent;

class FilterGraphs extends Component
{
    public $selectedGraphs = null;

    public function onGraphsChange()
    {
        $this->dispatch('graphs-selected', $this->selectedGraphs);
    }

    public function render()
    {
        return view('livewire.filter-graphs');
    }
}

display-graphs.blade.php (charts display view)

<div class="flex">
    <div class="flex-1 shadow-sm rounded-lg bg-white m-4 p-4">
        <canvas id="test-graph"></canvas>
    </div>
</div>

<script>
    document.addEventListener('livewire:init', () => {
        Livewire.on('updateGraphs', (data) => {
            if(Chart.getChart("test-graph"))
                Chart.getChart("test-graph").destroy();

            var ctx = document.getElementById('test-graph').getContext("2d");
            var config = {
                type: 'line',
                data: {
                    labels: data[0]['labels'],
                    datasets: data[0]['datasets']
                },
                options: {
                    scales: {
                        x: {
                           ticks: {
                                display: false
                            },
                            grid: {
                                display: false
                            }
                        }
                    },
                    plugins: {
                        title: {
                            display: true,
                            text: '',
                        }
                    },
                    animation: false
                }
            };

            // myChart.config.options.plugins.title.text = data['title'];
            var myChart = new Chart(ctx, config);
        });
    });
</script>

DisplayGraphs.php (charts display component)

namespace AppLivewire;

use LivewireComponent;

use AppModelsHistory;

class DisplayGraphs extends Component
{
    public array $datasets = [];
    public array $labels = [];
    public $graphs;

    protected $listeners = [
        'graphs-selected' => 'graphsSelected',
    ];

    public function graphsSelected($server_service_ids)
    {
        //Get the dummy data

        $labels = $dates; // Contains an array of 85 dates as strings

        $datasets = [
            [
                'label' => 'Temps de réponse (ms)',
                'backgroundColor' => 'rgba(247, 63, 58, 0.3)',
                'borderColor' => 'rgb(247, 63, 58)',
                'lineTension' => 0.5,
                'pointRadius' => 2,
                'fill' => true,
                'data' => $response_times, // Contains an array of 85 int
            ],
            [
                'label' => 'Niveau d'alerte (ms)',
                'backgroundColor' => 'rgba(255, 87, 34, 0.3)',
                'borderColor' => 'rgb(255, 87, 35)',
                'pointRadius' => 0,
                'data' => $alert, // Contains an array of 85 int
            ],
        ];

        $this->dispatch('updateGraphs', [
            'datasets' => $datasets,
            'labels' => $labels,
        ]);
    }

    public function mount()
    {
        $this->labels = [];
        $this->datasets = [];
    }

    public function render()
    {
        return view('livewire.display-graphs');
    }
}

And the view within which I show all that

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
            Title
        </h2>
    </x-slot>

    <!--Tabs navigation-->
    @include('partials.subnavigation-tabs')

    <livewire:filter-graphs />
    <livewire:display-graphs />
</x-app-layout>

What am I missing here to get the charts to display?
Thanks in advance

How to Auto-Redirect My Website from Android WebView Apps to an External Browser?

Attempt 1: Using googlechrome://

if (/android/i.test(navigator.userAgent)) {
  window.location.href = `googlechrome://${window.location.href.replace(/^https?:///, '')}`;
}

Result: The link stays inside LinkedIn’s WebView (no redirection happens).

Attempt 2: Using Android Intent

if (/android/i.test(navigator.userAgent)) {
  const intentUrl = `intent://${window.location.href.replace(/^https?:///, '')}#Intent;scheme=https;package=com.android.chrome;end`;
  window.location.href = intentUrl;
}

Result: The page loads infinitely inside LinkedIn’s WebView.

I Need a way to force the link to open in Chrome (or the default browser) only when possible.