diff --git a/src/client.js b/src/client.js index c38aa6a3..718d15c7 100644 --- a/src/client.js +++ b/src/client.js @@ -218,6 +218,7 @@ module.exports = class Client { * @prop {boolean} [DEBUG] Enable debug mode * @prop {'data' | 'prodata' | 'widgetdata'} [server] Server type * @prop {string} [location] Auth page location (For france: https://fr.tradingview.com/) + * @prop {Object} [headers] Custom WebSocket headers */ /** @@ -228,8 +229,17 @@ module.exports = class Client { if (clientOptions.DEBUG) global.TW_DEBUG = clientOptions.DEBUG; const server = clientOptions.server || 'data'; - this.#ws = new WebSocket(`wss://${server}.tradingview.com/socket.io/websocket?type=chart`, { + const defaultHeaders = { + // eslint-disable-next-line max-len + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + 'Accept-Language': 'en-US,en;q=0.9', + 'Cache-Control': 'no-cache', + Pragma: 'no-cache', + }; + + this.#ws = new WebSocket(`wss://${server}.tradingview.com/socket.io/websocket?from=chart&type=chart`, { origin: 'https://www.tradingview.com', + headers: { ...defaultHeaders, ...clientOptions.headers }, }); if (clientOptions.token) { diff --git a/src/miscRequests.js b/src/miscRequests.js index a5d5048a..a9707f2d 100644 --- a/src/miscRequests.js +++ b/src/miscRequests.js @@ -429,7 +429,11 @@ module.exports = { * @param {string} [location] Auth page location (For france: https://fr.tradingview.com/) * @returns {Promise} Token */ - async getUser(session, signature = '', location = 'https://www.tradingview.com/') { + async getUser(session, signature = '', location = 'https://www.tradingview.com/', redirectCount = 0) { + if (redirectCount > 5) { + throw new Error('Too many redirects - possible WAF or geo-restriction'); + } + const { data, headers } = await axios.get(location, { headers: { cookie: genAuthCookies(session, signature), @@ -461,7 +465,7 @@ module.exports = { } if (headers.location !== location) { - return this.getUser(session, signature, headers.location); + return this.getUser(session, signature, headers.location, redirectCount + 1); } throw new Error('Wrong or expired sessionid/signature'); diff --git a/tests/getUser-redirect.test.ts b/tests/getUser-redirect.test.ts new file mode 100644 index 00000000..505b2cbe --- /dev/null +++ b/tests/getUser-redirect.test.ts @@ -0,0 +1,49 @@ +import { describe, it, expect } from 'vitest'; + +describe('getUser redirect protection', () => { + it('should not loop infinitely on repeated redirects', async () => { + // Monkey-patch axios in the require cache to simulate redirect loop + let callCount = 0; + const axiosMock = { + get: async (url: string) => { + callCount += 1; + const isA = url === 'https://www.tradingview.com/'; + return { + data: 'no auth here', + headers: { + location: isA + ? 'https://www.tradingview.com/accounts/signin/' + : 'https://www.tradingview.com/', + }, + }; + }, + }; + + const axiosPath = require.resolve('axios'); + const originalModule = require.cache[axiosPath]; + require.cache[axiosPath] = { + id: axiosPath, + filename: axiosPath, + loaded: true, + exports: axiosMock, + } as any; + + const miscPath = require.resolve('../src/miscRequests'); + delete require.cache[miscPath]; + + try { + // eslint-disable-next-line global-require + const misc = require('../src/miscRequests'); + await expect( + misc.getUser('fake_session', 'fake_signature'), + ).rejects.toThrow('Too many redirects'); + + expect(callCount).toBeGreaterThan(0); + expect(callCount).toBeLessThanOrEqual(10); + } finally { + if (originalModule) require.cache[axiosPath] = originalModule; + else delete require.cache[axiosPath]; + delete require.cache[miscPath]; + } + }, 5000); +});