Whatsapp user initiated calling not working on live

I’ve implemented WhatsApp user initiated calling using WebRTC, nodeJS, ReactJS. Its working properly on local but not working on live, the i’m getting on both ends seems similar and normal, But on live the call the is getting auto hungup on after 20 seconds. On local its working Fine. Following is the code to handle user initiated calls from WhatsApp.

I’m following this documentation

https://developers.facebook.com/docs/whatsapp/cloud-api/calling/user-initiated-calls

const handleUserInitiatedCallRequest = async ({ req, res }) => {
  let db;
  try {
    // Extract and validate webhook data
    const change = req.body.entry?.[0]?.changes?.[0] || {};
    const call = change?.value?.calls?.[0] || {};
    const callId = call?.id;
    const receivedSdp = call.session?.sdp;
    const WHATSAPP_BUSINESS_ACCOUNT_ID =
      change.value?.metadata?.phone_number_id;

    let tenantIdRes = await getTenantIdFromUserMetaData(
      `whatsapp_id`,
      WHATSAPP_BUSINESS_ACCOUNT_ID
    );

    tenant_id = tenantIdRes?.tenant_id;

    const io = getIO();

    db = await getUserDBfromRequest({ tenant_id });

    let contactDetails =
      (await getChatUserDetailsById(db, call?.from, {
        platform: "whatsapp",
      })) || {};

    if (!callId || !receivedSdp || !WHATSAPP_BUSINESS_ACCOUNT_ID) {
      console.error("Missing required call data:", {
        callId,
        receivedSdp,
        WHATSAPP_BUSINESS_ACCOUNT_ID,
        chat_user: contactDetails,
      });
      return;
    }

    const callerName = call?.from || "Unknown";
    const callerNumber = call?.from || "Unknown";

    console.log(`Incoming WhatsApp call from ${callerName} (${callerNumber})`);
    io.emit(`whatsapp_calling_incoming_request_${tenant_id}`, {
      callId,
      callerName,
      callerNumber,
      chat_user: contactDetails,
    });

    setActiveCalls({
      callId,
      value: {
        whatsappOfferSdp: receivedSdp,
        phone_number_id: WHATSAPP_BUSINESS_ACCOUNT_ID,
      },
    });

    await initiateWebRTCBridge({ callId });
  } catch (error) {
    if (db) db.destroy();
    console.error("Error handling call request:", getAxiosError(error));
  }
};

async function initiateWebRTCBridge({ callId }) {
  try {
    const { getIO } = require("../../../../../sockets");
    let io = getIO();
    let browserOfferSdp = activeCalls[callId]?.browserOfferSdp;
    let whatsappOfferSdp = activeCalls[callId]?.whatsappOfferSdp;
    let browserSocket = io;
    let whatsappPc = activeCalls[callId]?.whatsappPc;
    if (!browserOfferSdp || !whatsappOfferSdp || !browserSocket) return;

    // --- Setup browser peer connection ---
    activeCalls[callId].browserPc = new RTCPeerConnection({
      iceServers: STUN_SERVERS,
    });
    activeCalls[callId].browserStream = new MediaStream();

    activeCalls[callId].browserPc.ontrack = (event) => {
      console.log("Audio track received from browser.");
      event.streams[0]
        .getTracks()
        .forEach((track) => activeCalls[callId].browserStream.addTrack(track));
    };

    activeCalls[callId].browserPc.onicecandidate = (event) => {
      if (event.candidate) {
        browserSocket.emit("whatsapp-calling-browser-candidate", {
          candidate: event.candidate,
          callId,
        });
      }
    };

    try {
      sdpTransform.parse(sanitizeSdp(browserOfferSdp)); // pinpoints the bad line
    } catch (e) {
      console.error("Bad SDP from browser:", e);
    }

    const cleanSdp = sanitizeSdp(browserOfferSdp);

    await activeCalls[callId].browserPc.setRemoteDescription(
      new RTCSessionDescription(cleanSdp, "offer")
    );
    console.log("Browser offer SDP set as remote description.");

    // --- Setup WhatsApp peer connection ---
    whatsappPc = new RTCPeerConnection({ iceServers: STUN_SERVERS });

    const waTrackPromise = new Promise((resolve, reject) => {
      const timeout = setTimeout(
        () => reject("Timed out waiting for WhatsApp track"),
        10000
      );
      whatsappPc.ontrack = (event) => {
        clearTimeout(timeout);
        console.log("Audio track received from WhatsApp.");
        whatsappStream = event.streams[0];
        resolve();
      };

      whatsappPc.onicecandidate = (e) => {
        console.log(
          "[WA-PC] cand:",
          e.candidate?.candidate || "end-of-candidates"
        );
      };
      whatsappPc.oniceconnectionstatechange = () => {
        console.log("[WA-PC] ice:", whatsappPc.iceConnectionState);
      };
    });

    await whatsappPc.setRemoteDescription(
      new RTCSessionDescription(whatsappOfferSdp, "offer")
    );
    console.log("WhatsApp offer SDP set as remote description.");

    // Forward browser mic to WhatsApp
    activeCalls[callId].browserStream?.getAudioTracks().forEach((track) => {
      console.log(track, "<<<<<<<<<<<<<<<< sending track to whatsapp");
      whatsappPc.addTrack(track, activeCalls[callId].browserStream);
    });
    console.log("Forwarded browser audio to WhatsApp.");

    // Wait for WhatsApp to send audio
    await waTrackPromise;

    // Forward WhatsApp audio to browser
    whatsappStream?.getAudioTracks().forEach((track) => {
      console.log(track, "<<<<<<<<<<<<<<<< sending track to browser");
      activeCalls[callId].browserPc.addTrack(track, whatsappStream);
    });

    // --- Create SDP answers for both peers ---
    const browserAnswer = await activeCalls[callId].browserPc.createAnswer();
    await activeCalls[callId].browserPc.setLocalDescription(browserAnswer);
    browserSocket.emit("whatsapp-calling-browser-answer", {
      sdp: browserAnswer.sdp,
      callId,
    });
    console.log("Browser answer SDP created and sent.");

    const waAnswer = await whatsappPc.createAnswer();
    await whatsappPc.setLocalDescription(waAnswer);
    const finalWaSdp = waAnswer.sdp.replace(
      "a=setup:actpass",
      "a=setup:active"
    );
    console.log("WhatsApp answer SDP prepared.");

    // Send pre-accept, and only proceed with accept if successful
    const preAcceptSuccess = await answerCallToWhatsApp(
      callId,
      finalWaSdp,
      "pre_accept",
      activeCalls?.[callId]?.phone_number_id
    );

    if (preAcceptSuccess) {
      await Promise.race([
        new Promise((r) => {
          const h = () => {
            if (
              ["connected", "completed"].includes(whatsappPc.iceConnectionState)
            ) {
              whatsappPc.removeEventListener("iceconnectionstatechange", h);
              r(true);
            }
          };
          whatsappPc.addEventListener("iceconnectionstatechange", h);
        }),
        waitForOutboundAudio(whatsappPc, 12000),
        new Promise((r) => setTimeout(r, 12000)),
      ]);

      setTimeout(async () => {
        const acceptSuccess = await answerCallToWhatsApp(
          callId,
          finalWaSdp,
          "accept",
          activeCalls?.[callId]?.phone_number_id
        );
        if (acceptSuccess && browserSocket) {
          browserSocket.emit("start-browser-timer");
        }
      }, 10);
    } else {
      console.error("Pre-accept failed. Aborting accept step.");
    }

    // Reset session state
    browserOfferSdp = null;
    whatsappOfferSdp = null;
  } catch (error) {
    console.error("Error initiating WebRTC bridge:", getAxiosError(error));
  }
}


async function waitForOutboundAudio(pc, ms = 12000) {
  const start = Date.now();
  while (Date.now() - start < ms) {
    const stats = await pc.getStats();
    for (const r of stats.values()) {
      if (
        r.type === "outbound-rtp" &&
        r.kind === "audio" &&
        (r.packetsSent || r.bytesSent)
      )
        return true;
    }
    await new Promise((r) => setTimeout(r, 300));
  }
  return false;
}

Following are the logs from live server

Incoming WhatsApp call from 9180975xxxxx (9180975xxxxx)
Received SDP offer from browser.
Audio track received from browser.
Browser offer SDP set as remote description.
Audio track received from WhatsApp.
WhatsApp offer SDP set as remote description.
MediaStreamTrack {
 _events: [Object: null prototype] {},
 _eventsCount: 0,
 _maxListeners: undefined,
 addEventListener: [Function: value],
 removeEventListener: [Function: value],
 uuid: 'c156af26-535c-492d-a2b1-75bcf2859547',
 streamId: '50ae6e2f-314a-45f4-9fd1-2b8ce773f552',
 remote: true,
 label: 'remote audio',
 kind: 'audio',
 id: 'c0e95fa9-f787-48a3-a31e-8e5f1a26beea',
 ssrc: 77909064,
 rid: undefined,
 header: undefined,
 codec: RTCRtpCodecParameters {
  payloadType: 111,
  mimeType: 'audio/opus',
  clockRate: 48000,
  channels: 2,
  rtcpFeedback: [ [RTCRtcpFeedback] ],
  parameters: 'minptime=10;useinbandfec=1',
  direction: 'all'
 },
 enabled: true,
 onReceiveRtp: Event {
  event: { stack: [Array], promiseStack: [], eventId: 1 },
  ended: false,
  onended: undefined,
  onerror: [Function: value],
  execute: [Function: value],
  complete: [Function: value],
  error: [Function: value],
  allUnsubscribe: [Function: value],
  subscribe: [Function: value],
  queuingSubscribe: [Function: value],
  once: [Function: value],
  watch: [Function: value],
  asPromise: [Function: value]
 },
 onReceiveRtcp: Event {
  event: { stack: [], promiseStack: [], eventId: 0 },
  ended: false,
  onended: undefined,
  onerror: [Function: value],
  execute: [Function: value],
  complete: [Function: value],
  error: [Function: value],
  allUnsubscribe: [Function: value],
  subscribe: [Function: value],
  queuingSubscribe: [Function: value],
  once: [Function: value],
  watch: [Function: value],
  asPromise: [Function: value]
 },
 onSourceChanged: Event {
  event: { stack: [], promiseStack: [], eventId: 0 },
  ended: false,
  onended: undefined,
  onerror: [Function: value],
  execute: [Function: value],
  complete: [Function: value],
  error: [Function: value],
  allUnsubscribe: [Function: value],
  subscribe: [Function: value],
  queuingSubscribe: [Function: value],
  once: [Function: value],
  watch: [Function: value],
  asPromise: [Function: value]
 },
 stopped: false,
 muted: true,
 stop: [Function: value],
 writeRtp: [Function: value],
 Symbol(shapeMode): false,
 Symbol(kCapture): false
} <<<<<<<<<<<<<<<< sending track to whatsapp
Forwarded browser audio to WhatsApp.
MediaStreamTrack {
 _events: [Object: null prototype] {},
 _eventsCount: 0,
 _maxListeners: undefined,
 addEventListener: [Function: value],
 removeEventListener: [Function: value],
 uuid: '58d00710-efb0-46d8-826c-2d6bdc94e0e2',
 streamId: undefined,
 remote: true,
 label: 'remote audio',
 kind: 'audio',
 id: '23ce1f0e-c369-41c0-8086-bdc092f1daa6',
 ssrc: 2795528785,
 rid: undefined,
 header: undefined,
 codec: RTCRtpCodecParameters {
  payloadType: 111,
  mimeType: 'audio/opus',
  clockRate: 48000,
  channels: 2,
  rtcpFeedback: [ [RTCRtcpFeedback] ],
  parameters: 'maxaveragebitrate=20000;maxplaybackrate=16000;minptime=20;sprop-maxcapturerate=16000;useinbandfec=1',
  direction: 'all'
 },
 enabled: true,
 onReceiveRtp: Event {
  event: { stack: [Array], promiseStack: [], eventId: 1 },
  ended: false,
  onended: undefined,
  onerror: [Function: value],
  execute: [Function: value],
  complete: [Function: value],
  error: [Function: value],
  allUnsubscribe: [Function: value],
  subscribe: [Function: value],
  queuingSubscribe: [Function: value],
  once: [Function: value],
  watch: [Function: value],
  asPromise: [Function: value]
 },
 onReceiveRtcp: Event {
  event: { stack: [], promiseStack: [], eventId: 0 },
  ended: false,
  onended: undefined,
  onerror: [Function: value],
  execute: [Function: value],
  complete: [Function: value],
  error: [Function: value],
  allUnsubscribe: [Function: value],
  subscribe: [Function: value],
  queuingSubscribe: [Function: value],
  once: [Function: value],
  watch: [Function: value],
  asPromise: [Function: value]
 },
 onSourceChanged: Event {
  event: { stack: [], promiseStack: [], eventId: 0 },
  ended: false,
  onended: undefined,
  onerror: [Function: value],
  execute: [Function: value],
  complete: [Function: value],
  error: [Function: value],
  allUnsubscribe: [Function: value],
  subscribe: [Function: value],
  queuingSubscribe: [Function: value],
  once: [Function: value],
  watch: [Function: value],
  asPromise: [Function: value]
 },
 stopped: false,
 muted: true,
 stop: [Function: value],
 writeRtp: [Function: value],
 Symbol(shapeMode): false,
 Symbol(kCapture): false
} <<<<<<<<<<<<<<<< sending track to browser
Received browser-candidate from browser. {
 candidate: 'candidate:1995127518 1 udp 2122260223 192.168.1.17 62065 typ host generation 0 ufrag PKLr network-id 1 network-cost 10',
 sdpMid: '0',
 sdpMLineIndex: 0,
 usernameFragment: 'PKLr'
} wacid.HBgMOTE4MDk3NTM0Njg0FQIAEhggOUU2RUM3QzM0NkQzQ0ZGRjZBNzk1NEE5NDY4OUVCOUYcGAw5MTkxNjk5NTk5NTkVAgAVAgA=
Received browser-candidate from browser. {
 candidate: 'candidate:884647390 1 udp 41885695 172.237.33.131 41118 typ relay raddr 116.72.105.227 rport 62065 generation 0 ufrag PKLr network-id 1 network-cost 10',
 sdpMid: '0',
 sdpMLineIndex: 0,
 usernameFragment: 'PKLr'
} wacid.HBgMOTE4MDk3NTM0Njg0FQIAEhggOUU2RUM3QzM0NkQzQ0ZGRjZBNzk1NEE5NDY4OUVCOUYcGAw5MTkxNjk5NTk5NTkVAgAVAgA=
Received browser-candidate from browser. {
 candidate: 'candidate:136632390 1 tcp 1518280447 192.168.1.17 9 typ host tcptype active generation 0 ufrag PKLr network-id 1 network-cost 10',
 sdpMid: '0',
 sdpMLineIndex: 0,
 usernameFragment: 'PKLr'
} wacid.HBgMOTE4MDk3NTM0Njg0FQIAEhggOUU2RUM3QzM0NkQzQ0ZGRjZBNzk1NEE5NDY4OUVCOUYcGAw5MTkxNjk5NTk5NTkVAgAVAgA=
Browser answer SDP created and sent.
Received browser-candidate from browser. {
 candidate: 'candidate:907102327 1 udp 1686052607 116.72.105.227 62065 typ srflx raddr 192.168.1.17 rport 62065 generation 0 ufrag PKLr network-id 1 network-cost 10',
 sdpMid: '0',
 sdpMLineIndex: 0,
 usernameFragment: 'PKLr'
} wacid.HBgMOTE4MDk3NTM0Njg0FQIAEhggOUU2RUM3QzM0NkQzQ0ZGRjZBNzk1NEE5NDY4OUVCOUYcGAw5MTkxNjk5NTk5NTkVAgAVAgA=
WhatsApp answer SDP prepared.
WhatsApp API Pre-accept Request Payload: {
 url: 'https://graph.facebook.com/v21.0/115017851530658/calls',
 method: 'POST',
 data: {
  messaging_product: 'whatsapp',
  call_id: 'wacid.HBgMOTE4MDk3NTM0Njg0FQIAEhggOUU2RUM3QzM0NkQzQ0ZGRjZBNzk1NEE5NDY4OUVCOUYcGAw5MTkxNjk5NTk5NTkVAgAVAgA=',
  action: 'pre_accept',
  session: {
   sdp_type: 'answer',
   sdp: 'v=0rn' +
    'o=- 59754882 0 IN IP4 0.0.0.0rn' +
    's=-rn' +
    't=0 0rn' +
    'a=group:BUNDLE audiorn' +
    'a=extmap-allow-mixedrn' +
    'a=msid-semantic:WMS *rn' +
    'm=audio 9 UDP/TLS/RTP/SAVPF 111rn' +
    'c=IN IP4 0.0.0.0rn' +
    'a=ice-ufrag:1ba9rn' +
    'a=ice-pwd:1b204f1058a8ccc5068917rn' +
    'a=ice-options:tricklern' +
    'a=fingerprint:sha-256 18:21:C7:E5:37:E6:AE:D3:DE:C5:DE:08:55:9D:13:CE:F8:46:AE:0F:54:18:84:00:A6:5C:60:FE:DE:C1:8D:A6rn' +
    'a=setup:activern' +
    'a=sendrecvrn' +
    'a=mid:audiorn' +
    'a=msid:7e5d7d63-467d-4bd2-b41c-8d08d08b1220 23ce1f0e-c369-41c0-8086-bdc092f1daa6rn' +
    'a=rtcp:9 IN IP4 0.0.0.0rn' +
    'a=rtcp-muxrn' +
    'a=ssrc:4129580503 cname:c6837d9e-0285-4fb2-81ac-f684d95751a9rn' +
    'a=rtpmap:111 opus/48000/2rn' +
    'a=rtcp-fb:111 transport-ccrn' +
    'a=fmtp:111 maxaveragebitrate=20000;maxplaybackrate=16000;minptime=20;sprop-maxcapturerate=16000;useinbandfec=1rn'
  }
 }
}
Pre-accept API Response: { messaging_product: 'whatsapp', success: true }
WhatsApp API Pre-accept Request Payload: {
 url: 'https://graph.facebook.com/v21.0/115017851530658/calls',
 method: 'POST',
 data: {
  messaging_product: 'whatsapp',
  call_id: 'wacid.HBgMOTE4MDk3NTM0Njg0FQIAEhggOUU2RUM3QzM0NkQzQ0ZGRjZBNzk1NEE5NDY4OUVCOUYcGAw5MTkxNjk5NTk5NTkVAgAVAgA=',
  action: 'accept',
  session: {
   sdp_type: 'answer',
   sdp: 'v=0rn' +
    'o=- 59754882 0 IN IP4 0.0.0.0rn' +
    's=-rn' +
    't=0 0rn' +
    'a=group:BUNDLE audiorn' +
    'a=extmap-allow-mixedrn' +
    'a=msid-semantic:WMS *rn' +
    'm=audio 9 UDP/TLS/RTP/SAVPF 111rn' +
    'c=IN IP4 0.0.0.0rn' +
    'a=ice-ufrag:1ba9rn' +
    'a=ice-pwd:1b204f1058a8ccc5068917rn' +
    'a=ice-options:tricklern' +
    'a=fingerprint:sha-256 18:21:C7:E5:37:E6:AE:D3:DE:C5:DE:08:55:9D:13:CE:F8:46:AE:0F:54:18:84:00:A6:5C:60:FE:DE:C1:8D:A6rn' +
    'a=setup:activern' +
    'a=sendrecvrn' +
    'a=mid:audiorn' +
    'a=msid:7e5d7d63-467d-4bd2-b41c-8d08d08b1220 23ce1f0e-c369-41c0-8086-bdc092f1daa6rn' +
    'a=rtcp:9 IN IP4 0.0.0.0rn' +
    'a=rtcp-muxrn' +
    'a=ssrc:4129580503 cname:c6837d9e-0285-4fb2-81ac-f684d95751a9rn' +
    'a=rtpmap:111 opus/48000/2rn' +
    'a=rtcp-fb:111 transport-ccrn' +
    'a=fmtp:111 maxaveragebitrate=20000;maxplaybackrate=16000;minptime=20;sprop-maxcapturerate=16000;useinbandfec=1rn'
  }
 }
}
Pre-accept API Response: { messaging_product: 'whatsapp', success: true }

Following are the logs i’m getting on local

Incoming WhatsApp call from 91809xxxxxxx (91809xxxxxxx)
Received SDP offer from browser.
Audio track received from browser.
Browser offer SDP set as remote description.
Audio track received from WhatsApp.
WhatsApp offer SDP set as remote description.
MediaStreamTrack {
 _events: [Object: null prototype] {},
 _eventsCount: 0,
 _maxListeners: undefined,
 addEventListener: [Function: value],
 removeEventListener: [Function: value],
 uuid: 'c156af26-535c-492d-a2b1-75bcf2859547',
 streamId: '50ae6e2f-314a-45f4-9fd1-2b8ce773f552',
 remote: true,
 label: 'remote audio',
 kind: 'audio',
 id: 'c0e95fa9-f787-48a3-a31e-8e5f1a26beea',
 ssrc: 77909064,
 rid: undefined,
 header: undefined,
 codec: RTCRtpCodecParameters {
  payloadType: 111,
  mimeType: 'audio/opus',
  clockRate: 48000,
  channels: 2,
  rtcpFeedback: [ [RTCRtcpFeedback] ],
  parameters: 'minptime=10;useinbandfec=1',
  direction: 'all'
 },
 enabled: true,
 onReceiveRtp: Event {
  event: { stack: [Array], promiseStack: [], eventId: 1 },
  ended: false,
  onended: undefined,
  onerror: [Function: value],
  execute: [Function: value],
  complete: [Function: value],
  error: [Function: value],
  allUnsubscribe: [Function: value],
  subscribe: [Function: value],
  queuingSubscribe: [Function: value],
  once: [Function: value],
  watch: [Function: value],
  asPromise: [Function: value]
 },
 onReceiveRtcp: Event {
  event: { stack: [], promiseStack: [], eventId: 0 },
  ended: false,
  onended: undefined,
  onerror: [Function: value],
  execute: [Function: value],
  complete: [Function: value],
  error: [Function: value],
  allUnsubscribe: [Function: value],
  subscribe: [Function: value],
  queuingSubscribe: [Function: value],
  once: [Function: value],
  watch: [Function: value],
  asPromise: [Function: value]
 },
 onSourceChanged: Event {
  event: { stack: [], promiseStack: [], eventId: 0 },
  ended: false,
  onended: undefined,
  onerror: [Function: value],
  execute: [Function: value],
  complete: [Function: value],
  error: [Function: value],
  allUnsubscribe: [Function: value],
  subscribe: [Function: value],
  queuingSubscribe: [Function: value],
  once: [Function: value],
  watch: [Function: value],
  asPromise: [Function: value]
 },
 stopped: false,
 muted: true,
 stop: [Function: value],
 writeRtp: [Function: value],
 Symbol(shapeMode): false,
 Symbol(kCapture): false
} <<<<<<<<<<<<<<<< sending track to whatsapp
Forwarded browser audio to WhatsApp.
MediaStreamTrack {
 _events: [Object: null prototype] {},
 _eventsCount: 0,
 _maxListeners: undefined,
 addEventListener: [Function: value],
 removeEventListener: [Function: value],
 uuid: '58d00710-efb0-46d8-826c-2d6bdc94e0e2',
 streamId: undefined,
 remote: true,
 label: 'remote audio',
 kind: 'audio',
 id: '23ce1f0e-c369-41c0-8086-bdc092f1daa6',
 ssrc: 2795528785,
 rid: undefined,
 header: undefined,
 codec: RTCRtpCodecParameters {
  payloadType: 111,
  mimeType: 'audio/opus',
  clockRate: 48000,
  channels: 2,
  rtcpFeedback: [ [RTCRtcpFeedback] ],
  parameters: 'maxaveragebitrate=20000;maxplaybackrate=16000;minptime=20;sprop-maxcapturerate=16000;useinbandfec=1',
  direction: 'all'
 },
 enabled: true,
 onReceiveRtp: Event {
  event: { stack: [Array], promiseStack: [], eventId: 1 },
  ended: false,
  onended: undefined,
  onerror: [Function: value],
  execute: [Function: value],
  complete: [Function: value],
  error: [Function: value],
  allUnsubscribe: [Function: value],
  subscribe: [Function: value],
  queuingSubscribe: [Function: value],
  once: [Function: value],
  watch: [Function: value],
  asPromise: [Function: value]
 },
 onReceiveRtcp: Event {
  event: { stack: [], promiseStack: [], eventId: 0 },
  ended: false,
  onended: undefined,
  onerror: [Function: value],
  execute: [Function: value],
  complete: [Function: value],
  error: [Function: value],
  allUnsubscribe: [Function: value],
  subscribe: [Function: value],
  queuingSubscribe: [Function: value],
  once: [Function: value],
  watch: [Function: value],
  asPromise: [Function: value]
 },
 onSourceChanged: Event {
  event: { stack: [], promiseStack: [], eventId: 0 },
  ended: false,
  onended: undefined,
  onerror: [Function: value],
  execute: [Function: value],
  complete: [Function: value],
  error: [Function: value],
  allUnsubscribe: [Function: value],
  subscribe: [Function: value],
  queuingSubscribe: [Function: value],
  once: [Function: value],
  watch: [Function: value],
  asPromise: [Function: value]
 },
 stopped: false,
 muted: true,
 stop: [Function: value],
 writeRtp: [Function: value],
 Symbol(shapeMode): false,
 Symbol(kCapture): false
} <<<<<<<<<<<<<<<< sending track to browser
Received browser-candidate from browser. {
 candidate: 'candidate:1995127518 1 udp 2122260223 192.168.1.17 62065 typ host generation 0 ufrag PKLr network-id 1 network-cost 10',
 sdpMid: '0',
 sdpMLineIndex: 0,
 usernameFragment: 'PKLr'
} wacid.HBgMOTE4MDk3NTM0Njg0FQIAEhggOUU2RUM3QzM0NkQzQ0ZGRjZBNzk1NEE5NDY4OUVCOUYcGAw5MTkxNjk5NTk5NTkVAgAVAgA=
Received browser-candidate from browser. {
 candidate: 'candidate:884647390 1 udp 41885695 172.237.33.131 41118 typ relay raddr 116.72.105.227 rport 62065 generation 0 ufrag PKLr network-id 1 network-cost 10',
 sdpMid: '0',
 sdpMLineIndex: 0,
 usernameFragment: 'PKLr'
} wacid.HBgMOTE4MDk3NTM0Njg0FQIAEhggOUU2RUM3QzM0NkQzQ0ZGRjZBNzk1NEE5NDY4OUVCOUYcGAw5MTkxNjk5NTk5NTkVAgAVAgA=
Received browser-candidate from browser. {
 candidate: 'candidate:136632390 1 tcp 1518280447 192.168.1.17 9 typ host tcptype active generation 0 ufrag PKLr network-id 1 network-cost 10',
 sdpMid: '0',
 sdpMLineIndex: 0,
 usernameFragment: 'PKLr'
} wacid.HBgMOTE4MDk3NTM0Njg0FQIAEhggOUU2RUM3QzM0NkQzQ0ZGRjZBNzk1NEE5NDY4OUVCOUYcGAw5MTkxNjk5NTk5NTkVAgAVAgA=
Browser answer SDP created and sent.
Received browser-candidate from browser. {
 candidate: 'candidate:907102327 1 udp 1686052607 116.72.105.227 62065 typ srflx raddr 192.168.1.17 rport 62065 generation 0 ufrag PKLr network-id 1 network-cost 10',
 sdpMid: '0',
 sdpMLineIndex: 0,
 usernameFragment: 'PKLr'
} wacid.HBgMOTE4MDk3NTM0Njg0FQIAEhggOUU2RUM3QzM0NkQzQ0ZGRjZBNzk1NEE5NDY4OUVCOUYcGAw5MTkxNjk5NTk5NTkVAgAVAgA=
WhatsApp answer SDP prepared.
WhatsApp API Pre-accept Request Payload: {
 url: 'https://graph.facebook.com/v21.0/115017851530658/calls',
 method: 'POST',
 data: {
  messaging_product: 'whatsapp',
  call_id: 'wacid.HBgMOTE4MDk3NTM0Njg0FQIAEhggOUU2RUM3QzM0NkQzQ0ZGRjZBNzk1NEE5NDY4OUVCOUYcGAw5MTkxNjk5NTk5NTkVAgAVAgA=',
  action: 'pre_accept',
  session: {
   sdp_type: 'answer',
   sdp: 'v=0rn' +
    'o=- 59754882 0 IN IP4 0.0.0.0rn' +
    's=-rn' +
    't=0 0rn' +
    'a=group:BUNDLE audiorn' +
    'a=extmap-allow-mixedrn' +
    'a=msid-semantic:WMS *rn' +
    'm=audio 9 UDP/TLS/RTP/SAVPF 111rn' +
    'c=IN IP4 0.0.0.0rn' +
    'a=ice-ufrag:1ba9rn' +
    'a=ice-pwd:1b204f1058a8ccc5068917rn' +
    'a=ice-options:tricklern' +
    'a=fingerprint:sha-256 18:21:C7:E5:37:E6:AE:D3:DE:C5:DE:08:55:9D:13:CE:F8:46:AE:0F:54:18:84:00:A6:5C:60:FE:DE:C1:8D:A6rn' +
    'a=setup:activern' +
    'a=sendrecvrn' +
    'a=mid:audiorn' +
    'a=msid:7e5d7d63-467d-4bd2-b41c-8d08d08b1220 23ce1f0e-c369-41c0-8086-bdc092f1daa6rn' +
    'a=rtcp:9 IN IP4 0.0.0.0rn' +
    'a=rtcp-muxrn' +
    'a=ssrc:4129580503 cname:c6837d9e-0285-4fb2-81ac-f684d95751a9rn' +
    'a=rtpmap:111 opus/48000/2rn' +
    'a=rtcp-fb:111 transport-ccrn' +
    'a=fmtp:111 maxaveragebitrate=20000;maxplaybackrate=16000;minptime=20;sprop-maxcapturerate=16000;useinbandfec=1rn'
  }
 }
}
Pre-accept API Response: { messaging_product: 'whatsapp', success: true }
WhatsApp API Pre-accept Request Payload: {
 url: 'https://graph.facebook.com/v21.0/115017851530658/calls',
 method: 'POST',
 data: {
  messaging_product: 'whatsapp',
  call_id: 'wacid.HBgMOTE4MDk3NTM0Njg0FQIAEhggOUU2RUM3QzM0NkQzQ0ZGRjZBNzk1NEE5NDY4OUVCOUYcGAw5MTkxNjk5NTk5NTkVAgAVAgA=',
  action: 'accept',
  session: {
   sdp_type: 'answer',
   sdp: 'v=0rn' +
    'o=- 59754882 0 IN IP4 0.0.0.0rn' +
    's=-rn' +
    't=0 0rn' +
    'a=group:BUNDLE audiorn' +
    'a=extmap-allow-mixedrn' +
    'a=msid-semantic:WMS *rn' +
    'm=audio 9 UDP/TLS/RTP/SAVPF 111rn' +
    'c=IN IP4 0.0.0.0rn' +
    'a=ice-ufrag:1ba9rn' +
    'a=ice-pwd:1b204f1058a8ccc5068917rn' +
    'a=ice-options:tricklern' +
    'a=fingerprint:sha-256 18:21:C7:E5:37:E6:AE:D3:DE:C5:DE:08:55:9D:13:CE:F8:46:AE:0F:54:18:84:00:A6:5C:60:FE:DE:C1:8D:A6rn' +
    'a=setup:activern' +
    'a=sendrecvrn' +
    'a=mid:audiorn' +
    'a=msid:7e5d7d63-467d-4bd2-b41c-8d08d08b1220 23ce1f0e-c369-41c0-8086-bdc092f1daa6rn' +
    'a=rtcp:9 IN IP4 0.0.0.0rn' +
    'a=rtcp-muxrn' +
    'a=ssrc:4129580503 cname:c6837d9e-0285-4fb2-81ac-f684d95751a9rn' +
    'a=rtpmap:111 opus/48000/2rn' +
    'a=rtcp-fb:111 transport-ccrn' +
    'a=fmtp:111 maxaveragebitrate=20000;maxplaybackrate=16000;minptime=20;sprop-maxcapturerate=16000;useinbandfec=1rn'
  }
 }
}
Pre-accept API Response: { messaging_product: 'whatsapp', success: true }

How can I make my Ajax add_to_cart work in nested async function

I have out of stock products for which customer can place a preorder. Initialy I am doing this by creating a duplicate product through ajax request by sending product id. Then i am adding this duplicate product (using its id) after response is received by changing add_to_cart button href (the old way). This works correctly. However I wanted to add a notification that shows after checking if the product is in the cart. Could not do that using this method as page reloads in this case. Here is the first working method:

        if(e.target.closest("button[name='pre-order-confirmation-btn']")){
            e.preventDefault()
            e.target.setAttribute('disabled', '')
            const notif_wrapper_in_use = document.querySelector('.stock-notif- wrapper.in-use')
            let btn_clicked = e.target.closest("button[name='pre-order-confirmation-btn']")
            let value = btn_clicked.value
            let a_add_to_cart = notif_wrapper_in_use.querySelector(`a[data-type = ${value}]`)
            let product_id = btn_clicked.dataset.productId
            send_request_create_preorder_product(make_duplicate_preorder_object.ajax_url,'post', product_id, a_add_to_cart, btn_clicked )       
        }

async function send_request_create_preorder_product(url, post, product_id, add_to_cart_btn, btn_clicked){
    let formData = new FormData()
    formData.append('action','send_product_id_for_preorder');
    formData.append('product_id', product_id);
    const options = {
        method : post,
        body : formData
    }
    const response = await fetch(url, options)
    const result = await response.text()
    console.log(result)
    add_to_cart_btn.href = `?add-to-cart=${result}`
    add_to_cart_btn.classList.remove('hide')
    btn_clicked.style.display = 'none'

    /* Here I wanted to add the notification if product is indeed added to cart*/

    if(document.body.classList.contains('home') /*and product in cart*/){
        
/*show successful notification*/
    }

/* ajax function using using hooks wp_ajax and wp_ajax_nopriv*/

function ajax_create_duplicate_product_preorder(){
    if(isset($_POST['action']) && $_POST['action'] === 'send_product_id_for_preorder'){
        $original_product_id = $_POST['product_id'];
        $duplicate_product_id = add_notif_duplicate_wc_product($original_product_id);
        echo $duplicate_product_id;

    }
    wp_die();
}

function add_notif_duplicate_wc_product($product_id){
    $original_product = wc_get_product($product_id);
    if(!$original_product){
        return new WP_Error('invalid_product', 'Invalid product ID.');
    }
    $duplicate_product = new WC_Product_Simple();
    $duplicate_product->set_name($original_product->get_name().'-precommande');
    $duplicate_product->set_status('publish');
    $duplicate_product->set_description($original_product->get_description());
    $duplicate_product->set_short_description($original_product->get_short_description());
    $duplicate_product->set_price($original_product->get_price());
    $duplicate_product->set_regular_price($original_product->get_regular_price());
    $duplicate_product->set_sale_price($original_product->get_sale_price());
    $duplicate_product->set_catalog_visibility('hidden');
    $duplicate_product->set_manage_stock(false);
    $duplicate_product->set_stock_status('instock');
    $duplicate_product->set_virtual(true);
    $duplicate_product_id = $duplicate_product->save();
    $duplicate_product->set_name($duplicate_product->get_name() . '-' . $duplicate_product_id);
    $duplicate_product->save();
    wp_set_object_terms($duplicate_product_id,'preorders', 'product_cat', false);
    $thumbnail_id = get_post_thumbnail_id($product_id);
    if($thumbnail_id){
        set_post_thumbnail($duplicate_product_id, $thumbnail_id);
    }
    // echo $duplicate_product_id;
    return $duplicate_product_id;
}

so as an alternative I tried using this modified async function and woocommerce woocommerce_ajax_add_to_cart (I also removed the ‘post’ parameter in the above async function when called)

async function send_request_create_preorder_product(url, product_id, add_to_cart_btn, btn_clicked) {
    // Create the preorder product via AJAX
    const formData = new FormData();
    formData.append('action', 'send_product_id_for_preorder');
    formData.append('product_id', product_id);

    const response = await fetch(url, { method: 'POST', body: formData });
    const duplicateProductId = await response.text();
    console.log(duplicateProductId)

    if (!duplicateProductId) return;

    // Update the Add to Cart button
    add_to_cart_btn.dataset.productId = duplicateProductId; // store the new product ID
    add_to_cart_btn.classList.remove('hide');
    btn_clicked.style.display = 'none';

   // AJAX Add-to-Cart
    add_to_cart_btn.addEventListener('click', async (e) => {
        e.preventDefault(); // prevent page reload
        const addFormData = new FormData();
        addFormData.append('action', 'woocommerce_ajax_add_to_cart');
        addFormData.append('product_id', duplicateProductId);
        addFormData.append('quantity', 1);

        const addResponse = await fetch(url, { method: 'POST', body: addFormData });
        const addResult = await addResponse.json();
        console.log(addResult)
        if (addResult && addResult.added) {
            // Show notification
            let notifDiv = document.createElement('div');
            notifDiv.className = 'preorder-notif-div-homepage';
            notifDiv.textContent = 'Le produit précommandé a été ajouté au panier';
            document.body.appendChild(notifDiv);
        }
    }, { once: true });
}

The duplicate product_id is properly console logged but then I have a 400 (Bad Request) error on this line const addResponse = await fetch(url, { method: ‘POST’, body: addFormData });
I have been turning around to solve it for 2 days but did not find any working solution.

and this is the HTML form used

                    <div class='stock-notif-wrapper'>
                        <p><button type='button' name='show-add-notif-btn' data-notify-product-id='". $product->get_id(). "'>Pre-commander 24h</button></p>
                        <div class='stock-notif-form-wrapper'>
                            <div class='pre-order-questions-input'>
                                <label>
                                    <input type='radio' name='".$product->get_id()."-"."preorder-or-question[]' class='inputs radios' value='just-ask-date' checked>
                                    Voulez vous savoir quand le produit sera en stock?
                                </label>
                                <label> 
                                    <input type='radio' name='".$product->get_id()."-"."preorder-or-question[]' class='inputs radios' value='send-preorder'>
                                    Voulez-vous payer et pre commander directement?
                                </label>
                            </div>
                            <form action='' method='POST' data-type = 'just-ask-date'>
                                <div class='stock-notif-error-notif-div'></div>
                                <div id='recaptcha-container-". $product->get_id() ."'></div>
                                <div class='notify-inputs-wrapper'>
                                    <input type='email' name='stock-notif-email' class='inputs' placeholder='Email' required>
                                    <input type='tel' name='stock-notif-tel' class='inputs' placeholder='Mobile ex 22 222 222' required>
                                    <input type='hidden' name='notify_product_id' value='{$product->get_id()}'>
                                </div>
                                <div class='button-wrapper'>
                                    <button type='submit' name='stock-notif-submit-btn'>Envoyer</button>
                                    <button type='button' name='show-add-notif-shown-close-btn'>Fermer</button>
                                </div>
                                <div class='company_wrapper'>
                                    <div class='company-addition'>
                                        <input type='text' name='company-name'>
                                    </div>
                                </div>
                                <p class='info'>Vous recevrez un email ou SMS vous informant de la date de disponibilité sous 48h</p>
                            </form>
                            <div class='preorder-notif hide' data-type = 'send-preorder'>
                                <p class='pre-order-description-text'>Vous Allez payer la totalité du produit et nous vous enverrons votre code sous 24h par email si paiement en ligne ou sous 48h si paiement à la livraison</p>
                                <!--<form action='' metod='POST'>-->
                                <button name='pre-order-confirmation-btn' data-product-id ='".$product->get_id()."' value='pre-order-confirmation-add-to-cart'>Confirmer</button>
                                <!--</form>-->
                                <a href='". esc_url( "?add-to-cart=" . $product->get_id() ) . "' class='button' data-type='pre-order-confirmation-add-to-cart'>Pré-commander 24h</a>"."
                                <button type='button' name='show-add-notif-shown-close-btn'>Fermer</button>
                            </div>
                        </div>
                    </div>
                    ";

How externally fetched CSS can be used or it’s source code can be seen for more targeted use? [closed]

In my recent project I copied a CSS source link in my HTML source. Now, using just given class works in my project. But if we are able to read the code, then it we have some scope to fine tune as per our use. Now I have no idea how traditionally developers do ?

At this point I can only try to find out their documentations which I think is unnecessarily time consuming. Sometime it is difficult to find out documentation as well. My need is to look into the source if possible.

Error when importing HydratedRouter and RouterProvider from react-router/dom in TypeScript project

const router = createBrowserRouter([
  {
    path: "/",
    element: <Welcomepage />,
  },
  {
    path: "/signin",
    element: <Signin />,
  },
]);
C:UserschaitOneDriveDesktopdocument3_Coding4-Popxnode_modulesreact-router-domdistindex.mjs:13:48
12 | // index.ts
13 | import { HydratedRouter, RouterProvider } from "react-router/dom";
  >    |                                                ^^^^^^^^^^^^^^^^^^
14 | export * from "react-router";
15 | export {

@parcel/resolver-default: Cannot load file './dom' from module 'react-router'

I am working on a React project and trying to set up routing.
In my index.js file, I wrote:

import { createBrowserRouter, RouterProvider, Outlet } from "react-router";

But I am getting this error:

Error | // index.js > 13 | import { HydratedRouter, RouterProvider } from "react-router/dom";

I installed react-router-dom using:

npm install react-router-dom

function to match a input in array of JSON [closed]

looking for your help on JavaScript function where I want to pass 2 elements as input and expect a true or false as response back.

Scenario – I will be passing below 2 input in Javascript and need to check if value of variable varTaskID i.e. TaskID3 is matching with ExternalTaskId which is in JSON array structure.. if available return as true as simple say false. SO comparing varTaskID with ExternalTaskId element in complete array file.

Input Id – a variable as varTaskID which will have only 1 value like TaskID3
Input 2(A JSON structure where we have a repetitive structure)

{
  "items" :
  [
    {
      "ExternalParentTaskId" : "12345",
      "ExternalTaskId"       : "TaskID1"
    }, 
    {
      "ExternalParentTaskId" : "11111",
      "ExternalTaskId"       : "TaskID2"
    }, 
    {
      "ExternalParentTaskId" : "3456",
      "ExternalTaskId"       : "TaskID3"
    }, 
    {
      "ExternalParentTaskId" : "423423",
      "ExternalTaskId"       : "TaskID3"
    }, 
    {
      "ExternalParentTaskId" : "55666",
      "ExternalTaskId"       : "TaskID3"
    }
  ]
}

Getting my Filtrable class to work as it should [closed]

I writing a reusable Filtrable trait for laravel projects. I have it working but not perfectly. One part work perfectly why another part not giving expected results

I writing a reusable Filtrable trait for laravel projects. I have it working but not perfectly.
This is my Fliterable trait code

namespace ObrainwaveLaravelQueryFilters;

use IlluminateDatabaseEloquentBuilder;
use IlluminateHttpRequest;

trait Filterable
{
    public function scopeFilter(Builder $query, Request | array | null $filters =     null) : QueryFilter
    {
        $filterClass = $this->getFilterClass();

        if (! class_exists($filterClass)) {
            return $query;
        }

        $filter = (new $filterClass($filters))
            ->setBuilder($query)
            ->apply(); // automatically apply request/array filters

        return $filter; // Now you can chain ->status(...)->role(...)->get()
    }

    protected function getFilterClass(): string
    {
        return str_replace('Models', 'Filters', static::class) . 'Filter';
    }
}

and this is my QueryFilter class code

namespace ObrainwaveLaravelQueryFilters;

use IlluminateDatabaseEloquentBuilder;
use IlluminateHttpRequest;

abstract class QueryFilter
{
    protected Builder|null $builder = null;
    protected array $filters = [];

/**
 * Accept request or array of filters
 */
    public function __construct(Request|array|null $filters = null)
   {
        if ($filters instanceof Request) {
            $this->filters = $filters->all();
        } elseif (is_array($filters)) {
            $this->filters = $filters;
        }
    }

/**
 * Set the query builder instance
 */
    public function setBuilder(Builder $builder): static
    {
        $this->builder = $builder;
        return $this;
    }

/**
 * Apply the filters to the builder
 */
    public function apply(): static
    {
        if (! $this->builder) {
           throw new Exception("Query builder is not set. Call setBuilder() first.");
        }

        foreach ($this->filters as $key => $value) {
            if ($value !== null && method_exists($this, $key)) {
                $this->$key($value);
            }
        }

        return $this;
    }

/**
 * Return the underlying builder for final query
 */
    public function get()
    {
        return $this->builder->get();
    }

    public function first()
    {
        return $this->builder->first();
    }

    public function toSql()
   {
        return $this->builder->toSql();
    }
}

So my problem is that it works when I passed the chaining methods

$users2 = User::filter(['status' => 'inactive', 'role' => 'admin'])
        ->status('inactive')
        ->role('admin')
        ->get();

But it returns all rows when I do this
$users1 = User::filter(request())->get();
which means it doesn’t apply the default filtering

I hope anyone can help me spotting my mistakes.

WordPress – Counter button – Get value from db – display counter value – increment – and update DB [closed]

In a WordPress projet, let say I want my users to click on a button when they aggree with a sentence such as :
If you like pancakes click here <button> YES I DO </button>

Let say I want to display the number of clicks such as :
<nb_click> have allready say yes

Question 1 :
Is there a plugin that does this ?

Question 2 :
I tried to do it with a code snippet (php/javascript ; Elementor short code widget): But I am facing a probleme with the execution evironnement.

PROBLEM : The php code is executed as soon as the web page loads. It looks like the execution environnement had removed the javascript code to execute only php tags. And when the user click on the button, the javascript code is executed (as if it had reappeared). The function counter_increment_and_update_db() is well triggered, but the php code is not executed.

at the end, the incremented value of the counter is always inserted into the database, no matter the button was clicked or not

<?php
    $current_counter_value = get_counter_value_from_db();    
?>

<button onclick="counter_increment_and_update_db()"> OUI </button>
<span id="counting"></span> 

<script>
    html_display_curent_counter_value(<?php echo $current_counter_value; ?>)
    function counter_increment_and_update_db() {
        <?php
            $incremented_value = increment_by_one($current_counter_value);
        ?>
        html_display_curent_counter_value(<?php echo $incremented_value; ?>)
        <?php
            update_db_counter_value($incremented_value);
        ?>
    }
    // -----------------            javascript UTIL FUNCTIONS        ----------------
    function html_display_curent_counter_value(text_to_be_displayed){
        document.getElementById("counting").innerText = text_to_be_displayed;
    }
</script>

<?php
    // --------------------------------------------------------------------
    // -----------------            php UTIL FUNCTIONS        ----------------
    // --------------------------------------------------------------------
    function increment_by_one($value){
        $out=$value+1;
        return $out;
    }
    function get_counter_value_from_db(){
        global $wpdb;
        $wpdb->show_errors();
        $query = "SELECT * FROM click_counter WHERE counter_name = 'sondage1'";
        $resultArray = $wpdb->get_results($query);
        $current_counter_value = get_last_value($resultArray);
        return $current_counter_value;
    }
    function get_last_value($array_of_values){
        $out=0;
        foreach ($array_of_values as $page) {
                $out = $page->counter_value;
        }
        return $out;
    }
    function update_db_counter_value($new_value) {
        global $wpdb;
        $wpdb->show_errors(); 
        $wpdb->update(
            'click_counter',
            array(
                'counter_value' => $new_value   
            ),
            array(
                'counter_name' => 'sondage1'
            ), 
            array( 
                '%d' 
            )
        );
    }
?>

Shuffle a list and keep the repeated values distant [closed]

I have a list of competitors and their respective horses, with each competitor identified by a unique ID, and competitors may be associated with different horses. I need to shuffle the list in such a way that the same competitor does not appear consecutively, ideally maintaining a minimum distance of 5 competitors between repetitions.

Do I need to apply this logic directly in the select query or through PHP.

I considered separating the items into groups and shuffling them, but there is a possibility that an item could be the last of one group and the first of another, causing them to be close together. However, the client requested that the order be shuffled, but items with the same ID cannot be close to each other, ideally with a distance of at least 5 competitors between them.

Why can’t I create a category as an administrator?

Laravel API Redirects to Login Route Despite Valid Bearer Token
I’ve created a REST API with Laravel 12 and I’m using Sanctum for authentication. My routes are defined in the api.php file.

When I try to access an authenticated route from an administrator account using Postman, the request is unexpectedly redirected to the named login route, and I receive the following error:

Missing required parameter for [Route: login] [URI: api/login/{type}] [Missing parameter: type]
*
I am sending a POST request to the following endpoint: /api/administrateur/categories.

I have confirmed that I am including a valid Bearer Token from a successful administrator login in my request headers. The token is valid and should grant me access to the authenticated routes. I believe the issue lies within the Sanctum authentication middleware, as the application seems to be failing to recognize my token and instead redirects me to the login route.

Here is my api.php file for reference:

<?php

use IlluminateHttpRequest;
use IlluminateSupportFacadesRoute;
use AppHttpControllersAuthentificationController;
use AppHttpControllersClientController;
use AppHttpControllersEntrepriseController;
use AppHttpControllersAdministrateurController;
use AppHttpControllersProduitController;
use AppHttpControllersDevisController;
use AppHttpControllersCommentaireController;
use AppHttpControllersCategorieController;


Route::post('/register', [AuthentificationController::class, 'register']);
Route::post('/login/{type}', [AuthentificationController::class, 'login'])->name('login');

Route::get('/categories', [CategorieController::class, 'index']);
Route::get('/categories/{id}', [CategorieController::class, 'show']);
Route::get('/produits', [ProduitController::class, 'index']);
Route::get('/produits/{id}', [ProduitController::class, 'show']);
Route::get('/entreprises', [EntrepriseController::class, 'index']);
Route::get('/entreprises/{id}', [EntrepriseController::class, 'show']);


Route::middleware('auth:sanctum')->group(function () {
    Route::post('/logout', [AuthentificationController::class, 'logout']);

    Route::prefix('client')->middleware('authentification.type:client')->group(function () {
        Route::get('/profile', [ClientController::class, 'showProfile']);
        Route::put('/profile', [ClientController::class, 'updateProfile']);
        
        Route::get('/commentaires', [CommentaireController::class, 'index']);
        Route::post('/commentaires', [CommentaireController::class, 'store']);
        Route::get('/commentaires/{commentaire}', [CommentaireController::class, 'show']);
        Route::put('/commentaires/{commentaire}', [CommentaireController::class, 'update']);
        Route::delete('/commentaires/{commentaire}', [CommentaireController::class, 'destroy']);
        
        Route::get('/devis', [DevisController::class, 'index']);
        Route::post('/devis', [DevisController::class, 'store']);
        Route::get('/devis/{devi}', [DevisController::class, 'show']);
        Route::put('/devis/{devi}', [DevisCont

type here

roller::class, 'update']);
        Route::delete('/devis/{devi}', [DevisController::class, 'destroy']);
    });

    Route::prefix('entreprise')->middleware('authentification.type:entreprise')->group(function () {
        Route::get('/profile', [EntrepriseController::class, 'showProfile']);
        Route::put('/profile', [EntrepriseController::class, 'updateProfile']);
        
        Route::post('/produits', [ProduitController::class, 'store']);
        Route::put('/produits/{produit}', [ProduitController::class, 'update']);
        Route::delete('/produits/{produit}', [ProduitController::class, 'destroy']);
        
        Route::put('/devis/{id}/statut', [DevisController::class, 'updateStatus']);
    });

    Route::prefix('administrateur')->middleware('authentification.type:administrateur')->group(function () {
        Route::get('/dashboard', [AdministrateurController::class, 'dashboardStats']);
        
        Route::post('/entreprises/{id}/valider', [AdministrateurController::class, 'validerEntreprise']);
        Route::post('/entreprises/{id}/refuser', [AdministrateurController::class, 'refuserEntreprise']);
        
        Route::post('/entreprises', [AdministrateurController::class, 'store']);
        Route::get('/entreprises', [AdministrateurController::class, 'indexEntreprises']);
        Route::put('/entreprises/{entreprise}', [AdministrateurController::class, 'update']);
        Route::delete('/entreprises/{entreprise}', [AdministrateurController::class, 'destroy']);
        Route::get('/entreprises/{id}', [AdministrateurController::class, 'show']);

        Route::post('/categories', [CategorieController::class, 'store'])->name('categories.store');
        Route::put('/categories/{categorie}', [CategorieController::class, 'update'])->name('categories.update');
        Route::delete('/categories/{categorie}', [CategorieController::class, 'destroy'])->name('categories.destroy');
    });
});

What could be causing this redirect to the login page, even with a valid token? How can I fix this

Test,body

Laravel Reverb Curl Exception on Laravel Herd

I keep getting this error below

GuzzleHttpExceptionRequestException: cURL error 1: Received HTTP/0.9 when not allowed (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for http://127.0.0.1:8080/apps/local/events?auth_key=local-key&auth_timestamp=1756475569&auth_version=1.0&body_md5=baf98ab9765fdd4ce6328183d1646a5a&auth_signature=2828b9b5b9db7af8f931e42c37b138a9456babfa1ae0df72cb5d395e03a05b40 in /Users/chris/Herd/utility.com/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:278

Below are my .env variables

REVERB_APP_ID=local
REVERB_APP_KEY=local-key
REVERB_APP_SECRET=local-secret
REVERB_PORT=443
REVERB_HOST=utility.com.test
REVERB_SCHEME=https

Java Script function to match a input in array of JSON

looking for your help on Java script function where I want to ass 2 element as input and expect a true or false as response back.

Scenario – I will be passing below 2 input in Java script and need to check if value of variable varTaskID i.e. TaskID3 is matching with ExternalTaskId which is in JSON array structure.. if available return as true as simple say false. SO comparing varTaskID with ExternalTaskId element in complete array file.

Input Id – a variable as varTaskID which will have only 1 value like TaskID3
Input 2(A JSON structure where we have a repetitive structure)

{
  "items" : [ 
  {
    "ExternalParentTaskId" : "12345",
    "ExternalTaskId" : "TaskID1"
  }, 
  {
    "ExternalParentTaskId" : "11111",
    "ExternalTaskId" : "TaskID2"
  }, 
  {
    "ExternalParentTaskId" : "3456",
    "ExternalTaskId" : "TaskID3"
  }, 
  {
    "ExternalParentTaskId" : "423423",
    "ExternalTaskId" : "TaskID3"
  }, 
  {
    "ExternalParentTaskId" : "55666",
    "ExternalTaskId" : "TaskID3"
  }
  ]
}

Restore scroll position in React after navigating back from a dynamic list

I have a car listing page built with React and React Router. Users can scroll down a list of cars, click a car to view its detail page,
and then hit the browser back button.

The problem: when returning, the page always scrolls to the top instead of the position where the user left off.

The list is dynamic: it loads asynchronously from Firestore, shuffles the cars randomly, and supports pagination and filters. Because of this, scroll restoration happens before the list is fully rendered, so the scroll jumps to the top instead of staying at the previous position.

Using window.history.scrollRestoration = “manual”.

A custom SmartScroll component that saves/restores scroll positions.

React Router v6 ScrollRestoration component.

Expected behavior:
When navigating back from a car detail page, the scroll should restore to the exact position the user left off, even with async data loading, shuffling, and pagination.

// src/main.jsx
import React from "react";
import "./index.css";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import SmartScroll from './utils/SmartScroll';
import { CartProvider } from "./context/CartContext";
import { WishlistProvider } from "./context/WishlistContext";
import { AuthProvider } from "./context/AuthContext";
import { SearchProvider } from "./context/SearchContext";
import { FilterProvider } from "./context/FilterContext";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
  <BrowserRouter>
    <SmartScroll />
    <AuthProvider>
      <CartProvider>
        <WishlistProvider>
          <SearchProvider>
            <FilterProvider>
             <App />
            </FilterProvider>
          </SearchProvider>
        </WishlistProvider>
      </CartProvider>
    </AuthProvider>
  </BrowserRouter>
  </React.StrictMode>
);

// src/App/jsx
import { Routes, Route } from "react-router-dom";
import Home from "./pages/Home/Home";
import Cart from "./pages/Cart";
import CarDetails from './pages/CarDetails';
import Navbar from "./components/layout/Navbar";
import Footer from "./components/layout/Footer"; 
import Checkout from "./pages/Checkout"; 
import Wishlist from "./pages/Wishlist";
import Login from "./pages/Login";
import Signup from "./pages/Signup";
import Success from "./pages/Success";
import About from "./pages/About";
import MyOrders from "./pages/MyOrders";
import ReceiptPage from "./pages/ReceiptPage";
import Inventory from "./pages/Inventory";
import SellYourCar from "./pages/Sellcar";
import Profile from "./pages/Profile";
import EditProfile from "./pages/EditProfile";
import SuccessSold from "./pages/SuccessSold";
import Sold from "./pages/Sold";
import { SoldProvider } from "./context/SoldContext";

function App() {
  return (
    <>
      <Navbar />
      <SoldProvider>
        <Routes>
          {/* Public Routes */}
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/cars" element={<Inventory />} />
          <Route path="/cars/:userId/:carId" element={<CarDetails />} /> 
          <Route path="/login" element={<Login />} />
          <Route path="/signup" element={<Signup />} />
          <Route path="/SuccessSold" element={<SuccessSold />} />

          {/* Protected Routes */}
          <Route path="/profile" element={<Profile />} />
          <Route path="/profile/edit" element={<EditProfile />} />
          <Route path="/cart" element={<Cart />} />
          <Route path="/orders" element={<MyOrders />}/>
          <Route path="/checkout" element={<Checkout />}/>
          <Route path="/edit-car/:userId/:carId" element={<SellYourCar />}/>
          <Route path="/sellcar" element={<SellYourCar />}/>
          <Route path="/sold" element={<Sold />}/>
          <Route path="/wishlist" element={<Wishlist />}/>
          <Route path="/receipt" element={<ReceiptPage />}/>
          <Route path="/success" element={<Success />}/>
        </Routes>
      </SoldProvider>
      <Footer />
    </>
  );
}

export default App;

import { useLayoutEffect, useRef } from "react";
import { useLocation, useNavigationType } from "react-router-dom";

function getScroller() {
  return document.scrollingElement || document.documentElement;
}

export default function SmartScroll({ ready = true }) {
  const location = useLocation();
  const navType = useNavigationType();
  const restored = useRef(false);

  useLayoutEffect(() => {
    if ("scrollRestoration" in window.history) {
      window.history.scrollRestoration = "manual";
    }

    const scroller = getScroller();
    const key = `scroll:${location.key}`;

    const restoreScroll = () => {
      const saved = sessionStorage.getItem(key);
      if (saved) {
        const { x = 0, y = 0 } = JSON.parse(saved);
        scroller.scrollTo({ left: x, top: y, behavior: "auto" });
        restored.current = true;
      }
    };

    if (navType === "POP") {
      // Wait until content is ready
      if (ready) {
        restoreScroll();
      } else {
        const interval = setInterval(() => {
          if (ready && !restored.current) {
            restoreScroll();
            clearInterval(interval);
          }
        }, 50);
        return () => clearInterval(interval);
      }
    } else {
      // New page → scroll to top
      scroller.scrollTo({ left: 0, top: 0, behavior: "auto" });
    }

    // Save scroll on unmount
    return () => {
      const s = getScroller();
      sessionStorage.setItem(key, JSON.stringify({ x: s.scrollLeft, y: s.scrollTop }));
    };
  }, [location, navType, ready]);

  return null;
}

import { useEffect, useState, useLayoutEffect, useRef } from "react";
import { collectionGroup, getDocs, query } from "firebase/firestore";
import { db } from "../../firebase/firebase";
import CarCard from "../../components/cars/CarCard";
import Cambodia from "../../assets/images/logo/Cambodia.png";

export default function CarList({ filters = {}, sortOption }) {
  const [cars, setCars] = useState([]);
  const [carsToDisplay, setCarsToDisplay] = useState(12);
  const containerRef = useRef(null); // New: ref for car grid container

  // Fetch cars
  useEffect(() => {
    const fetchCars = async () => {
      const savedCars = sessionStorage.getItem("carsList");
      if (savedCars) {
        setCars(JSON.parse(savedCars));
        return;
      }

      const carsCollection = collectionGroup(db, "cars");
      const q = query(carsCollection);
      const querySnapshot = await getDocs(q);
      const fetchedCars = querySnapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
      const shuffledCars = fetchedCars.sort(() => Math.random() - 0.5);
      setCars(shuffledCars);
      sessionStorage.setItem("carsList", JSON.stringify(shuffledCars));
    };

    fetchCars();
  }, []);

  const filterAndSortCars = () => {
    let result = [...cars];
    const { brand, condition, location, price, search, type, year } = filters;
    result = result.filter((car) => {
      const matchesSearch =
        !search || `${car.name} ${car.model}`.toLowerCase().includes(search.toLowerCase());
      const matchesLocation = location === "All locations" || car.location === location;
      const matchesBrand = brand === "All brands" || car.name === brand;
      const matchesType = type === "All types" || car.type === type;
      const matchesCondition = condition === "All conditions" || car.condition === condition;
      const matchesYear = year === "All years" || car.year === year;
      const matchesPrice =
        price === "No max" || (price && car.price <= parseInt(price.replace(/D/g, ""), 10));
      return (
        matchesSearch &&
        matchesLocation &&
        matchesBrand &&
        matchesType &&
        matchesCondition &&
        matchesYear &&
        matchesPrice
      );
    });

    switch (sortOption) {
      case "price-asc":
        return result.sort((a, b) => a.price - b.price);
      case "price-desc":
        return result.sort((a, b) => b.price - a.price);
      case "year-desc":
        return result.sort((a, b) => b.year - a.year);
      case "year-asc":
        return result.sort((a, b) => a.year - b.year);
      default:
        return result;
    }
  };

  const filteredCars = filterAndSortCars();
  const carsToShow = filteredCars.slice(0, carsToDisplay);
  const handleViewMore = () => setCarsToDisplay((prev) => prev + 8);

  // ✅ Scroll restoration
  useLayoutEffect(() => {
    const key = "cars-scroll"; // fixed key for CarList
    const scroller = document.scrollingElement || document.documentElement;

    const restoreScroll = () => {
      const saved = sessionStorage.getItem(key);
      if (saved) {
        const { x = 0, y = 0 } = JSON.parse(saved);
        scroller.scrollTo({ left: x, top: y, behavior: "auto" });
      }
    };

    // Only restore when grid has been painted
    if (cars.length > 0) {
      restoreScroll();
    }

    return () => {
      sessionStorage.setItem(
        key,
        JSON.stringify({ x: scroller.scrollLeft, y: scroller.scrollTop })
      );
    };
  }, [cars]);

  return (
    <section className="m-6 mx-2 rounded-[2px] bg-card p-3 px-2 md:m-4 md:p-3 sm:px-10 lg:m-6 lg:mx-10 lg:p-4" ref={containerRef}>
      <div className="mx-auto max-w-7xl">
        <div className="mb-5 flex items-center gap-2">
          <h2 className="text-xl font-bold text-gray-800">Cars for Sale</h2>
          <img src={Cambodia} alt="Cambodia Flag" className="h-7 w-10" />
        </div>

        <div className="grid grid-cols-2 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
          {carsToShow.length > 0
            ? carsToShow.map((car) => <CarCard key={car.id} car={car} />)
            : (
              <div className="col-span-full py-10 text-center text-gray-500">
                <p className="text-lg">No cars found matching your criteria.</p>
                <p className="mt-2 text-sm">Try adjusting your filters or search query.</p>
              </div>
            )}
        </div>

        {filteredCars.length > carsToDisplay && (
          <div className="mt-8 text-center">
            <button onClick={handleViewMore} className="font-base text-[#2384C1] underline">
              View More
            </button>
          </div>
        )}
      </div>
    </section>
  );
}

How can we achieve similar drag-n-drop behaviour of speed dials of opera browser for both sort and group?

I was trying to implement similar drag drop behaviour like opera browser’s speed dial.

  1. Group: It creates new group or add speed dial to existing group when we drag a speed dial over other speed dial or group.
  2. sorting with Smart positioning: Unlike typical sorting behaviour by various sorting libraries, opera browser triggers movement when a speed dial dragged to specific gap positions between cards it seems. It does not trigger movement when we drag a speed dial card over another partially or even completly

I tried Sortable.js and Muuri libs but they triggers movement as soon as the pointer entered into other items boundary and this blocks the possibility of grouping or other actions we can add.

if we want to move card 1 to location of card 2 then we need to drag to opposite side gap not adjusent gap

[ card 1 ] [ card 2 ]
[ card 1 ]|gap1|[ card 2 ]|gap2|

card 1 should be dragged to gap2 then only card 2 will pushed to empty area left by card after dragged.

this allows us to make group by not going to that gap and keep the card 1 over card 2 which will trigger creation of new group with card 1 and card 2.

If you can suggest something about it that will be helpful.

vernabulities in the session cookie

Let’s imagine the user forgets to log out and directly closes the application, so the session ID is still in the browser, and the server has an expiry.If it has the expiry, then if the user is logged in and the cookie expired then does they have to relogin or something else?

I just want to know the answer

“Cannot read property ‘computeModPow’ of null”

I am using aws-identity-js in a React Native Expo project, and when I try to sign in with my Cognito user pool credentials, the sign-in process does not work as expected. Instead, it throws the following error: “Cannot read property ‘computeModPow’ of null.” How can I resolve this issue and successfully authenticate users?

give solution to that problem