bigskysoftware / htmx

</> htmx - high power tools for HTML
https://htmx.org
Other
37.59k stars 1.27k forks source link

[v1.9.10] 'ws-send' sends message twice when triggered with 'load' #2141

Open zarekr opened 9 months ago

zarekr commented 9 months ago

With ver 1.9.10 I have noticed that, when using ws-send on an element to send to an open websocket connection, using hx-trigger="load", the message is sent twice. For example:

<div ws-connect="/ws/some/url">

<div id="some_id" ws-send hx-trigger="load" hx-vals='{"some_key":"some_val"}'></div>

</div>

In the network pane of browser dev tools, I'll see this twice as a sent message when using v1.9.10 and only once with 1.9.9:

{"some_key": "some_val", "HEADERS":{"HX-Request":"true","HX-Trigger":"some_id", "HX-Trigger-Name":null,"HX-Target":"some_id","HX-Current-URL":"http://127.0.0.1:8000/home"}}    1703969407.3404334

{"some_key": "some_val", "HEADERS":{"HX-Request":"true","HX-Trigger":"some_id", "HX-Trigger-Name":null,"HX-Target":"some_id","HX-Current-URL":"http://127.0.0.1:8000/home"},    1703969407.3404334

As a result the backend route handling the ws message processes it twice.

I'm guessing it is to do with load being fired an additional time but I can't find where this is occurring. Looking at the changes it may be to do with the new readystate stuff.

zarekr commented 8 months ago

bump as I'm still having this issue with 1.9.10 and have reverted to 1.9.9 in the meantime

DaveManders2205 commented 8 months ago

This problem also occurred to me. This is the temporary workaround im using until it gets fixed: hx-trigger="load delay:1ms"

johannesschaffer commented 7 months ago

Doesn't work for me with v1.9.9 or below either. First time I'm using HTMX, so maybe I'm doing something wrong?

<form hx-ws='send' hx-ext="ws" ws-connect="/chat" class="flex items-center gap-2">
        <input placeholder="Type a message" name='message'
johannesschaffer commented 7 months ago

For me the problem was I used the old attribute: hx-ws='send' instead of hx-boost="" ws-send=""

CaliViking commented 7 months ago

I have the same issue. Even setting delay:0s or delay:0.000 causes the same issue, so there must be somewhere in the code where it explicitly checks for the delay being 0 or non-existent.

The parse interval function is interesting: https://github.com/bigskysoftware/htmx/blob/f919c0705182c904a440e3ff4a9687f4d5166c55/src/htmx.js#L149

It creates a delay in milliseconds as a float from the string. It only checks for ms, s, and m. However, it is possible to pass in 0.000001 (1 nanosecond) and it will work (it will not return a duplicate message like 0 does). With this setting, a full round trip takes about 30 milliseconds (typically from 28 to 32).

If I set the delay to 1s, then the round trip takes from 1.1 to 1.9 seconds to return (is this a JavaScript feature?)

If I pass in 1us or 1ns then it will treat it like 1s as it is only checking for ms, and then checking if the last character is s.

Surprisingly, if I pass in 1h, then it just does a floatParse and returns 1ms. The same would be true if I passed in 1d, 1q, 1y or 1asdnaf. However, if I pass in 1 asdnas, then it will treat it as 1 second (1000 ms).

I have not been able to find where the issue is with duplicated send. Someone with a better understanding of the code would likely be able to fix this very quickly.

Would this be a better way to parse the interval?

/**
 * Parses a string representing a time interval and converts it to milliseconds.
 * The string can end with 'ms' (milliseconds), 's' (seconds), or 'm' (minutes).
 * If no unit is specified, the function assumes the input is in milliseconds.
 * 
 * @param {string} str - The time interval string to parse.
 * @returns {number|undefined} The time interval in milliseconds, or undefined if the input is invalid.
 */
function parseInterval(str) {
    if (typeof str !== 'string') {
        return undefined;
    }

    const unitMultipliers = {
        ms: 1,
        s: 1000,
        m: 1000 * 60,
        // Add more units here if necessary
    };

    const unit = str.match(/[a-zA-Z]+$/)?.[0]; // Extract unit
    const number = parseFloat(str);

    // Check if the parsed number is a valid number
    if (isNaN(number)) {
        return undefined;
    }

    // If a unit is found and is valid, multiply by its multiplier. Otherwise, assume milliseconds.
    return unit && unitMultipliers[unit] ? number * unitMultipliers[unit] : number;
}