Skip to content

Commit

Permalink
v3.1.2
Browse files Browse the repository at this point in the history
  • Loading branch information
mytonwalletorg committed Nov 22, 2024
1 parent 08c15c9 commit 1f45e04
Show file tree
Hide file tree
Showing 27 changed files with 280 additions and 56 deletions.
1 change: 1 addition & 0 deletions changelogs/3.1.2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Bug fixes and performance improvements
197 changes: 197 additions & 0 deletions dev/resolveStackTrace.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
const fs = require('fs');
const { SourceMapConsumer } = require('source-map');
const path = require('path');

const USAGE_GUIDE = `This script is intended to be used manually.
It makes JavaScript errors from user logs more readable by converting the stacktrace references from minified file addresses to source code addresses.
To use the script, build the application first.
Make sure the application version you build is the same as the version of the application that produced the logs.
Then run any of these commands:
npm run resolve-stacktrace <error>
npm run resolve-stacktrace <dist-directory> <error>
Where <error> is an error string from a log file exported by the application,
and <dist-directory> is the path to a directory with the application's sourcemaps (default: dist).
Examples:
npm run resolve-stacktrace ${JSON.stringify('{"name":"Error","message":"Test","stack":"Error: Test\n at t.BitBuilder.writeVarUint (https://mytonwallet.local/941.c17ba5754ec7f174fec2.js:2:25840)\n at t.BitBuilder.writeCoins (https://mytonwallet.local/941.c17ba5754ec7f174fec2.js:2:26382)"}')}
npm run resolve-stacktrace "Error: Test\n at t.BitBuilder.writeVarUint (https://mytonwallet.local/941.c17ba5754ec7f174fec2.js:2:25840)\n at t.BitBuilder.writeCoins (https://mytonwallet.local/941.c17ba5754ec7f174fec2.js:2:26382)"`;

const DEFAULT_MAP_DIRECTORY = path.join(__dirname, '..', 'dist');

const { mapDirectory, stackTrace } = resolveArguments();
const resolvedStackTrace = resolveStackTrace(mapDirectory, stackTrace);
process.stdout.write(`${resolvedStackTrace}\n`);
process.exit(0);

function resolveArguments() {
const arguments = process.argv.slice(2);
switch (arguments.length) {
case 0:
process.stderr.write(`Too few arguments!\n\n${USAGE_GUIDE}\n`)
process.exit(1);
break;
case 1:
return {
mapDirectory: DEFAULT_MAP_DIRECTORY,
stackTrace: parseStackTrace(arguments[0]),
};
case 2:
return {
mapDirectory: arguments[0],
stackTrace: parseStackTrace(arguments[1]),
};
default:
process.stderr.write(`Too many arguments!\n\n${USAGE_GUIDE}\n`)
process.exit(1);
break;
}
}

function parseStackTrace(inputText) {
const decoders = [parseJsonStackTrace, parsePlainStackTrace];

for (const decoder of decoders) {
const decoded = decoder(inputText);
if (decoded) {
return decoded;
}
}

process.stderr.write(`Unknown input error format. Check the examples.\n\n${USAGE_GUIDE}\n`)
process.exit(1);
}

// Decodes a line from a log file as is. For example:
// "{\"name\":\"TypeError\",\"message\":\"Cannot...
function parseJsonStackTrace(inputText) {
let data
try {
data = JSON.parse(inputText);
} catch {
return null;
}

if (!data || typeof data !== 'object' || typeof data.stack !== 'string') {
return null;
}

return data.stack;
}

function parsePlainStackTrace(inputText) {
if (/^(.+)(\n.*:\d+:\d+)+$/.test(inputText)) {
return inputText;
}

return null;
}

function resolveStackTrace(mapDirectory, stackTrace) {
const consumerCache = {};

return stackTrace
.split('\n')
.map(line => resolveStackTraceLine(mapDirectory, consumerCache, line))
.join('\n');
}

function resolveStackTraceLine(mapDirectory, consumerCache, line) {
const parsedLine = parseStackTraceLine(line);
if (!parsedLine) {
return line;
}

const newTrace = resolveTrace(
mapDirectory,
consumerCache,
parsedLine.fileUrl,
parsedLine.lineNumber,
parsedLine.columnNumber,
);
if (!newTrace) {
return line;
}

return `${parsedLine.lineIndent}at ${newTrace.name ?? ''} ${newTrace.filePath}:${newTrace.lineNumber}:${newTrace.columnNumber}`;
}

function parseStackTraceLine(line) {
// Example: at t.BitBuilder.writeCoins (https://mytonwallet.local/941.c17ba5754ec7f174fec2.js:2:26382)
const chromeRegex1 = /^(\s*)at\s.+\((.+):(\d+):(\d+)\)\s*$/;
// Example: at async https://mytonwallet.local/941.c17ba5754ec7f174fec2.js:2:1906473
const chromeRegex2 = /^(\s*)at(?:\sasync)?\s(.+):(\d+):(\d+)\s*$/;
// Example: safeExec@http://localhost:4321/main.0f90301c98b9aa1b7228.js:55739:14
// Example: @http://localhost:4321/main.0f90301c98b9aa1b7228.js:49974:25
// Example: ./src/lib/teact/teact.ts/runUpdatePassOnRaf</<@http://localhost:4321/main.0f90301c98b9aa1b7228.js:49974:32
const safariAndFirefoxRegex = /^(\s*)\S*@(.+):(\d+):(\d+)\s*$/;

const match = chromeRegex1.exec(line) || chromeRegex2.exec(line) || safariAndFirefoxRegex.exec(line);
if (!match) {
return null;
}

const [, lineIndent, fileUrl, lineNumber, columnNumber] = match;
return { lineIndent, fileUrl, lineNumber: Number(lineNumber), columnNumber: Number(columnNumber) };
}

function resolveTrace(mapDirectory, consumerCache, fileUrl, lineNumber, columnNumber) {
const mapFile = findSourceMapFile(mapDirectory, fileUrl);
if (!mapFile) {
return null;
}

if (!consumerCache[mapFile]) {
const sourceMap = JSON.parse(fs.readFileSync(path.join(mapDirectory, mapFile), 'utf8'));
const consumer = new SourceMapConsumer(sourceMap);
consumerCache[mapFile] = consumer;
}

const sourcePosition = consumerCache[mapFile].originalPositionFor({ line: lineNumber, column: columnNumber });
if (sourcePosition.line === null) {
return null;
}

return {
name: sourcePosition.name,
filePath: resolveSourceFilePath(sourcePosition.source),
lineNumber: sourcePosition.line,
columnNumber: sourcePosition.column,
};
}

function findSourceMapFile(mapDirectory, fileUrl) {
const filePath = extractFilePathFromUrl(fileUrl);
const bundleId = extractBundleIdFromFilePath(filePath, '.js');
if (bundleId === null) {
return null;
}

const directoryContent = fs.readdirSync(mapDirectory, { withFileTypes: true });
const isDesiredMap = (item) => item.isFile() && extractBundleIdFromFilePath(item.name, '.js.map') === bundleId;
return directoryContent.find(isDesiredMap)?.name ?? null;
}

function extractFilePathFromUrl(fileUrl) {
return fileUrl.replace(/^https?:\/\/[^\/]*\//, '');
}

function extractBundleIdFromFilePath(filePath, extension) {
if (!filePath.endsWith(extension)) {
return null;
}

const match = /^([\w.-]*)\.[\w]{16,}$/.exec(filePath.slice(0, filePath.length - extension.length));
if (!match) {
return null;
}

return match[1];
}

function resolveSourceFilePath(sourceFileUrl) {
return sourceFileUrl.replace(/^webpack:\/\/[^\/]*\//, '');
}
7 changes: 5 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mytonwallet",
"version": "3.1.1",
"version": "3.1.2",
"description": "The most feature-rich web wallet and browser extension for TON – with support of multi-accounts, tokens (jettons), NFT, TON DNS, TON Sites, TON Proxy, and TON Magic.",
"main": "index.js",
"scripts": {
Expand Down Expand Up @@ -42,7 +42,8 @@
"postversion": "./deploy/postversion.sh",
"giveaways:build": "webpack --config ./webpack-giveaways.config.ts && bash ./deploy/copy_to_dist.sh dist-giveaways",
"giveaways:build:dev": "APP_ENV=development webpack --mode development --config ./webpack-giveaways.config.ts && bash ./deploy/copy_to_dist.sh dist-giveaways",
"giveaways:dev": "APP_ENV=development webpack serve --mode development --port 4322 --config ./webpack-giveaways.config.ts"
"giveaways:dev": "APP_ENV=development webpack serve --mode development --port 4322 --config ./webpack-giveaways.config.ts",
"resolve-stacktrace": "node ./dev/resolveStackTrace.js"
},
"engines": {
"node": "^22",
Expand Down Expand Up @@ -167,6 +168,7 @@
"sass-loader": "16.0.2",
"script-loader": "0.7.2",
"serve": "14.2.3",
"source-map": "0.6.1",
"stylelint": "14.16.1",
"stylelint-config-clean-order": "0.8.0",
"stylelint-config-recommended-scss": "8.0.0",
Expand Down
2 changes: 1 addition & 1 deletion public/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.1.1
3.1.2
11 changes: 9 additions & 2 deletions src/api/methods/swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ export async function swapBuildTransfer(
const { network } = parseAccountId(accountId);
const authToken = await getBackendAuthToken(accountId, password);

const { address } = await fetchStoredTonWallet(accountId);
const { address, version } = await fetchStoredTonWallet(accountId);
request.walletVersion = version;

const { id, transfers } = await swapBuild(authToken, request);

const transferList = transfers.map((transfer) => ({
Expand Down Expand Up @@ -183,7 +185,12 @@ export async function fetchSwaps(accountId: string, ids: string[]) {
return { nonExistentIds, swaps };
}

export function swapEstimate(request: ApiSwapEstimateRequest): Promise<ApiSwapEstimateResponse | { error: string }> {
export async function swapEstimate(
accountId: string,
request: ApiSwapEstimateRequest,
): Promise<ApiSwapEstimateResponse | { error: string }> {
request.walletVersion = (await fetchStoredTonWallet(accountId)).version;

return callBackendPost('/swap/ton/estimate', request, {
isAllowBadRequest: true,
});
Expand Down
2 changes: 2 additions & 0 deletions src/api/types/backend.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { DieselStatus } from '../../global/types';
import type { ApiTonWalletVersion } from '../chains/ton/types';
import type { ApiLoyaltyType } from './misc';

export type ApiSwapEstimateRequest = {
Expand All @@ -9,6 +10,7 @@ export type ApiSwapEstimateRequest = {
toAmount?: string;
fromAddress: string;
shouldTryDiesel?: boolean;
walletVersion?: ApiTonWalletVersion;
};

export type ApiSwapEstimateResponse = ApiSwapEstimateRequest & {
Expand Down
26 changes: 16 additions & 10 deletions src/components/AppLocked.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ import logoLightPath from '../assets/logoLight.svg';
const WINDOW_EVENTS_LATENCY = 5000;
const INTERVAL_CHECK_PERIOD = 5000;
const PINPAD_RESET_DELAY = 300;
const ACTIVATION_EVENT_NAMES = [
'focus', // For Web
'mousemove', // For Web
'touch', // For Capacitor
'wheel',
'keydown',
];
// `capture: true` is necessary because otherwise a `stopPropagation` call inside the main UI will prevent the event
// from getting to the listeners inside `AppLocked`.
const ACTIVATION_EVENT_OPTIONS = { capture: true };

interface StateProps {
isNonNativeBiometricAuthEnabled: boolean;
Expand Down Expand Up @@ -131,18 +141,14 @@ function AppLocked({
const handleActivityThrottled = useThrottledCallback(handleActivity, [handleActivity], WINDOW_EVENTS_LATENCY);

useEffectOnce(() => {
window.addEventListener('focus', handleActivityThrottled, { capture: true }); // For Web
window.addEventListener('mousemove', handleActivityThrottled, { capture: true }); // For Web
window.addEventListener('touch', handleActivityThrottled, { capture: true }); // For Capacitor
window.addEventListener('wheel', handleActivityThrottled, { capture: true });
window.addEventListener('keydown', handleActivityThrottled);
for (const eventName of ACTIVATION_EVENT_NAMES) {
window.addEventListener(eventName, handleActivityThrottled, ACTIVATION_EVENT_OPTIONS);
}

return () => {
window.removeEventListener('focus', handleActivityThrottled, { capture: true });
window.removeEventListener('mousemove', handleActivityThrottled, { capture: true });
window.removeEventListener('touch', handleActivityThrottled, { capture: true });
window.removeEventListener('wheel', handleActivityThrottled, { capture: true });
window.removeEventListener('keydown', handleActivityThrottled);
for (const eventName of ACTIVATION_EVENT_NAMES) {
window.removeEventListener(eventName, handleActivityThrottled, ACTIVATION_EVENT_OPTIONS);
}
};
});

Expand Down
Loading

0 comments on commit 1f45e04

Please sign in to comment.