Tampermonkey / tampermonkey

Tampermonkey is the most popular userscript manager, with over 10 million users. It's available for Chrome, Microsoft Edge, Safari, Opera Next, and Firefox.
GNU General Public License v3.0
4.15k stars 415 forks source link

Websocket support #1483

Open HaloWang opened 2 years ago

HaloWang commented 2 years ago

I mean, are there any built in functions like GM_wsConnectTo() because of some webpages forbid unauthorized websocket connection, for example: "ws://localhost:8000"?

I think TM use built in function GM_xmlHttpRequest to avoid being blocked by website. So I ask this question.

I'm not good at network programming. But using GM_xmlHttpRequest to tell server to upgrade connect to websocket seems like something not possible 🤔

jonnytest1 commented 2 years ago

i sort of got it to work i think with the responseType "stream" API here are some snippets:

client side;

     GM_xmlhttpRequest({
            url: url.href,
            timeout: 99999999,
            headers: {
                socket: this.socketId,
                index: index
            },
            responseType: 'stream',
            onreadystatechange: async (r) => {
                if (r.state === r.OPENED && !this.opened) {
                    this.onopen?.();
                    this.opened = true;
                }
                if (r.status === 200 && !startedReading) {
                    startedReading = true;
                    const reader = r.response.getReader();
                    // eslint-disable-next-line no-constant-condition
                    while (true) {
                        const { done, value } = await reader.read(); // value is Uint8Array
                        if (value) {
                            const jsonStr = new TextDecoder().decode(value);
                            const jsonObj = JSON.parse(jsonStr);
                            debugger;
                            this.onmessage?.(jsonObj);
                            console.log(value.length, 'received');
                        }
                        if (done) { break; }
                    }
                    console.log('done');
                }
            },
            ontimeout: (e) => {
                if (this.options.reconnect) {
                    this.connect('timeout', index);
                }
                this.onclose?.();
            }
        });

server side socket implementation (node ,express,typescript)

    send(data) {
        const responseKey = Object.keys(this.connectionMap)[0];
        const connection = this.connectionMap[responseKey];
        if (!connection) {
            debugger;
            return;
        }
        if (!connection.sentHeaders) {
            connection.response.setHeader('Content-Type', 'text/plain; charset=utf-8');
            connection.response.setHeader('Transfer-Encoding', 'chunked');
            connection.response.status(200).flushHeaders();
            connection.sentHeaders = true;
        }
        const buffer = Buffer.from(JSON.stringify(data));
        connection.response.write(buffer);  //connection.response is just the default express response
        // connection.response.end();   //dont call end or your handle wont work anymore
    }
HaloWang commented 2 years ago

i sort of got it to work i think with the responseType "stream" API here are some snippets:

client side;

GM_xmlhttpRequest({
  url: url.href,
  timeout: 99999999,
  headers: {
    socket: this.socketId,
    index: index,
  },
  responseType: 'stream',
  onreadystatechange: async r => {
    if (r.state === r.OPENED && !this.opened) {
      this.onopen?.()
      this.opened = true
    }
    if (r.status === 200 && !startedReading) {
      startedReading = true
      const reader = r.response.getReader()
      // eslint-disable-next-line no-constant-condition
      while (true) {
        const { done, value } = await reader.read() // value is Uint8Array
        if (value) {
          const jsonStr = new TextDecoder().decode(value)
          const jsonObj = JSON.parse(jsonStr)
          debugger
          this.onmessage?.(jsonObj)
          console.log(value.length, 'received')
        }
        if (done) {
          break
        }
      }
      console.log('done')
    }
  },
  ontimeout: e => {
    if (this.options.reconnect) {
      this.connect('timeout', index)
    }
    this.onclose?.()
  },
})

server side socket implementation (node ,express,typescript)

send(data) {
    const responseKey = Object.keys(this.connectionMap)[0];
    const connection = this.connectionMap[responseKey];
    if (!connection) {
        debugger;
        return;
    }
    if (!connection.sentHeaders) {
        connection.response.setHeader('Content-Type', 'text/plain; charset=utf-8');
        connection.response.setHeader('Transfer-Encoding', 'chunked');
        connection.response.status(200).flushHeaders();
        connection.sentHeaders = true;
    }
    const buffer = Buffer.from(JSON.stringify(data));
    connection.response.write(buffer);  //connection.response is just the default express response
    // connection.response.end();   //dont call end or your handle wont work anymore
}

Pretty good! I will try this approach

jonnytest1 commented 1 year ago

small update the stream api only works when sending data from backend to client

jonnytest1 commented 1 year ago

would be nice to also have support for the https://developer.chrome.com/articles/fetch-streaming-requests/ request streaming with fetch - didnt work when i tried it tho :( https://github.com/Tampermonkey/tampermonkey/issues/1757

Destroy666x commented 8 months ago

@jonnytest1 what do you mean byy "from backend to client"? Is it not possible to send data from Tampermonkey to e.g. local app through websocket currently?

jonnytest1 commented 8 months ago

@Destroy666x well i havent found a way to stream a request body 🤔 what i did in he end was i just sent a new request from tampermonkey against the server every time an event happened and then matched the events to uuids i sent with the socket

kryptoniancode commented 7 months ago

what is timeline for this feature implementation?

Destroy666x commented 7 months ago

@kryptoniancode I doubt there's any timeline. But a hint from me if you're looking for solutions - I was seeking OBS integration personally and obs-websocket-js works perfectly with some tunelling for me. Maybe you'll find something similar for your use case.

kryptoniancode commented 7 months ago

Thanks I will look into this.