junlarsen / league-connect

:electric_plug: Node.js HTTP/1.1, HTTP/2.0 and WebSocket interface to the League of Legends Client APIs
https://www.npmjs.com/package/league-connect
MIT License
156 stars 23 forks source link

In the last update of the China Tencent Server (20220923), all LCU api has been disabled, and i get some problem. #94

Closed xiaowuyaya closed 1 year ago

xiaowuyaya commented 1 year ago

In the last update of the China Tencent Server (20220923), all LCU api has been disabled.

The main reason is that League of Legends client will reject all requests for access after client launch loading finished, all api will return code 404.

But here is a solution to this problem:

  1. make request to be HTTP persistent connection.
  2. the request object is a singleton.
  3. waiting for League of Legends client launch, send request with Connect: keep-alive when application get credentials, then subsequent requests reuse this object

This solution has been verified, NPhoenix: solve the 12.18 version some errors.

But I modified league-connect source code,add Connect: 'keep-alive', to function createHttp2Request and createHttp1Request header, running app like that solution, and it not work.

let session = null 

exports.http2Request = async (url, method = 'GET', postData = null) => {
  try {
    const credentials = c.get('credentials')  // get store, i save credentials when function authenticate return
    if(!session) { 
      session = await createHttpSession(credentials) // is it singleton ?
    }
    const response = await createHttp2Request(
      {
        method,
        url,
        body: postData,
      },
      session,
      credentials,
    )
    session.close()
    return response.json()
  } catch (err) {
    return null
  }
}

Then i will send a request of lcu api to keep http connection when client launch and get credentials, but there aways throw error: connect ECONNREFUSED 127.0.0.1:${port}

I'm not familiar with http/net related stuff, am i doing something wrong?

junlarsen commented 1 year ago

I wasn't aware of that change on the chinese client so I'm not sure I can help too much there. Can you send a diff of what you changed in createHttp2/Http1 request code?

xiaowuyaya commented 1 year ago

image image That red box is what i add in header. I don't know if this will make request to be persistent connection.

junlarsen commented 1 year ago

Okay, that would add the header as expected, I guess it isn't working for some other reasons?

Afraid I can't really help you debug this because I don't play on the china servers but if you have more info I can try to help

xiaowuyaya commented 1 year ago

Thanks, matsjla. I add Connection: keep-alive in the http1 and http2 header, it work now! The reason I couldn't use before is I used the session in http2 function wrongly... But i got two question on http2 function.

  1. when i need send a request with data and use mthod like POST or PATCH ,It not work, i get notiong response. is my body data in HttpRequestOptions is wrong?
exports.http2Request = async (url, method = 'GET', postData = null, json = true) => {
  try {
    const credentials = c.get('credentials')
    const session = await createHttpSession(credentials)
    const response = await createHttp2Request(
      {
        method,
        url,
        body: postData,
      },
      session,
      credentials,
    )
    session.close()
    if(json) {
      return response.json()
    }else{
      return response.text()
    }
  } catch (err) {
    return null
  }
}
  1. In my service, i need to get some image resource(i use other request package superagent before), but in function createHttp2Request, the response object only has text() and json(), i use text() and i think it is a binary data, but it' wrong. and how could i get the image and return to frontend?
    exports.getLcuImgBase64 = async imgUrl => {
    const data = await http2Request(imgUrl, 'GET', null, false)
    return 'data:image/jpeg;base64,' + Buffer.from(data, 'binary').toString('base64')
    }

I'm truly grateful for you help!

junlarsen commented 1 year ago
  1. I'm not sure, are you sure the endpoint is supposed to return something apart from the status code? I'm pretty sure the code should handle response body for all request types
  2. Does this resource come from the LCU? I don't support blobs at the moment
xiaowuyaya commented 1 year ago
  1. So the request data should put in body, is it right?
  2. Yes, the resource is come from the lcu, so i need to get it on base64 and return to frontend, how can i get the lcu jpeg resource. ^_^
junlarsen commented 1 year ago

Yeah the code just assumes the response is a string right now, you could modify it to return a Blob

xiaowuyaya commented 1 year ago

Still the previous problem, When i send a PUT or POST request, I dont get anything, no error and response. But GET request ( or no body request ) is functioning normally This is my request function, Because of the above problem I make the Session to be singleton.

let session: ClientHttp2Session | null = null
export async function http2Request(url: string, method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" = 'GET', data: any = null, json: boolean = true) {
  try {
    const credentials = $utils.cache.get('credentials')
    if (!session) {
      session = await createHttpSession(credentials)
    }
    const response = await createHttp2Request(
      {
        method,
        url,
        body: data,
      },
      session,
      credentials,
    )
    // session.close()
    if (json) {
      return response.json()
    } else {
      return response.text()
    }
  } catch (err) {
    console.log(err);
    return null
  }
}
export async function putPlayerChatInfo(info: any) {
  return await http2Request(`/lol-chat/v1/me`, 'PUT', info)
}
// using
async function handleChange(type: string, event: any) {
  let chatInfo = await $api.getPlayerChatInfo()
  if (type == 'ranked') {
    chatInfo.lol.rankedLeagueDivision = 'I'
    chatInfo.lol.rankedLeagueQueue = 'RANKED_SOLO_5x5'
    chatInfo.lol.rankedLeagueTier = event
    chatInfo.lol.rankedPrevSeasonDivision = 'I'
    const res = await $api.putPlayerChatInfo(chatInfo)  // no work, no error and no response

  } else if (type == 'status') {
    chatInfo.availability = event
    const res = await $api.putPlayerChatInfo(chatInfo)  // no work, no error and no response
  }
};

image

how to fix it?

xiaowuyaya commented 1 year ago

in an old version of my project, i use superagent to send request, but because it's session is not singleton, the connection can not be keep alive. After about a minute, the request will be fail.

export async function http2Request(url: string, method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" = 'GET', data: any = null,) {
try {
    const credentials = $utils.cache.get('credentials')
    const resp = await request(method, `https://127.0.0.1:${credentials.port}${url}`)
      .auth('riot', credentials.password) 
      .send(data)
      .ca(credentials.certificate) 
      .http2();
    return resp.body;
  } catch (e) {
    return e;
  }
}
xiaowuyaya commented 1 year ago

I find something! if i use http1 to send post request, it work, but a few munitine later, i cant get anything, and i find this, the request cant close and end,

async function createHttp1Request(options, credentials) {
  const agentOptions = credentials.certificate === void 0 ? { rejectUnauthorized: false } : { ca: credentials.certificate };
  console.log("catch");
  return new Promise((resolve, reject) => {
    const request = import_https.default.request({
      host: "127.0.0.1",
      port: credentials.port,
      path: "/" + trim(options.url),
      method: options.method,
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        'Connection':'keep-alive',
        Authorization: "Basic " + Buffer.from(`riot:${credentials.password}`).toString("base64")
      },
      agent: new import_https.default.Agent(agentOptions)
    }, (response) => {
      let bodyText = "";
      response.setEncoding("utf8");
      response.on("data", (data) => {
        console.log(data);
        bodyText += data
      });
      response.on('close', () => console.log('close'))
      response.on("end", () => {
        console.log('end');
        try {
          if (bodyText.indexOf('{')!=-1){
            const json = JSON.parse(bodyText);
            resolve(new Http1Response(response, json));
          }else {
            resolve(new Http1Response(response, bodyText));
          }
        }
        catch (jsonError) {
          reject(jsonError);
        }
      });
    });
    if (options.body !== void 0) {
      const data = JSON.stringify(options.body);
      const body = new import_util2.TextEncoder().encode(data);
      request.write(body, "utf8");
    }
    request.on("error", (err) => reject(err));
    console.log('ready end');
    request.end();
  });
}

out: image

junlarsen commented 1 year ago

I'd love to be able to give you more guidance on this, but my knowledge of the HTTP Keep-Alive spec is very limited and I'm also unable to test on the tencent server myself.