How can I create a Control Page for my Esp32 devices?

I am working on a small IoT personal project with some esp32 powered devices. The devices are controlled with a web interface, and all have the same program inside, basically a multifunction timer with sensors and relays. Interface is driven by Esp32AsyncWebserver. I have a “let’s look it up in StackOverflow” programming knowledge and I was able to get them running and working fine. I also designed the PCB and got it manufactured for cheap in China, and that is WAY more that I expexted I was able to do!

Now the problem: I would like to setup a “Controlling interface” where all the devices are showing up, as many IoT apps do (see: Google >Home, TP-Link Tapo etc), and I’m not able to conjure up something working. This is what I tried so far:

  • first approach eas to give each device an unique, hardcoded, fixed IP then creating a static HTML linking to each device. I would like to avoid this as I would need to program each device one by one, make a list of the devices-IPs, reserve the IPs in the router so that they are not assigned to other stuff in the network. This would also prevent me to bring them with me and hook them up into someone’s else wifi (they have an inbuilt wifi manager so moving them around would not be a problem “per se”)
  • I then tried an HTML page with a JavaScript function to scan my network. First approach was to give all the devices a specific Hostname and look for it, but apparently JS is not able to read Local Network Hostnames, or at least I couldn’t find how. I then tried adding a “ping_me” page in each device and ask JS to scan my network for any active “http:192.168.1.xping_me” page, and that kind of worked, only problem is that the fetch() function in JS takes ages to give up a non existing IPs. It took nearly 5 minutes to run through a 10 IP test range
  • As a last, desperate attempt I also gave a try to AI app generators. Browser based ones told me that “browser cannot do what I ask due to browser limitations”. Native android app generators did’t work at all (wouldn’t say :D)
  • I’ve seen the Arduino IoT library but I’d rather to keep things “local” and not “cloud”

Given my absolute lack of knowledge in Android app creation, I still think that the HTML+JS approach is the best approach. Is there any way to have a fast network scan in JS, something like “check if IP is used and look for the ping_me page only for existing >IPs”? I’m also open to suggestions for other approaches to the problem.
Thanks in advance to anyone who will try help! 🙂

Can someone explain me please , why am I getting different results [closed]

I was trying to search the index of a substring “id” in the string = “Widget with id”which is at 12th position. But I am getting wrong result as ‘1’. Why ? enter image description here

I was trying to search the index of substring using .indexOf but I was getting different results for str1. I am getting 1 as a result, when I was supposed to get 12 while searching it using the .index of().
Although it’s giving me the correct index for other string I tried to search.
When I am adding the position value to it it’s giving me the right result as 12. enter image description here

Getting Code Not Found Error in Vercel Deploynment for Node JS Application

I’m trying to deploy my Node.js backend on Vercel, and after the deployment, which showed no error in the build process, and was deployed. I’m seeing 404 Error, Code Not Found.
I have added the vercel.json file as stated in other similar answers, but none of them are backend-specific, and my problem is not solved. I’m still seeing the error.

I’m using TypeScript and a Turborepo mono-repo for this project.

Here is the code to reproduce my problems:

//tsconfig.json
{
  "compilerOptions": {
    "module": "nodenext",
    "target": "esnext",
    "types": [],
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "strict": true,
    "jsx": "react-jsx",
    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "noUncheckedSideEffectImports": true,
    "moduleDetection": "force",
    "skipLibCheck": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
//package.json

{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "dist/Server.js",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "dev": "tsx watch src/Server.ts",
    "start": "node dist/Server.js",
    "check-types": "tsc --noEmit",
    "lint": "eslint ."
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "mongoose": "^8.18.0"
  },
  "devDependencies": {
    "tsx": "^4.20.5"
  }
}
//app>src>Server.ts
import express from "express";

if (process.env.NODE_ENV !== "production") {
  import("dotenv").then((dotenv) => dotenv.config({ path: "./.env" }));
}
const app: express.Application = express();
app.use(express.json());

app.get("/", (req, res) => {
  res.send("Hello From the server");
});

app.get("/api/v1/health", (req, res) => {
  res.json({ status: "OK" });
});


if (process.env.VERCEL === undefined) {
  app.listen(process.env.PORT || 3000, () => {
    console.log(`The app is running on PORT: ${process.env.PORT || 3000}`);
  });
}

export default app;

I want to know why this is happening for node js specific application and how to resolve this issue.

Thanks in advance

Hi I want to add option product prices into the main product price which is shown above the 2 option product prices in the image

enter image description here
I want to add the option product prices into to the main product price shown above the 2 options , when quantities of either of one increase will reflect or add in the main product price shown above.
I am attaching another images for reference.

[option prices added in the main product price when quantities increase of the option product either of any real time .](https://i.sstatic.net/fkuPsE6t.jpg)

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.