Normalising size of page in captureVisibleTab in chrome extension?

I am looking to capture and store a “normalised” (fixed dimensions) preview of a web page and then store it for display in a page of a Chrome extension. The issue is when using chrome.tabs.captureVisibleTab the generated result is whatever the size of the window was at the time of the capture.

Can anyone suggest a way to capture an image of the target web page at a specified width and height?

chrome.bookmarks.onCreated.addListener(async (id, bookmark) => {
  const result = await chrome.tabs.captureVisibleTab(undefined, {
    format: 'jpeg'
  });
  chrome.storage.local.set({ [`bookmark-${bookmark.id}-preview`]: result });
});

I did look at trying to create a temporary iframe for this, but I run into iframe security issues and I can’t see if there is a way to override it in a Chrome extension?

React (acuity component) not showing on deploy

created an website application with React, website looks fine in localhost, but once deployed to GitHub Pages the booking section only appears on laptop and tablet. I have loaded it on my iphone and it comes up blank. I have checked network on devtools and I think it may be a ‘recaptcha’ issue. I could be wrong..

Here’s the github pages link: https://armanijacobs.github.io/bedroom-shut-music-up/

(https://i.sstatic.net/nuatg9oP.png)
(https://i.sstatic.net/bLmSbmUr.png)](https://i.sstatic.net/WxPeIlOw.png)](https://i.sstatic.net/BH67uHhz.png)](https://i.sstatic.net/tTZDHGyf.png)

I was attempting to deploy a function acuity booking section in my website.

Getting error while trying to test my API backend

I’m currently writing backend tests as part of the fullstackopen course. I have 2 seperate test files: one for list testing (dummy data) and the other for API testing. While the list tests work, the API tester just instantly throws an error. This is the log:

file:///C:/Users/Edwar/WebstormProjects/fullstackopen/Part4/bloglist/tests/blog_api.test.js:1
const { test, describe} = require('node:test')
                          ^

ReferenceError: require is not defined in ES module scope, you can use import instead
    at file:///C:/Users/Edwar/WebstormProjects/fullstackopen/Part4/bloglist/tests/blog_api.test.js:1:27
    at ModuleJob.run (node:internal/modules/esm/module_job:274:25)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:644:26)
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5)

Node.js v22.16.0
✖ testsblog_api.test.js (47.9562ms)
✔ dummy function (0.8653ms)
▶ total likes
  ✔ total likes (0.1556ms)
✔ total likes (0.3705ms)
▶ favorite blog
  ✔ most liked (0.4298ms)
✔ favorite blog (1.1711ms)
▶ most blogs
  ✔ best author (0.5545ms)
✔ most blogs (0.7417ms)
▶ most likes
  ✔ most liked author (0.3022ms)
✔ most likes (0.5679ms)
ℹ tests 6
ℹ suites 4
ℹ pass 5
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 83.8575

✖ failing tests:

test at testsblog_api.test.js:1:1
✖ testsblog_api.test.js (47.9562ms)
  'test failed'

[Project structure][1]

{
  "name": "bloglist",
  "version": "1.0.0",
  "main": "app.js",
  "scripts": {
    "start": "cross-env NODE_ENV=production node index.js",
    "dev": "cross-env NODE_ENV=development node --watch index.js",
    "test": "cross-env NODE_ENV=test node --test"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "cors": "^2.8.5",
    "cross-env": "^10.0.0",
    "dotenv": "^17.2.2",
    "express": "^5.1.0",
    "mongoose": "^8.18.0"
  },
  "devDependencies": {
    "nodemon": "^3.1.10",
    "supertest": "^7.1.4"
  }
}

test_helper.test.js:

const {test, describe} = require('node:test')
const assert = require('node:assert')
const listHelper = require('../utils/list_helper')

test('dummy function', () => {
    const blogs = []

    const result = listHelper.dummy()
    assert.strictEqual(result, 1)
})

describe('total likes', () => {

    test('total likes', () => {
        const likes = listHelper.totalLikes(listHelper.initialBlogs)

        assert.strictEqual(likes, 12)
    })

})

describe('favorite blog', () => {

    test('most liked', () => {
        const favorite = listHelper.favoriteBlog(listHelper.initialBlogs)

        assert.deepStrictEqual(favorite, listHelper.initialBlogs[0])
    })

})

describe('most blogs', () => {

    test('best author', () => {
        const topAuthor = listHelper.mostBlogs(listHelper.initialBlogs)

        assert.deepStrictEqual(topAuthor, {author: "Michael Chan", blogs: 1})
    })
})

describe('most likes', () => {

    test('most liked author', () => {
        const bestAuthor = listHelper.mostLikes(listHelper.initialBlogs)

        assert.deepStrictEqual(bestAuthor, { author: 'Michael Chan', likes: 7 })
    })
}

blog_api.test.js:

const { test, describe} = require('node:test')
const supertest = require('supertest')
const mongoose = require('mongoose')
const app = require('../app')


describe('Blog api', () => {
    test('GET blogs', async () => {
        const response = await supertest(app)
            .get('/api/blogs')
            .expect(200)
            .expect('Content-Type', /application/json/)

        console.log(response.body)
    })
})

await mongoose.connection.close()

app.js:

const express = require('express')
const mongoose = require('mongoose')
const {MONGODB_URI} = require("./utils/config");
const logger = require("./utils/logger");
const middleware = require("./utils/middleware");
const blogsRouter = require("./controllers/blogs");

const app = express();

logger.info("Connecting to the database...", MONGODB_URI)

mongoose.connect(MONGODB_URI)
    .then(() => {
        console.log("Connected to the database...")
    }).catch((error) => {
    logger.error("An error occurred with the database, ", error);
})

app.use(express.json());
app.use(express.static('dist'));
app.use(middleware.requestLogger);

app.use('/api/blogs', blogsRouter);

app.use(middleware.unknownEndpoint);
app.use(middleware.errorHandler);


module.exports = app;

When I try to add beforeAll, afterAll methods from the node:test library, they also are never defined although I have Node v22 installed.

Chrome Extension stops working if service work dev tools is not open

A co-worker created a Chrome Extension using Javascript with functionality to take employees to different tools/pages we use when a prefix and some text are entered into the omnibar or highlighted and an option is selected from the context menu. I decided to convert the code to Typescript. A build script used webpack to convert the Typescript to Javascript. All that works fine.

The extension works fine when you use it once or twice by entering text into the omnibar or highlighting text and selection an option from the context menu but it eventually stops working. When you take an action that should take you to a page in a new tab nothing happens. The strange thing is if you have the dev tools window open when you click on the service worker link for the extension the extension never stops working. I assumed there was some error and it would be logged to the console but it’s not. I thought maybe having dev tools open was causing the error to be swallowed so I put a bunch of try {...} catch (e) { console.error(e); } throughout the code but nothing is logged.

The gist of the code:

background.ts

const omniboxHandler = new OmniboxHandler();
const contextMenu = new ContextMenu();

try {
    chrome.runtime.onInstalled.addListener(async () => {
        omniboxHandler.addListener();
        contextMenu.createContextMenu();
    });
} catch (e) {
    console.log('***', e);
}

omnibox-handler.ts

export class OmniboxHandler {
    public addListener() {
        chrome.omnibox.onInputEntered.addListener(this.handleInputEntered);
    }

    private handleInputEntered(searchString: string) {
        try {
            for (const command of COMMANDS) {
                console.log('Checking command:', JSON.stringify(command.constructor, null, 2), searchString);
                if (command.processSearchString(searchString)) {
                    console.log(`Omnibox command executed:`, JSON.stringify(command, null, 2), searchString);
                    break;
                }
            }
        } catch (e) {
            console.error('Error in omnibox input handler', e);
        }
    }
}

context-menu.ts

export class ContextMenu {
    public createContextMenu(): void {
        chrome.contextMenus.create({
            title: 'Go to "%s"',
            id: CONTEXT_MENU_ID,
            contexts: [ContextType.SELECTION]
        } as CreateProperties);
        chrome.contextMenus.onClicked.addListener(this.searchText);
    }

    private async searchText(info: OnClickData) {
        try {
            const searchString = info.selectionText;
            console.log(`Searching ${searchString}`);
            // A bunch of if elses that open a new tab to a url based on the search string
        } catch (e) {
            console.error('Error in context menu click handler', e);
        }
    }
}

Chrome Manifest V3 Service Worker fails to execute without errors after bundling with esbuild

I’ve built a Chrome extension using Manifest V3. My background.js script, which uses the modular Firebase v9 SDK (import { … } from ‘firebase/firestore’), works correctly when loaded directly.

To handle the dependencies, I’ve used esbuild to bundle my background.js and the Firebase SDK into a single background.bundle.js file. My manifest points to this new file and declares it as a module:

"background": {
  "service_worker": "background.bundle.js",
  "type": "module"
},

The npm run build command runs without errors.

However, when I load the unpacked extension, the service worker fails to run. The service worker console is completely blank (no errors, no logs). The extension’s popup, which tries to message the service worker, is stuck on “Loading…”.

The bundled file is not empty. What could cause a bundled service worker to fail to execute so early that it doesn’t produce any errors in its own console?

Developing browser extensions using react

I want to develop a browser extension using react. Which bundler can do this well? I need hot reload support and source map support to debug without problems. I also plan to include wasm modules. I tried vite with crxjs and vite-plugin-web-extension plugins, but I didn’t find any suitable settings to make debugging painless.

jQuery Tablesorter plugin: How to define the value that the filter is tested against

I have a tablesorter table with a column where each td contains a lot of html. I don’t want the filter term to be tested against all of the contents of the td, i just want it to be tested against a specific value which i define in a data attribute in the td. Something like this:

<% @lessons.each do |lesson| %>
  <tr>
    <td data-filter-value="<%= lesson.name %>">
      <%= render :partial => "lessons/lesson" #a big chunk of html %>
    </td>
  </tr>
<% end %>

This is ruby but that doesn’t really matter, it’s just to illustrate. The point is that i want the tablesorter filter to only look at the contents of the data-filter-value attribute.

I can think of ways around this like a zero width column with the value in it and then i do some css hack to make the filter look like it’s for the other column, but that’s all pretty nasty. Is there a way to just do it with a data attribute? I’m looking in the filter widget documentation and i can’t see it but i might just not be reading it right.

https://rotto-bg.com/userfiles/frontend/js/tablesorter-v2.17/docs/example-widget-filter.html

Remotion Video has audio quality issue

I’m generating video using remotion library. We can combine the video and text and generate an new video. I used to place the text at the bottom of the video and generate an video.
Scenario 1:
When i use a video with audio, then new video is not generating properly, The new video has audio quality issue. PFA.

Scenario 2:
When i use a video without audio, then combine it with audio and text, then the new video is working fine.

import { renderMediaOnLambda} from '@remotion/lambda/client';

const { renderId, bucketName } = await renderMediaOnLambda({
      region: "us-east-1",
      functionName: config.lambdaFunction,
      serveUrl: config.serverUrl,
      composition: composition,
      inputProps: jsonData,
      codec: "h264",
      audioCodec:"mp3",
       
      audioBitrate:"1M",
      videoBitrate:"1M",
      outName: {
        bucketName: config.bucket,
        key: outputFileName,
      },
      imageFormat: "jpeg",
      maxRetries: 1,
      framesPerLambda: 1000,
      privacy: "public",
    });
    
  //  Example 2:Actual data
  
  
  const jsonData = {
    "design": {
        "id": "BlrKj0qKg1PX7vfx",
        "size": {
            "width": 1080,
            "height": 1920
        },
        "fps": 30,
        "tracks": [
            {
                "id": "k9aar-Jm6RQoPT3Yvyk3C",
                "accepts": [
                    "text",
                    "image",
                    "video",
                    "audio",
                    "composition",
                    "caption",
                    "template",
                    "customTrack",
                    "customTrack2",
                    "illustration",
                    "custom",
                    "main",
                    "shape",
                    "linealAudioBars",
                    "radialAudioBars",
                    "progressFrame",
                    "progressBar",
                    "rect",
                    "progressSquare"
                ],
                "type": "text",
                "items": [
                    "UyDpqx4ZxJY0CRsV"
                ],
                "magnetic": false,
                "static": false
            },
            {
                "id": "tl2rFnfH3Mn2kRWKkC_88",
                "accepts": [
                    "text",
                    "image",
                    "video",
                    "audio",
                    "composition",
                    "caption",
                    "template",
                    "customTrack",
                    "customTrack2",
                    "illustration",
                    "custom",
                    "main",
                    "shape",
                    "linealAudioBars",
                    "radialAudioBars",
                    "progressFrame",
                    "progressBar",
                    "rect",
                    "progressSquare"
                ],
                "type": "video",
                "items": [
                    "H5HD31deQOSTyVlo"
                ],
                "magnetic": false,
                "static": false
            }
        ],
        "trackItemIds": [
            "H5HD31deQOSTyVlo",
            "UyDpqx4ZxJY0CRsV"
        ],
        "trackItemsMap": {
            "H5HD31deQOSTyVlo": {
                "id": "H5HD31deQOSTyVlo",
                "details": {
                    "width": 960,
                    "height": 960,
                    "opacity": 100,
                    "src": "https://v3.fal.media/files/monkey/xbAZ2BQi73CF7Gkv6hnAF_6z6-Lkg6MeICe-cIXwqMs_video.mp4",
                    "volume": 100,
                    "borderRadius": 0,
                    "borderWidth": 0,
                    "borderColor": "#000000",
                    "boxShadow": {
                        "color": "#000000",
                        "x": 0,
                        "y": 0,
                        "blur": 0
                    },
                    "top": "480px",
                    "left": "60px",
                    "transform": "scale(1.125)",
                    "blur": 0,
                    "brightness": 100,
                    "flipX": false,
                    "flipY": false,
                    "rotate": "0deg",
                    "visibility": "visible"
                },
                "metadata": {
                    "previewUrl": "https://remotionlambda-useast1-myb.amazonaws.com/demo-assets/group-1.png"
                },
                "trim": {
                    "from": 0,
                    "to": 5015.011
                },
                "type": "video",
                "name": "video",
                "playbackRate": 1,
                "display": {
                    "from": 0,
                    "to": 5015.011
                },
                "duration": 5015.011,
                "isMain": false
            },
            "UyDpqx4ZxJY0CRsV": {
                "id": "UyDpqx4ZxJY0CRsV",
                "name": "text",
                "type": "text",
                "display": {
                    "from": 0,
                    "to": 5000
                },
                "details": {
                    "text": "Heading and some body",
                    "fontSize": 120,
                    "width": 600,
                    "fontUrl": "https://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlvAx05IsDqlA.ttf",
                    "fontFamily": "Roboto-Bold",
                    "color": "#ffffff",
                    "wordWrap": "break-word",
                    "textAlign": "center",
                    "borderWidth": 0,
                    "borderColor": "#000000",
                    "boxShadow": {
                        "color": "#ffffff",
                        "x": 0,
                        "y": 0,
                        "blur": 0
                    },
                    "fontWeight": "normal",
                    "fontStyle": "normal",
                    "textDecoration": "none",
                    "lineHeight": "normal",
                    "letterSpacing": "normal",
                    "wordSpacing": "normal",
                    "backgroundColor": "transparent",
                    "border": "none",
                    "textShadow": "none",
                    "opacity": 100,
                    "wordBreak": "normal",
                    "WebkitTextStrokeColor": "#ffffff",
                    "WebkitTextStrokeWidth": "0px",
                    "top": "748.5px",
                    "left": "240px",
                    "textTransform": "none",
                    "transform": "none",
                    "skewX": 0,
                    "skewY": 0,
                    "height": 423
                },
                "metadata": {},
                "isMain": false
            }
        },
        "transitionIds": [],
        "transitionsMap": {},
        "scale": {
            "index": 7,
            "unit": 300,
            "zoom": 0.0033333333333333335,
            "segments": 5
        },
        "duration": 5015.011,
        "activeIds": [
            "H5HD31deQOSTyVlo"
        ],
        "structure": [],
        "background": {
            "type": "color",
            "value": "transparent"
        }
    },
    "options": {
        "fps": 30,
        "size": {
            "width": 1080,
            "height": 1920
        },
        "format": "mp4"
    }
};
  const inputJSON = {"region":"us-east-1","functionName":"remotion-render-myfunc-240sec","serveUrl":"https://remotionlambda-useast1-mybucket.s3.us-east-1.amazonaws.com/sites/dev/index.html","composition":"RenderVideo","inputProps":jsonData,"codec":"h264","audioCodec":"mp3","audioBitrate":"1M","videoBitrate":"1M","outName":{"bucketName":"remotionlambda-useast1-3rne5v73bs","key":"demo-vid-9169.mp4"},"imageFormat":"jpeg","maxRetries":1,"framesPerLambda":1000,"privacy":"public"};
  
  
  const { renderId, bucketName } = await renderMediaOnLambda(inputJSON);
  
     

INPUT VIDEO URL: https://v3.fal.media/files/monkey/xbAZ2BQi73CF7Gkv6hnAF_6z6-Lkg6MeICe-cIXwqMs_video.mp4

Output Generated Video URL:
https://vimeo.com/1118133462?share=copy

Reference URL:
https://www.remotion.dev/docs/lambda/rendermediaonlambda

Reference URL which as all the props related to this function:
https://www.remotion.dev/docs/cli/render#–muted

Note: We can use any video with audio to reproduce this issue with Remotion.

Any help would be highly appreciated.

How can I prevent selecting DOM elements that exist only in memory?

I have a modal with elements that I want to return to a default state every time the modal is closed. Rather than worry about making sure I tell the openModal or closeModal functions to clear each element I put in, I created a <div id='controlsPrototypeDiv'> with all the controls inside, then did $('#controlsPrototypeDiv').clone().detach(); and stored the result as a variable. Then I can have the openModal function just replace the existing controls div with a copy of the stored prototype.

Inside the controls div there is a child element with id='receiveItemChosenTitle'. Whenever I do $('#receiveItemChosenTitle') it selects both copies of the element: the one in the DOM and the one in the prototype variable. This defeats the purpose of keeping a separate “clean” copy of the prototype. Shouldn’t doing $('#receiveItemChosenTitle') select only the element attached to the DOM and not the one stored in the variable?

Please note, I’m aware that standard form controls can be easily reset with a single call. However I need to include images and other non-form elements (like images and other read-only data) that I want to reset to a default state also.

The relevant code (with some irrelevant stuff removed) is below.

var RECEIVE_ITEM_MODAL_CLEAN;
$(function()
{
    RECEIVE_ITEM_MODAL_CLEAN = $('#receiveItemControlsPrototype').clone().detach();
});

function openReceiveModal()
{
    $('#receiveItemControlsPrototype').append($(RECEIVE_ITEM_MODAL_CLEAN).clone().show());
    $('#modalBGDimmer').show();
    $('#receiveItemModal').show();
    $('#receiveItemSearch').focus();
}
<div id='receiveItemControlsPrototype' hidden>
    <div id='receiveItemChosenTitleDiv'>
        <svg ...>...</svg>
        &nbsp;<span id='receiveItemChosenTitle'></span>
    </div>
</div>

I tried using .detach() when I cloned the prototype div, but I can still select the detached element’s children.

Node.js/EJS: POST request targets all list items instead of the one clicked

I’m using Node.js with EJS to display a list of products. Each product has a form with a button to add it to a wishlist. The forms are generated in a loop, and each form’s action attribute is correctly set to /addToWishlist/:id.

My problem is that when I click the “Add to Wishlist” button on one product, the server-side code seems to receive a request that affects multiple products instead of just the one I clicked. I have confirmed that the EJS loop correctly renders a separate form for each item.

I’m using the following EJS and Node.js code:

EJS:

<li class="product">
  <form action="/addToWishlist/<%= product.id %>" method="POST">
    <button type="submit" aria-label="Add to Wishlist">
      </button>
  </form>
</li>

Node.js (Express) route:

app.post("/addToWishlist/:id", async (req, res) => {
  const productId = parseInt(req.params.id, 10);
  try {
    // Check if the item already exists in the wishlist
    const checkQuery = "SELECT * FROM wishlist WHERE product_id = $1 AND user_id = $2";
    const existingItem = await db.query(checkQuery, [productId, req.user.id]);

    if (existingItem.rowCount === 0) {
      // Only insert if the item is not already in the wishlist
      const insertQuery = "INSERT INTO wishlist (product_id, product_name, user_id) VALUES ($1, $2, $3)";
      await db.query(insertQuery, [productId, 'Product Name', req.user.id]); // Note: Use product.name instead of productId
      res.redirect('/success');
    } else {
      res.redirect('/already-in-wishlist');
    }
  } catch (err) {
    console.error(err);
    res.status(500).send("An error occurred.");
  }
});

My server-side code correctly receives the product ID in req.params.id. What could be causing this unexpected behavior where clicking one button seems to trigger an action for multiple items? Is there a common front-end issue that could cause this with forms generated in a loop? I am not using any custom JavaScript to handle the form submission.

“Unexpected end of JSON input at JSON.parse” API error with POSTMAN and javascript

I am trying to write an API to essentially take in a user credential and return a hashed value with a digital certificate. I have created an API with node.js and POSTMAN to create a JSON with which I can manipulate the data and use it for other API interfaces. I am receiving the following error

SyntaxError: Unexpected end of JSON input
application.js:617
    at JSON.parse (<anonymous>)
application.js:617
    at createStrictSyntaxError (C:UsersframDesktopAugust_26node_modulesbody-parserlibtypesjson.js:156:10)
application.js:617
    at parse (C:UsersframDesktopAugust_26node_modulesbody-parserlibtypesjson.js:71:15)
application.js:617
    at C:UsersframDesktopAugust_26node_modulesbody-parserlibread.js:123:18
application.js:617
    at AsyncResource.runInAsyncScope (node:async_hooks:214:14)
application.js:617
    at invokeCallback (C:UsersframDesktopAugust_26node_modulesraw-bodyindex.js:238:16)
application.js:617

The request body is showing blank for one of my posts but two other posts are returning good data in the console log (certJson). Unfortunately my body JSON output is a html displaying the above error message. I believe I am looking for a syntax error in my code but I cannot find it and neither can the compilers in VS Studio and POSTMAN web. I would appreciate any assistance to solve this.

Here is the node server.js file to initiate the api.It runs fine with no compiling errors.


// server.js
import express from "express";
import cors from "cors";
import { ethers } from "ethers";

//dummy key

const SP_PRIVATE_KEY = process.env.SP_PRIVKEY || "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const SIGNER = new ethers.Wallet(SP_PRIVATE_KEY);

// EIP-712 domain and types
const domain = {
  name: "SPCertificate",
  version: "1",
  chainId: 1,               
  verifyingContract: "0x0000000000000000000000000000000000000000"  
};

const types = {
  Certificate: [
    {name: "userId", type:"bytes32"},
    {name: "dln", type:"string"},
    {name: "audience", type:"string"},
    {name: "nonce", type:"bytes32" },  
    {name: "issuedAt", type:"uint64"},
    {name: "exp", type:"uint64"}
  ]
};

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

// Utility: random 32-byte hex
function randBytes32() {
  return ethers.hexlify(ethers.randomBytes(32));
}

// POST /cert/issue construction and check { dln, secret_hash, audience }
app.post("/cert/issue", async (req, res) => {
  try {
    const {dln,secret_hash,audience="SP:DEMO"} = req.body || {};
    if (!dln || !secret_hash) {
      return res.status(400).json({error:"Missing dln or secret_hash"});
    }
    // Basic input string regular expression (regex) checks 
    if (typeof dln !== "string" || !/^[-A-Za-z0-9]{5,}$/.test(dln)) {
      return res.status(400).json({ error: "Invalid dln format" });
    }
    if (!ethers.isHexString(secret_hash, 32)) {
      return res.status(400).json({ error: "secret_hash must be 0x + 32-byte hex" });
    }

    const now = Math.floor(Date.now() / 1000);
    const exp = now + 60 * 60 * 24 * 7; // 7 days validity 
    const cert = {userId: randBytes32(), dln, audience, nonce: secret_hash, issuedAt: now, exp};

    const signature = await SIGNER.signTypedData(domain, types, cert);
    return res.json({cert, signature, signer: SIGNER.address, domain, types});
  } catch (e) {
    console.error(e);
    res.status(500).json({ error: "internal_error" });
  }
});

// POST /cert/verify construction { cert, signature, expectedSigner? }
app.post("/cert/verify", async (req, res) => {
  try {
    const { cert, signature, expectedSigner } = req.body || {};
    if (!cert || !signature) {
      return res.status(400).json({ok:false, error: "Missing cert or signature"});
    }
    const recovered = ethers.verifyTypedData(domain, types, cert, signature);
    const ok = expectedSigner ? (recovered.toLowerCase() === expectedSigner.toLowerCase()) : true;
    return res.json({ok, recovered,expected: expectedSigner || null });
  } catch (e) {
    console.error(e);
    res.status(400).json({ok:false,error: "verify_failed" });
  }
});

const port = process.env.PORT || 10533;
app.listen(port, () => {
  console.log(`SP demo listening on http://localhost:${port}`);
  console.log(`Signer address: ${SIGNER.address}`);
});

I have a pre request file in POSTMAN which looks like this.

const secret = pm.environment.get("user_secret");
const dln = pm.environment.get("dln");
const spBase = pm.environment.get("sp_base");
const crypto = pm.require('npm:[email protected]');

if (!secret || !dln || !spBase) {
    throw new Error("Missing env vars: user_secret, dln, sp_base");
}
const sha = crypto.SHA256(crypto.enc.Utf8.parse(secret));
const secretHashHex = "0x" + sha.toString(crypto.enc.Hex);
pm.environment.set("secret_hash", secretHashHex);
const url = spBase + "/cert/issue";
const body = {dln: dln,secret_hash:secretHashHex, audience:"SP:DEMO"};

pm.sendRequest({url, method:"POST",header: [{key:"Content-Type",value:"application/json"}],
    body: {mode:"raw",raw:JSON.stringify(body)}
}, function (err, resp) {
    try {
        if (err) { 
            console.log("Issue error:", err); 
            throw err; 
        }
        if (resp.code !== 200) {
            console.log("Issue failed:", resp.text());
            throw new Error("SP issue endpoint failed");
        }
        const data = resp.json();
        // Check if data and cert exist
        if (data && data.cert) {
            pm.environment.set("cert_json", JSON.stringify(data.cert));
            pm.environment.set("signature", data.signature);
            pm.environment.set("sp_signer", data.signer);
            pm.environment.set("cert_userId", data.cert.userId);
            pm.environment.set("cert_dln", data.cert.dln);
            pm.environment.set("cert_exp", data.cert.exp.toString());
            console.log("Cert and signature set successfully");
        } else {
            throw new Error("Invalid response structure: " + JSON.stringify(data));
        }
    }
    catch (e) {
        console.error("An error occurred:", e);
    }   
});

Then I have the tests (post response) which is mostly JSON format checks and looks like this:

const spBase = pm.environment.get("sp_base");
const certJson = pm.environment.get("cert_json");
const signature = pm.environment.get("signature");
const spSigner = pm.environment.get("sp_signer");

//console.log("certJson is " + certJson);

pm.test("Cert and signature exist", function () {
  pm.expect(certJson).to.be.a("string");
  // Updated regex to ensure signature matches expected format
  pm.expect(signature).to.match(/^0x[0-9a-fA-F]{130}$/);
});

const verifyReq = {
  url: spBase + "/cert/verify",
  method: "POST",
  header: [{ key: "Content-Type", value: "application/json" }],
  body: { mode: "raw", raw: JSON.stringify({ cert: JSON.parse(certJson), signature: signature, expectedSigner: spSigner})} 
  };

pm.sendRequest(verifyReq, function (err, resp) {
  pm.test("Verify endpoint responded", function () {
    pm.expect(err).to.be.null;
    pm.expect(resp).to.have.property("code", 200);
  });

  const data = resp.json();
  pm.test("Signature is valid", function () {
    pm.expect(data.ok).to.eql(true);
    pm.expect((data.recovered || "").toLowerCase()).to.eql((spSigner || "").toLowerCase());
  });
}); 

Please check my pm.sendRequest code for potential errors. One of my posts returns a 400 bad response error so this could be triggered inside the server.js but I am not sure. This is the POST that seems to be generated by POSTMAN and not my scripts.

There are three POSTS appearing in the console. I can see output in the console log raw output which looks valid for two of these posts (certJson), I checked this output with JSONLINT and it is good JSON, but its in the console log! not the message body. As mentioned above the second post in the list of three contains a response body with the above html (error code) and no JSON ever appears in the body field of POSTMAN. My content type is set to application.json and the sp_base url is correctly showing the local host address. I start with three manually configured environment variables dln, the user_secret (which will eventually become a large prime) and the sp_base (localhost:10533 as per postman agent). Then server.js produces more credentials. However I am trying to solve this JSON output error which refuses to parse into the message body. Any assistance gratefully received. Please look for syntax errors as per error message. I am using firefox as my browser for POSTMAN web.

using same index value for two fields

I’m looking to correlate two fields of my form, using the same index number.

I mean, I have a group of fields called traj in an array. Each traj field is (or should be) related to a niv field, with the same index number (hierarchical index), and I would like to find a specific word in one of the traj fields, get the number on the related niv field and modify the result field with it. If there isn’t the word, nothing happens.

My code is:

  // there are 6 traj fields (traj.0, and so on, up to traj.5) and 6 niv fields (same way, niv.0 to niv.5), and a result field

  var oAtzar = this.getField("traj").getArray(); //create the array

  for (i = 0; i < oAtzar.length; i++) { //begin the loop

  var tNom = oAtzar[i].valueAsString //define I'm looking for a word inside the array

    if (tNom === "Jesuïta") { //if the word is the one I'm looking for...

      var tNiv = this.getField("niv."+i) //check the niv field related to the traj field; same *i* value

      event.value = 16 - 1*(tNiv.value) //modify the result field (autocalculated)

    } else {

      event.value = 16 //no word, nothing happens

    }

  }

However, I found some issues:

  • It doesn’t modify the result field. I though as the i value is the same, it would work, but is doesn’t.
  • Checking before publishing I found that it finds the word, but only in the first field. If I write it in any other field of the array, it doesn’t find anything.

I konw I can do it with a cascade of if…else, but I though it would be faster (and easier) with an array and a loop.

I hope I have detailed well enought!

Thank you in advance for your help!

Puppeteer – scroll down until you can’t anymore – script failing

I want to automate deleting my ChatGPT chats because I have about 244 of them, and I’m not going to do that manually. What I want to do is scroll down to the bottom until there are no more chat items, then delete them from last to first. The deletion part works, but I’m having some issues with the scrolling part.
console.log(“scrollToBottom has been called”);

await page.evaluate(async () => {
    const delay = 10000;
    const wait = (ms) => new Promise(res => setTimeout(res, ms));
    const sidebar = document.querySelector('#stage-slideover-sidebar');

    const count = () => document.querySelectorAll('#history aside a').length;

    const scrollDown = async () => {
        const lastChild = document.querySelector('#history aside a:last-child');
        if (lastChild) {
            lastChild.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'end' });
        }
    }

    let preCount = 0;
    let postCount = 0;
    let attempts = 0;
    do {
        preCount = count(); 
        await scrollDown();
        await wait(delay);
        postCount = count(); 
        console.log("preCount", preCount, "postCount", postCount, "attempts", attempts);
        if (postCount === preCount) {
            attempts++;
        } else {
            attempts = 0;
        }
    }  while (attempts < 10);

    console.log("Reached bottom. Total items:", postCount);

    // await wait(delay);
});

}
This works better than when I set the delay to 1, 2, or 3 seconds and the attempts to 3. When I use this, it stops loading at 84.
However, the issue I have with a 10-second delay and 10 attempts is that I run into this error after everything has loaded.

    #error = new Errors_js_1.ProtocolError();
             ^

ProtocolError: Runtime.callFunctionOn timed out. Increase the 'protocolTimeout' setting in launch/connect calls for a higher timeout if needed.
    at <instance_members_initializer> (/Users/pc/WebstormProjects/puppeteer/node_modules/puppeteer-core/lib/cjs/puppeteer/common/CallbackRegistry.js:102:14)
    at new Callback (/Users/pc/WebstormProjects/puppeteer/node_modules/puppeteer-core/lib/cjs/puppeteer/common/CallbackRegistry.js:106:16)
    at CallbackRegistry.create (/Users/pc/WebstormProjects/puppeteer/node_modules/puppeteer-core/lib/cjs/puppeteer/common/CallbackRegistry.js:24:26)
    at Connection._rawSend (/Users/pc/WebstormProjects/puppeteer/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/Connection.js:99:26)
    at CdpCDPSession.send (/Users/pc/WebstormProjects/puppeteer/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/CdpSession.js:73:33)
    at #evaluate 
(/Users/pc/WebstormProjects/puppeteer/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/ExecutionContext.js:363:50)
    at ExecutionContext.evaluate (/Users/pc/WebstormProjects/puppeteer/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/ExecutionContext.js:277:36)
    at IsolatedWorld.evaluate (/Users/pc/WebstormProjects/puppeteer/node_modules/puppeteer-core/lib/cjs/puppeteer/cdp/IsolatedWorld.js:100:30)
    at CdpFrame.evaluate (/Users/pc/WebstormProjects/puppeteer/node_modules/puppeteer-core/lib/cjs/puppeteer/api/Frame.js:364:43)
    at CdpFrame.<anonymous> (/Users/pc/WebstormProjects/puppeteer/node_modules/puppeteer-core/lib/cjs/puppeteer/util/decorators.js:109:27)

Node.js v22.19.0

How best can I approach this

Redux vs. Zod: Clarifying their Roles in Modern React State Management

I’m working on a React application and am trying to understand the fundamental difference between Redux and Zod. I’ve seen both mentioned in discussions about managing state, and I’m confused about how they relate, if at all.

My current understanding is that:

Redux (specifically with React-Redux hooks like useSelector and useDispatch) is a state management library that provides a predictable container for your application’s global state.

Zod is a validation library, often used with form management libraries like React Hook Form.

Where I’m getting mixed up is the term “state.” I have two separate code snippets below and would appreciate an explanation of how they handle “state” differently.

Redux Code Example

Here’s how I’m using Redux to manage a simple counter’s state:

# store.js
import { createStore } from 'redux';

const initialState = {
  count: 0
};

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    default:
      return state;
  }
}

const store = createStore(counterReducer);

export default store;

# CounterComponent.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';

const CounterComponent = () => {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>
        Increment
      </button>
    </div>
  );
};

export default CounterComponent;

In this example, the count is the application’s state.

Zod Code Example

Here’s how I’m using Zod to validate a user form input:

import { z } from 'zod';

// Define the schema for our form data
const userSchema = z.object({
  username: z.string().min(3, { message: "Username must be at least 3 characters." }),
  email: z.string().email({ message: "Invalid email address." }),
});

// A sample piece of 'state' (form data) I might want to validate
const userData = {
  username: "jo", // This is invalid
  email: "not-an-email", // This is also invalid
};

try {
  userSchema.parse(userData);
} catch (e) {
  console.log(e.errors);
}

In this case, userData is the state I’m trying to validate.

My Core Questions
How do Redux’s state (the count) and Zod’s “state” (the userData) differ conceptually?

In a typical application, where would you use Redux vs. Zod, and why?

Are there scenarios where they can be used together, and if so, how do they complement each other? For example, would I validate Redux state with Zod?

I’m looking for an answer that clarifies the separate roles and responsibilities of these libraries, not just a list of features.

React + Tailwind: Flip card not showing back image

I’m trying to implement a flip card animation in React with Tailwind.
The rotation works (the card flips in 3D), but the back image does not appear: when the card rotates, it stays blank or still shows the front.

I tried using both backgroundImage and with rotateY(180deg) and backface-visibility: hidden, but the back side never shows.
How can I make the back side of the card (CardBack) visible when the card rotates 180°?

Here’s a minimal example of my code:

import { useState } from "react";
import CardCover from "../cardImages/cardCover.png";
import CardBack from "../cardImages/cardBack.png";

export default function TestCard() {
  const [isFlipped, setIsFlipped] = useState(false);

  return (
    <div
      className="w-[200px] h-[300px] cursor-pointer [perspective:1000px]"
      onClick={() => setIsFlipped(!isFlipped)}
    >
      <div
        className="relative w-full h-full transition-transform duration-500"
        style={{
          transformStyle: "preserve-3d",
          transform: isFlipped ? "rotateY(180deg)" : "rotateY(0deg)",
        }}
      >
        {/* Front */}
        <div
          className="absolute w-full h-full rounded-xl bg-cover bg-center"
          style={{
            backgroundImage: `url(${CardCover})`,
            backfaceVisibility: "hidden",
          }}
        ></div>

        {/* Back */}
        <div
          className="absolute w-full h-full rounded-xl bg-cover bg-center"
          style={{
            backgroundImage: `url(${CardBack})`,
            transform: "rotateY(180deg)",
            backfaceVisibility: "hidden",
          }}
        ></div>
      </div>
    </div>
  );
}