Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
run: npm test -- --run
env:
SESSION: ${{ secrets.TW_SESSION }}
SIGNATURE: ${{ secrets.TW_SIGNATURE }}
12 changes: 11 additions & 1 deletion src/chart/study.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,17 @@ module.exports = (chartSession) => class ChartStudy {
};

if (parsed.dataCompressed) {
updateStrategyReport((await parseCompressed(parsed.dataCompressed)).report);
try {
const compressedData = await parseCompressed(parsed.dataCompressed);
if (compressedData && compressedData.report) {
updateStrategyReport(compressedData.report);
}
} catch (error) {
this.#handleError(
'Unable to parse compressed strategy report:',
error.message || error,
);
}
}

if (parsed.data && parsed.data.report) updateStrategyReport(parsed.data.report);
Expand Down
54 changes: 49 additions & 5 deletions src/protocol.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const zlib = require('zlib');
const JSZip = require('jszip');

/**
Expand All @@ -9,6 +10,41 @@ const JSZip = require('jszip');
const cleanerRgx = /~h~/g;
const splitterRgx = /~m~[0-9]{1,}~m~/g;

/**
* Normalise base64 data received from TradingView.
* TradingView occasionally omits padding and may use URL-safe characters.
* @param {string} data Base64 payload
* @returns {string} Normalised base64 payload
*/
function normaliseBase64(data) {
const normalised = data.replace(/-/g, '+').replace(/_/g, '/');
return normalised.padEnd(normalised.length + ((4 - (normalised.length % 4)) % 4), '=');
}

/**
* Parse JSON from a decoded buffer, optionally trying common compression wrappers.
* @param {Buffer} buffer Decoded compressed payload
* @returns {Object | undefined} Parsed JSON when a format matches
*/
function parseDecodedCompressed(buffer) {
const readers = [
() => buffer,
() => zlib.inflateSync(buffer),
() => zlib.inflateRawSync(buffer),
() => zlib.gunzipSync(buffer),
];

for (const read of readers) {
try {
return JSON.parse(read().toString('utf8'));
} catch (error) {
// Try the next known TradingView payload format.
}
}

return undefined;
}

module.exports = {
/**
* Parse websocket packet
Expand Down Expand Up @@ -50,11 +86,19 @@ module.exports = {
* @returns {Promise<{}>} Parsed data
*/
async parseCompressed(data) {
const normalised = normaliseBase64(data);
const zip = new JSZip();
return JSON.parse(
await (
await zip.loadAsync(data, { base64: true })
).file('').async('text'),
);

try {
const archive = await zip.loadAsync(normalised, { base64: true });
const file = archive.file('') || archive.file(/.*/)[0];
if (!file) throw new Error('Compressed payload does not contain a file');
return JSON.parse(await file.async('text'));
} catch (zipError) {
const decoded = Buffer.from(normalised, 'base64');
const parsed = parseDecodedCompressed(decoded);
if (parsed) return parsed;
throw zipError;
}
},
};
8 changes: 4 additions & 4 deletions tests/indicators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('Indicators', () => {
expect(chart.infos.full_name).toBe('BINANCE:BTCEUR');
});

it.skipIf(noAuth).concurrent('gets performance data from SuperTrend strategy', async () => {
it.skipIf(noAuth)('gets performance data from SuperTrend strategy', async () => {
const SuperTrend = new chart.Study(indicators.SuperTrend);

let QTY = 10;
Expand Down Expand Up @@ -95,7 +95,7 @@ describe('Indicators', () => {
},
});

if (QTY >= 50) {
if (perfReport?.all?.totalTrades !== undefined && QTY >= 50) {
resolve(true);
return;
}
Expand All @@ -112,9 +112,9 @@ describe('Indicators', () => {
expect(perfResult).toBe(true);

SuperTrend.remove();
}, 10000);
}, 30000);

it.skipIf(noAuth).concurrent('gets data from MarketCipher B study', async () => {
it.skipIf(noAuth)('gets data from MarketCipher B study', async () => {
const CipherB = new chart.Study(indicators.CipherB);

const lastResult: any = await new Promise((resolve) => {
Expand Down
Loading