How is node v20’s File class intended to be used?

Node v20 officially took the File class out of experimental, but I’m not entirely sure how it was intended to be used.

There doesn’t seem to be any exports in the “fs” module that create File objects. I also couldn’t find references to File objects elsewhere in the docs. There is the fs.openAsBlob(), but I don’t see anything that produces an instance of the File subclass.

The reason that I want to try using this is that Files and Blobs can handle more data than what in-process memory (Buffers) can handle. This is possible, because these classes are just references to the raw data in the file system rather than the raw bytes themselves (Buffers).

The idea is that they can be segmented before being loaded into in-process memory. Additionally, it’s method for segmenting (Blob.slice()) can also be shorter than other methods from fs, since you don’t need to respecify arguments like fd and outbuffer (from fs.read()) every time you want to manipulate the file.

For right now, I’m ok with working with fs.openAsBlob() and Blobs, but being able to work with File objects would help simplify my code as it targets both the browser and node.

Limit max number of images uploading to a webpage

I’m designing a website. Currently, I’m trying to get photos from user. I want to limit the maximum number of photos uploaded, say three. I have a input file to browse photos from user computer. And if user uploads more than 3 photos, the browser alerts user max number of photos is 3, the submit button will become disable. However, I cannot disable the button with the code I have. The picture is as follows

//display pictures in browser
document.querySelector("#files").addEventListener("changeE", (e) => {
  if (window.File && window.FileReader && window.FileList && window.Blob) {
    const files = e.target.files;
    const max_file_number = 3;
    const output = document.querySelector("#result");

    for (let i = 0; i < files.length; i++) {
      if (!files[i].type.match("image")) continue;
      const picReader = new FileReader();
      picReader.addEventListener("load", function(event) {
        const picFile = event.target;
        const div = document.createElement("div");
        div.innerHTML = `<img class="thumbnail" src="${picFile.result}" title="${picFile.name}">`;
        output.appendChild(div);
      });
      picReader.readAsDataURL(files[i]);
    }
  } else {
    alert("Your browser does not support the file API");
  }
});

//limit upload to 3 pictures
document.querySelector("#files").addEventListener("change", (e) => {
  const max_file_number = 3;
  const number_of_images = e.target.files.length;
  var button = document.querySelector(".submit-button");
  console.log(number_of_images);
  if (number_of_images > max_file_number) {
    alert(`You can upload maximum ${max_file_number} files.`);
    button.disabled = true;
  } else {
    button.disabled = false;
  }
});
/*upload photo*/
#result {
  display: flex;
  gap: 10px;
  padding: 10px 0;
}

.thumbnail {
  height: 100%;
  width: 100%;
}
<div class="upload-photo">
    <span class="item-title">Upload photo</span>
    <input 
        id="files" 
        type="file" 
        name="picture" 
        accept="image/jpg,image/jpeg,image/png"
        multiple="multiple"
        />
    <output id="result"></output>
</div>
<div class="submit-button">
    <button class="submit-button" type="submit" value="submit">
        submit
    </button>
</div>

I use JavaScript and attach eventlistener to check, if current uploaded photos exceeds the max number of photos, if so, disable submit button, otherwise enable it.

Why is promise object resolved at different times even after the same amount of delay?

Screenshot

Code1:

let p = new Promise((resolve,reject) => {
    setTimeout(() => {console.log("itsdone"); resolve("done");},10000);
});

Code2:

let p = new Promise((resolve,reject) => {
    setTimeout(resolve("done"),10000);
});

Even though same delay of 10s has been provided, why the promise object’s (p) state differs in the two code scenarios?
The promise in Code1 is resolved after 10s, but in Code2 it is resolved instantaneously?
Please see the attached image for the difference in outputs.

Isn’t the promise in Code2 is also expected to be resolved in 10s.

Stereo microphone not recognized as 2 channels on Chrome for Android

I used a stereo microphone and processed the 2-channel sound with Web Audio API. On the Windows PC version of Chrome, I was able to record the sound as 2 channels.(Chrome 127.0.6533.122)

When I used the same code in Chrome for Android on a smartphone, it was only recognized as 1 channel.(Android 10, Chrome 128.0.6613.88)

I am very confused because I want to do the same process as in the Windows PC version of Chrome.

I checked the channelCount in stream.getAudioTracks()[0].getSettings() and it was 1.

  • I am using enumerateDevices() to select the correct microphone to use.
  • A stereo-capable recording smartphone app such as “Voice Recorder” correctly recognizes it as 2 channels and can record separate left and right sounds. This is not a malfunction of the microphone or the smartphone.
  • We have also tried with Android smartphones from other manufacturers, and the same phenomenon occurred.
  • I also tried Firefox, but the same phenomenon occurred.

My code is shown below:

<!DOCTYPE html>
<html >
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <select name="" id="mic_select"></select>
  <button type="button" id="run_btn">Click</button>
  <p id="res">...</p>

  <script>
    const selectElement = document.getElementById('mic_select');
    
    // Create a list of microphones
    navigator.mediaDevices.enumerateDevices()
      .then(function(devices) {
        // extract microphone
        let audioInputDevices = devices.filter(function(device) {
          return device.kind === 'audioinput';
        });
        // Add to dropdown list
        audioInputDevices.forEach(function(device, index) {
          let option = document.createElement('option');
          option.value = device.deviceId;
          option.text = device.label || 'mic ' + (index + 1);
          selectElement.appendChild(option);
        });
      }).catch(function(err) {
        let option = document.createElement('option');
        option.value = '';
        option.text  = 'no mic';
        selectElement.appendChild(option);
    });

    // EventListener
    document.getElementById('run_btn').addEventListener('click', async function() {
      navigator.mediaDevices.getUserMedia({
        // Disable echo canceller to get stereo.
        audio: { 
          'deviceId' : selectElement.value,
          echoCancellation : false, googEchoCancellation: false,
          autoGainControl : false,
          noiseSuppression : false,
          channelCount : 2
        }, 
        video: false
      })
      .then(stream => {
        // Get channel number from audio track
        document.getElementById('res').innerText = 
            'stream.getAudioTracks()[0]n' + 
            'getSettings().channelCount ' + stream.getAudioTracks()[0].getSettings().channelCount + 'n' +
            'label '      + stream.getAudioTracks()[0].label + 'n' +
            'readyState ' + stream.getAudioTracks()[0].readyState;
      })
      .catch(err => {
        document.getElementById('res').textContent = 'NG';
      });
    });
  </script>
</body>
</html>

Proccess dates in nest.js and typeOrm

I got this table:

Id date_time_capture idHive
1001 2024-06-19 20:17:45 2
1002 2024-06-19 20:18:45 2

date_time_capture is a datetime field in my database using MySql then in postman:

Image

This is my method:

async findAll(tempFilterDto: TempFilterDto) {
  try {
    const { hiveId, startDate, endDate } = tempFilterDto;
    const whereConditions: any = {};

    if (hiveId) {
      // Verifica si el hiveId existe
      const hiveExists = await this.hiveRepository.findOne({ where: { id: hiveId } });
      if (!hiveExists) {
        return { status: HttpStatus.OK, message: 'Records not find', data: [] };
      }
      whereConditions.id_hive = hiveId;
    }

    if (startDate && endDate) {
      whereConditions.date_time_capture = Between(new Date(startDate), new Date(endDate));
    } else if (startDate) {
      whereConditions.date_time_capture = MoreThanOrEqual(new Date(startDate));
    } else if (endDate) {
      whereConditions.date_time_capture = LessThanOrEqual(new Date(endDate));
    }

    console.log(whereConditions);

    const records = await this.tempRepository.find({
      where: whereConditions,
      order: { date_time_capture: 'ASC' },
    });

    return { status: HttpStatus.OK, message: 'Registros obtenidos correctamente', data: records };
  } catch (error) {
    throw new InternalServerErrorException({
      status: HttpStatus.INTERNAL_SERVER_ERROR,
      message: 'Ocurrió un error al obtener los registros de temperatura: ' + error.message,
    });
  }
}

It does not return results although they exist in that range. And if I change the values ​​of startDate and endDate to the form 2024-06-19 20:17:44 2024-06-19 20:20:45.

The time is converted to another time zone according to the log:

whereConditions {
  id_hive: 2,
  date_time_capture: FindOperator {
    '@instanceof': Symbol(FindOperator),
    _type: 'between',
    _value: [ 2024-06-20T01:17:44.000Z, 2024-06-20T01:20:45.000Z ],
    _useParameter: true,
    _multipleParameters: true,
    _getSql: undefined,
    _objectLiteralParameters: undefined
  }
}

I appreciate any help to see how I can make the query bring the correct information and make the filter correctly.
I can’t find information on how to do this.

Forbidden access error: following Directus SDK tutorial

I’m having trouble learning Directus SDK – loosely following: https://docs.directus.io/blog/building-a-personal-travel-journal-with-vue-js-and-directus.html#creating-a-journal-and-users-collection

When I try to access data, I’m getting:

GET http://127.0.0.1:8055/items/calibers 403 (Forbidden)

I can access this from Chrome directly, just not the SDK. The details of the error in the console are:

{
    {
      "message": "You don't have permission to access collection "calibers" or it does not exist. Queried in root.",
      "extensions": {
        "reason": "You don't have permission to access collection "calibers" or it does not exist. Queried in root.",
        "code": "FORBIDDEN"
      }
    }
  ],
  "response": {
    ...
    status: 403
    statusText: "Forbidden"
    type: "cors"
    url: "http://127.0.0.1:8055/items/calibers"
  }
}

I thought it may be a CORS issue, but I’ve seen that before in my learning and because of that I have CORS_ORIGIN: 'true' in my docker file.

I can create the login page okay and login successfully following the tutorial (it’s the only page I can get working), but I can’t even pull data using a simple test page:

<script setup>
import { ref, onMounted } from 'vue'
import { createDirectus, rest, readItems } from '@directus/sdk'

// Client with REST support
const client = createDirectus('http://127.0.0.1:8055/').with(rest())
const retval = ref([])

onMounted(async () => {
  try {
    const response = await client.request(readItems('calibers'))
    console.log('Full response:', response)
    retval.value = response
    console.log('success value read:', retval.value)
  } catch (error) {
    console.error('Error fetching:', error)
  }
})
</script>

<template>
  <div v-for="val in retval" :key="val.id">
    <div>{{ val }}</div>
  </div>
</template>

After logging in successfully using the login page from this tutorial, I can access all of the data in my chrome browser directly. I can also access it with postman using the same user credentials, so I have to assume that my user is setup correctly (I’m using the default admin user).

My setup has:

  "dependencies": {
    "@directus/sdk": "^17.0.0",
    "pinia": "^2.1.7",
    "vue": "^3.4.29",
    "vue-router": "^4.3.3"
  },
  "devDependencies": {
    "@rushstack/eslint-patch": "^1.8.0",
    "@tsconfig/node20": "^20.1.4",
    "@types/jsdom": "^21.1.7",
    "@types/node": "^20.14.5",
    "@vitejs/plugin-vue": "^5.0.5",
    "@vue/eslint-config-prettier": "^9.0.0",
    "@vue/eslint-config-typescript": "^13.0.0",
    "@vue/test-utils": "^2.4.6",
    "@vue/tsconfig": "^0.5.1",
    "autoprefixer": "^10.4.20",
    "eslint": "^8.57.0",
    "eslint-plugin-vue": "^9.23.0",
    "jsdom": "^24.1.0",
    "npm-run-all2": "^6.2.0",
    "postcss": "^8.4.41",
    "prettier": "^3.2.5",
    "tailwindcss": "^3.4.10",
    "typescript": "~5.4.0",
    "vite": "^5.3.1",
    "vitest": "^1.6.0",
    "vue-tsc": "^2.0.21"
  }

And, my docker compose file has:

  environment:
    DB_CLIENT: 'sqlite3'
    DB_FILENAME: '/directus/database/data.db'
    KEY: 'xxxxx'
    SECRET: 'xxxxx'
    ADMIN_EMAIL: 'xxxxx'
    ADMIN_PASSWORD: 'xxxxx'
    CACHE_ENABLED: 'false'
    CACHE_STORE: 'redis'
    REDIS: 'redis://cache:6379'
    CORS_ENABLED: 'true'
    CORS_ORIGIN: 'true'
    CORS_METHODS: 'GET,POST,PATCH,DELETE'
    CORS_CREDENTIALS: 'true'
    REFRESH_TOKEN_COOKIE_DOMAIN: 'localhost'
    EXTENSIONS_AUTO_RELOAD: 'true'
    WEBSOCKETS_ENABLED: 'true'
    IMPORT_IP_DENY_LIST: ''

I’m new and lost – any direction is appreciated. I’m not even sure where to troubleshoot (error logs, etc.). Thanks!

Frontend Web buttons don’t work untill refreshing the window

I’m developing web page using React JavaScript,and I used a layout downloaded from the web as below:

import React, { useEffect, useState } from 'react';
import { Outlet, Link, useLocation } from 'react-router-dom';
import {
  UserOutlined,
  FileOutlined,
} from '@ant-design/icons';
import { Layout, Menu, theme } from 'antd';

const { Header, Content, Footer, Sider } = Layout;

function getItem(label, key, icon) {
  return {
    key,
    icon,
    label,
  };
}



const items = [
  getItem(<Link to="profile">Profile</Link>, '1', <UserOutlined />),
  getItem(<Link to="data">Data List</Link>, '2', <FileOutlined />),
];

const MainLayout = () => {
  const [collapsed, setCollapsed] = useState(false);
  const {
    token: { colorBgContainer },
  } = theme.useToken();


  return (
    <Layout
      style={{
        minHeight: '100vh',
      }}
    >
      <Sider collapsible collapsed={collapsed} onCollapse={(value) => setCollapsed(value)}>
        <div className="demo-logo-vertical" />
        <Menu theme="dark"  mode="inline" items={items} />
      </Sider>
      <Layout>
        
        <Content
          style={{
            margin: '0 16px',
          }}
        >
          <div
            style={{
              padding: 24,
              minHeight: 360,
              background: colorBgContainer,
            }}
          >
            <Outlet />
          </div>
        </Content>
        <Footer
          style={{
            textAlign: 'center',
          }}
        >
          Ant Design ©{new Date().getFullYear()} Created by Ant UED
        </Footer>
      </Layout>
    </Layout>
  );
};

export default MainLayout;

and put a <Outlet /> in the middle.

There’s two item Profile and Data List. When I click one of them, the page changes correctly as I coded the app.jsx below:

<Route path='/home' element={<MainLayout />}>
            <Route index element={<Navigate to="data" />} />
            <Route path='profile' element={<ProfilePage />} />
            <Route path='data' element={<DataListingPage />} />

But every time when I’m in one page trying to switch to the other page, after clicking that item, the page changed but all the button in that page don’t work as I clicking them.
Untill I refresh the window by hand.
This really makes me nervous.

I tried to add some refresh codes after switching pages. Like adding key into Outlet:<Outlet key={location.pathname} /> and it doesn’t work.
Then I tryna add this function:

  useEffect(() => {
    if (isFirstLoad.current) {
      isFirstLoad.current = false;
      return;
    }

    window.location.reload();
  }, [location.pathname]);

But the page refreshed in an infinite loop.TT

Send “heartbeat” ping messages JS

I am 100% new to programing, and I am having a hard time online finding out how to do this. All I find are very old questions (10+ years) saying that this was not possible then. What about now?

I have access to a server. From what I was told, the server will respond automatically to pings.

In my website I have a websocket:

const loc = window.location;
const protocol = loc.protocol === "https:" ? "wss:" : "ws:";
const ws = new WebSocket(protocol + "//" + loc.host + loc.pathname + "/../ws");

// Then, to send messages to the server I do:
ws.send(JSON.stringify(jsonMessage));

The server is running behind a reverse proxy that is closing the connection after 5 minutes without a message. I need to somehow send a ping every X seconds to both keep the connection alive, and check on the client side that the server is still up.

I assume this is very common, but I am having a hard time doing this. I appreciate any help in the right direction.

Thanks

SVG eternal Stylesheet HTML Object tag and CSP object-src

WRT the MDN documentation for the CSP object-src rule: –

Note: Elements controlled by object-src are perhaps coincidentally considered legacy HTML elements and aren’t receiving new standardized features (such as the security attributes sandbox or allow for ). Therefore it is recommended to restrict this fetch-directive (e.g. explicitly set object-src ‘none’ if possible).

How can one still use external CSS stylesheets with SVG images and have a solid Content Security policy?

Is the Mozilla description of being legacy jumping the gun?

Is it still kosher/safe to have ‘object-src’ URL in your CSP or perhaps just ‘self’?

I don’t see any CSP directive “unsafe-object” so is there a problem with still using OBJECT?

Here is an SSCCE of object-tag, external svg stylesheet. Sorce-code can be found here.

Is Keycloack an IAM Tool like “Microsoft Azure Active Directory”?

I am new to this field, I am a front-end developer, but recently learnt of few terms – like “Keycloack” and “ping-IMS”.

Now I came to know that there are IAM tools like following:

  • Microsoft Azure Active Directory;
  • One Login
  • AWS Verified Access;

I got a feel that ==> Keyclock or ping_Identiy is FREE or open-source, so I don’t think it does the same thing as — heavily paid services lilke MS_Azure_AD, or One_Login or IBM Identity and Access Mangement.

All these terms are pretty confusing to me. can anyone tell me — are all these things just the same?

Also, is there any difference between “SSO” and “IAM/IMS Tools”? (Identity Management tools). Thanks.

Sometimes the “printWidth” option in “prettier” is applied, but sometimes it is not

How to reproduce the phenomenon

  1. Install “Prettier – Code formatter” extention in Vscode

  2. write file .prettierrc

{
  "tabWidth": 2,
  "singleQuote": true,
  "printWidth": 400,
  "trailingComma": "es5",
  "semi": true,
  "bracketSpacing": true,
  "bracketSameLine": false,
  "jsxBracketSameLine": true
}

Please remember that I set the “printWidth” option to 400 very well.

  1. write file .vscode/settings.json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode"
}

Situation where printWidth option works well

— write

export default function Page() {
  const menu = [{ name: 'hong', age: 11 }, { name: 'gil', age: 22 }, { name: 'gil' }];
  return <></>;
}

— after save (auto formatting)

export default function Page() {
  const menu = [{ name: 'hong', age: 11 }, { name: 'gil', age: 22 }, { name: 'gil' }];
  return <></>;
}

Situation where printWidth option not works

— write

export default function Page() {
  const menu = [{ name: 'hong', age: 11 }, { name: 'gil', age: 22 }];
  return <></>;
}

— after save (auto formatting)

export default function Page() {
  const menu = [
    { name: 'hong', age: 11 },
    { name: 'gil', age: 22 },
  ];
  return <></>;
}

Why is this happening?

Issue with a buttons active class not working in a scrollbar script

A few days ago, I asked a question here about how to make a div move as a scrollbar. I got a response giving me a script that works, but I just have a small problem with it: two of the buttons active classes don’t work. It seems like a simple enough task, but I cannot for the life of me figure out how to fix it 🙁 The code is below, and the ones I need help with are the .buttonscrollbarupdown and .buttonscrollbarsidetosdide. Do I simply need to specify an active class in one of the EventListener sections, or change the initial element selection? Any help would be incredible 😀 I know almost nothing about JavaScript, so I’m sorry if this is a very basic question!

<script>
// Element selections
const scrollArea = document.querySelector('.canvastext');
const scrollBarVertical = document.querySelector('.scrollbarvertical');
const scrollBarHorizontal = document.querySelector('.scrollbarhorizontal');
const scrollBarVerticalHandle = scrollBarVertical.querySelector('.buttonscrollbarupdown');
const scrollBarHorizontalHandle = scrollBarHorizontal.querySelector('.buttonscrollbarsidetoside');
const buttonScrollUp = document.querySelector('.buttonscrollbarup');
const buttonScrollDown = document.querySelector('.buttonscrollbardown');
const buttonScrollLeft = document.querySelector('.buttonscrollbarleft');
const buttonScrollRight = document.querySelector('.buttonscrollbarright');

const rightButtonWidth = 15; // Width of the right button on horizontal scrollbar
const rightButtonMargin = 15; // Margin between the right button and scrollbar on horizontal scrollbar
const bottomButtonHeight = 15; // Height of the bottom button on vertical scrollbar
const bottomButtonMargin = 15 // Margin between the bottom button and scrollbar on vertical scrollbar

// Scroll ratio settings
const scrollRatio = 0.05; // Scroll amount as a percentage (5%)

// Synchronize custom scrollbars with the scroll area
function updateCustomScrollbars() {
  const scrollTop = scrollArea.scrollTop;
  const scrollLeft = scrollArea.scrollLeft;
  const scrollHeight = scrollArea.scrollHeight;
  const scrollWidth = scrollArea.scrollWidth;
  const clientHeight = scrollArea.clientHeight;
  const clientWidth = scrollArea.clientWidth;

  // Calculate vertical scrollbar handle size and position
  const verticalHandleHeight = Math.max((clientHeight / scrollHeight) * (scrollBarVertical.clientHeight - bottomButtonHeight - bottomButtonMargin), 30); // Minimum height 30px
  const verticalScrollRatio = scrollTop / (scrollHeight - clientHeight);
  scrollBarVerticalHandle.style.height = `${verticalHandleHeight}px`;
  scrollBarVerticalHandle.style.top = `${Math.min(verticalScrollRatio * (scrollBarVertical.clientHeight - verticalHandleHeight - bottomButtonHeight - bottomButtonMargin), scrollBarVertical.clientHeight - verticalHandleHeight - bottomButtonMargin)}px`;

  // Calculate horizontal scrollbar handle size and position
  const horizontalHandleWidth = Math.max((clientWidth / scrollWidth) * (scrollBarHorizontal.clientWidth - rightButtonWidth - rightButtonMargin), 30); // Minimum width 30px
  const horizontalScrollRatio = scrollLeft / (scrollWidth - clientWidth);
  scrollBarHorizontalHandle.style.width = `${horizontalHandleWidth}px`;
  scrollBarHorizontalHandle.style.left = `${Math.min(horizontalScrollRatio * (scrollBarHorizontal.clientWidth - horizontalHandleWidth - rightButtonWidth - rightButtonMargin), scrollBarHorizontal.clientWidth - horizontalHandleWidth - rightButtonWidth - rightButtonMargin)}px`;
}

// Update custom scrollbars when scrolling
scrollArea.addEventListener('scroll', updateCustomScrollbars);

// Handle dragging of custom scrollbars
function onDragStart(event, axis) {
  event.preventDefault();
  const startX = event.clientX;
  const startY = event.clientY;
  const startScrollTop = scrollArea.scrollTop;
  const startScrollLeft = scrollArea.scrollLeft;

  function onDragMove(moveEvent) {
    const deltaX = moveEvent.clientX - startX;
    const deltaY = moveEvent.clientY - startY;

    if (axis === 'vertical') {
      const maxScrollTop = scrollArea.scrollHeight - scrollArea.clientHeight;
      const handleHeight = scrollBarVerticalHandle.clientHeight;
      const scrollTop = startScrollTop + (deltaY / (scrollBarVertical.clientHeight - handleHeight - bottomButtonHeight - bottomButtonMargin)) * maxScrollTop;
      scrollArea.scrollTop = Math.min(Math.max(scrollTop, 0), maxScrollTop);
    } else if (axis === 'horizontal') {
      const maxScrollLeft = scrollArea.scrollWidth - scrollArea.clientWidth;
      const handleWidth = scrollBarHorizontalHandle.clientWidth;
      const scrollLeft = startScrollLeft + (deltaX / (scrollBarHorizontal.clientWidth - handleWidth - rightButtonWidth - rightButtonMargin)) * maxScrollLeft;
      scrollArea.scrollLeft = Math.min(Math.max(scrollLeft, 0), maxScrollLeft);
    }
    updateCustomScrollbars();
  }

  function onDragEnd() {
    document.removeEventListener('mousemove', onDragMove);
    document.removeEventListener('mouseup', onDragEnd);
  }

  document.addEventListener('mousemove', onDragMove);
  document.addEventListener('mouseup', onDragEnd);
}

// Add drag functionality to custom scrollbars
scrollBarVerticalHandle.addEventListener('mousedown', (event) => onDragStart(event, 'vertical'));
scrollBarHorizontalHandle.addEventListener('mousedown', (event) => onDragStart(event, 'horizontal'));

// Update scrollbar positions initially
updateCustomScrollbars();

// Scroll by a set ratio when buttons are clicked
function scrollByRatio(axis, direction) {
  const maxScroll = axis === 'vertical' ? scrollArea.scrollHeight - scrollArea.clientHeight : scrollArea.scrollWidth - scrollArea.clientWidth;
  const scrollAmount = maxScroll * scrollRatio;

  if (axis === 'vertical') {
    if (direction === 'up') {
      scrollArea.scrollTop = Math.max(scrollArea.scrollTop - scrollAmount, 0);
    } else if (direction === 'down') {
      scrollArea.scrollTop = Math.min(scrollArea.scrollTop + scrollAmount, maxScroll);
    }
  } else if (axis === 'horizontal') {
    if (direction === 'left') {
      scrollArea.scrollLeft = Math.max(scrollArea.scrollLeft - scrollAmount, 0);
    } else if (direction === 'right') {
      scrollArea.scrollLeft = Math.min(scrollArea.scrollLeft + scrollAmount, maxScroll);
    }
  }
  updateCustomScrollbars();
}

// Add click events to scrollbar buttons
buttonScrollUp.addEventListener('click', () => scrollByRatio('vertical', 'up'));
buttonScrollDown.addEventListener('click', () => scrollByRatio('vertical', 'down'));
buttonScrollLeft.addEventListener('click', () => scrollByRatio('horizontal', 'left'));
buttonScrollRight.addEventListener('click', () => scrollByRatio('horizontal', 'right'));
</script>

I have tried adding active classes anywhere I can think of and I’ve tried changing the element selections to be buttons, but nothing has worked 🙁

Creating Service Workers to make dynamically-generated webpages available offline

I am designing a website using PHP and MySQL, and I am having trouble creating a Service Worker that works the way I want.

My setup/requirements:


I have a menu page that offers links like /myevents/dataEntry.php?event=2024abcd and /myevents/dataEntry.php?event=2024efgh. The specific event keys are loaded from an external API into the database, so they are not known ahead of time.

The dataEntry.php page itself is mostly the same regardless of the parameter, but there is one <select> control whose options are populated from the database, server-side (having been populated from the API as well).

I need each event’s dataEntry.php page to be available offline, once it’s loaded. That is, at some point I would need to be online to load dataEntry.php?event=2024abcd the first time, but once I load it once, I want to be able to load that page again even if I’m offline. Since every event parameter results in a different list of options in the select control, each page/event combination needs to be cached separately.

What I’ve Tried:


My approach was to create a different service worker for each page/event, by using <script src="offlineWorker.js.php?event=<?php echo ($_GET['event']); ?>> in /myevents/dataEntry.php

(which would render as <script src="offlineWorker.js.php?event=2024abcd"> in the HTML).

The service worker that I’ve cobbled together (/myevents/offlineworker.js.php) is as follows:

<?php
header('Content-type: text/javascript');
if ( isset($_GET["event"]) ) {
?>
    const cacheName = "event-<?php echo $_GET["event"]; ?>";
    const precacheResources = [
        "/myevents/dataEntry.php?event=<?php echo $_GET["event"]; ?>",
        "/assets/css/theme.css"
    ];


    if ('serviceWorker' in navigator) {
        window.addEventListener('load', function() {
            navigator.serviceWorker.register('/myevents/offlineWorker.js.php?event=<?php echo $_GET["event"]; ?>')
            .then(() => console.log("Successfully registered service worker after window loaded."));
        });
    }

    self.addEventListener('install', (event) => {
        event.waitUntil((async () => {
            const cache = await caches.open(cacheName);
            await cache.addAll(precacheResources);
        })());
    });

    self.addEventListener('fetch', (event) => {
        const requestURL = new URL(event.request.url);
        if(!(event.request.url.startsWith('http'))) {
            // skip request for things like plugins while you're offline.
        } else if (precacheResources.includes(requestURL.pathname) || precacheResources.includes(requestURL.pathname + requestURL.search)) {
            event.respondWith((async () => {
                // try the cache first
                const r = await caches.match(event.request);
                if (r) { return r; }

                // cache the new resource and return it
                const response = await fetch(event.request);
                const cache = await caches.open(cacheName);

                cache.put(event.request, response.clone());
                return response;
            })());
        }
    });

    self.addEventListener('activate', event => {
        event.waitUntil(
            caches.keys().then(cacheList => {
                return Promise.all(
                    cacheList.filter(thisCache => {
                        return thisCache !== cacheName;
                    }).map(thisCache => {
                        return caches.delete(thisCache);
                    })
                );
            })
        );
    });

<?php
}
?>

The idea is that each event would have its OWN service worker, caching the specific dataEntry page and the css file I need for the page. Each cache has its own name as well.

My Problem


Unfortunately, although the service worker is registered (correctly, as far as I can tell), and I can confirm that the .css file is cached and available offline, the /myevents/dataEntry.php?event=2024abcd page is not.

My best guesses as to why are that perhaps:

  1. Only a single service worker per domain might be able to be registered
  2. Service workers cannot be registered with a URL Parameter (ChatGPT suggests this is the case, though I’m dubious, especially since the css file is properly cached)
  3. Cached files cannot have URL Parameters in their paths.

I’m hoping the answer is simply a mistake I’ve made rather than any of these three, since I’m not sure how I’d get around those issues if they’re true 🙂

Thank you for your attention and thoughts!

How do I bundle a service worker in Next.js?

I’m following this Firebase guide on setting up a service worker to intercept and modify fetch() requests. Currently, my implementation produces the error

Uncaught SyntaxError: Cannot use import statement outside a module (at service-worker.js:1:1)

I suspect it’s is related to this note in the guide

Make sure that the service worker is bundled so that it will still work after the browser has been closed.

This topic is new to me, and I’m unsure how to go about it. I’m using Next.js and all I found in their docs was this.


Here’s my service worker

// public/service-worker.js

import { auth } from "@/firebase/firebase"
import { getIdToken, onAuthStateChanged } from "firebase/auth"

// Firebase docs use "activate" not "install"
self.addEventListener("install", (event) => {
  console.log("Installing...")
})

...

I’m installing it from a signup route, like this

// src/app/signup/SignUpForm.js

"use client"

export function SignUpForm() {
  useEffect(() => {
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/service-worker.js', { scope: '/' })
    }
  }, [])
  ...
}

How do I resolve this error?

Django/Dash – injected script not executed but no errors

I’m trying to create a text area in a dash app which shall function as status window for the user. Updates shall be sent to the window via messaging (ws, channels, redis).

    html.Div(style={'flex': '0 0 auto', 'padding': '0px', 'boxSizing': 'border-box'}, children=[
        dcc.Textarea(
            id='log-window',
            style={'width': '100%', 'height': '400px', 'resize': 'none'},
            readOnly=True),
        html.Script("""
            console.log('logging script loaded successfully!');
            setTimeout(function() {
                var logWindow = document.getElementById('log-window');
                if (logWindow) {
                    var socket = new WebSocket('ws://127.0.0.1:8000/ws/logs/');

                    socket.onmessage = function (event) {
                        var data = JSON.parse(event.data);
                        logWindow.value += data.message + '\n';
                    };

                    socket.onopen = function () {
                        console.log('WebSocket connection opened');
                    };

                    socket.onerror = function (error) {
                        console.error('WebSocket error:', error);
                    };

                    socket.onclose = function () {
                        console.log('WebSocket connection closed');
                    };

                } else {
                    console.error('logWindow element is null');
                };
            });
        """)
    ]),

The text area is created successfully but the injected script from above for starting the WebSocket connection is not executed. Nevertheless, the script is visible when inspecting the source code in the web browser. The console does not produce any error or hint. The text area is part of an iframe which holds the above dash application and allows scripts.

The script was executed when I included it in the view.html but there were diffuclties in obtaining the logWindow element which was always reported as null. This is why I included the script in the above dash app using html.Script(“””……”””) but here it’s not executed.

I’m using Firefox 129.02, Django 5.0.7, Dash 2.9.3, channels 4.1.0, channels-redis 4.2.0, python3.12. The web server is Django runserver (python manage.py runserver).