diff --git a/web/assets/js/actions.ts b/web/assets/js/actions.ts index a179eec..1f7427a 100644 --- a/web/assets/js/actions.ts +++ b/web/assets/js/actions.ts @@ -1,24 +1,32 @@ -import {saveToLocalStorage} from './utils/localstorage'; +import { saveToLocalStorage } from './utils/localstorage'; import { - defaultFaucetTokenKey, - defaultMnemonicKey, - drawRequestTimer, - Game, - type GameoverType, - type GameSettings, - GameState, - GameTime, - Player, - Promotion + defaultFaucetTokenKey, + defaultMnemonicKey, + drawRequestTimer, + Game, + type GameoverType, + type GameSettings, + GameState, + GameTime, + Player, + Promotion } from './types/types'; -import {defaultTxFee, GnoJSONRPCProvider, GnoWallet} from '@gnolang/gno-js-client'; -import {BroadcastTxCommitResult, TM2Error, TransactionEndpoint} from '@gnolang/tm2-js-client'; -import {generateMnemonic} from './utils/crypto.ts'; +import { + defaultTxFee, + GnoJSONRPCProvider, + GnoWallet +} from '@gnolang/gno-js-client'; +import { + BroadcastTxCommitResult, + TM2Error, + TransactionEndpoint +} from '@gnolang/tm2-js-client'; +import { generateMnemonic } from './utils/crypto.ts'; import Long from 'long'; import Config from './config.ts'; -import {constructFaucetError} from './utils/errors.ts'; -import {AlreadyInLobbyError, ErrorTransform} from './errors.ts'; -import {preparePromotion} from './utils/moves.ts'; +import { constructFaucetError } from './utils/errors.ts'; +import { AlreadyInLobbyError, ErrorTransform } from './errors.ts'; +import { preparePromotion } from './utils/moves.ts'; // ENV values // // const wsURL: string = Config.GNO_WS_URL; TODO temporarily disabled @@ -33,6 +41,14 @@ const cleanUpRealmReturn = (ret: string) => { const decodeRealmResponse = (resp: string) => { return cleanUpRealmReturn(atob(resp)); }; +const parsedJSONOrRaw = (data: string, nob64 = false) => { + const decoded = nob64 ? cleanUpRealmReturn(data) : decodeRealmResponse(data); + try { + return JSON.parse(decoded); + } finally { + return decoded; + } +}; /** * Actions is a singleton logic bundler @@ -123,19 +139,32 @@ class Actions { return this.faucetToken || localStorage.getItem(defaultFaucetTokenKey); } + private gkLog(): Boolean { + const wnd = window as { gnokeyLog?: Boolean }; + return typeof wnd.gnokeyLog !== 'undefined' && wnd.gnokeyLog; + } + /** * Performs a transaction, handling common error cases and transforming them * into known error types. */ public async callMethod( - path: string, method: string, args: string[] | null, gasWanted: Long = defaultGasWanted ): Promise { + const gkLog = this.gkLog(); try { - return (await this.wallet?.callMethod( - path, + if (gkLog) { + const gkArgs = args?.map((arg) => '-args ' + arg).join(' ') ?? ''; + console.info( + `$ gnokey maketx call -broadcast ` + + `-pkgpath ${chessRealm} -gas-wanted ${gasWanted} -gas-fee ${defaultTxFee} ` + + `-func ${method} ${gkArgs} test1` + ); + } + const resp = (await this.wallet?.callMethod( + chessRealm, method, args, TransactionEndpoint.BROADCAST_TX_COMMIT, @@ -145,6 +174,14 @@ class Actions { gasWanted: gasWanted } )) as BroadcastTxCommitResult; + if (gkLog) { + console.info('response:', resp); + const respData = resp.deliver_tx.ResponseBase.Data; + if (respData !== null) { + console.info('response (parsed):', parsedJSONOrRaw(respData)); + } + } + return resp; } catch (e) { const ex = e as { log?: string; message?: string } | undefined; if ( @@ -152,12 +189,37 @@ class Actions { typeof ex?.message !== 'undefined' && ex.message.includes('abci.StringError') ) { - throw ErrorTransform(e as TM2Error); + e = ErrorTransform(e as TM2Error); + } + if (gkLog) { + console.info('error:', e); } throw e; } } + public async evaluateExpression(expr: string): Promise { + const gkLog = this.gkLog(); + if (gkLog) { + const quotesEscaped = expr.replace(/'/g, `'\\''`); + console.info( + `$ gnokey query vm/qeval --data '${chessRealm}'$'\\n''${quotesEscaped}'` + ); + } + + const resp = (await this.provider?.evaluateExpression( + chessRealm, + expr + )) as string; + + if (gkLog) { + console.info('response:', parsedJSONOrRaw(resp, true)); + } + + // Parse the response + return resp; + } + /**************** * GAME ENGINE ****************/ @@ -171,7 +233,7 @@ class Actions { const seconds = time.time * 60; // Join the waiting lobby try { - await this.callMethod(chessRealm, 'LobbyJoin', [ + await this.callMethod('LobbyJoin', [ seconds.toString(), time.increment.toString() ]); @@ -188,7 +250,7 @@ class Actions { return await this.waitForGame(); } catch (e) { // Unable to find the game, cancel the search - await this.callMethod(chessRealm, 'LobbyQuit', null); + await this.callMethod('LobbyQuit', null); this.quitLobby(); // Propagate the error throw new Error('unable to find game'); @@ -210,7 +272,6 @@ class Actions { private async lookForGame(): Promise { if (!this.isInTheLobby) throw new Error('Left the lobby'); return (await this.callMethod( - chessRealm, 'LobbyGameFound', null )) as BroadcastTxCommitResult; @@ -283,8 +344,7 @@ class Actions { * @param gameID the ID of the running game */ public async getGame(gameID: string): Promise { - const gameResponse: string = (await this.provider?.evaluateExpression( - chessRealm, + const gameResponse: string = (await this.evaluateExpression( `GetGame("${gameID}")` )) as string; @@ -306,7 +366,7 @@ class Actions { promotion: Promotion = Promotion.NO_PROMOTION ): Promise { // Make the move - const moveResponse = await this.callMethod(chessRealm, 'MakeMove', [ + const moveResponse = await this.callMethod('MakeMove', [ gameID, from, to, @@ -344,9 +404,7 @@ class Actions { */ async requestDraw(gameID: string, timeout?: number): Promise { // Make the request - const drawResponse = await this.callMethod(chessRealm, 'DrawOffer', [ - gameID - ]); + const drawResponse = await this.callMethod('DrawOffer', [gameID]); // Parse the response from the node const drawResponseRaw: string | null = @@ -383,9 +441,7 @@ class Actions { async claimTimeout(gameID: string): Promise { // Make the request - const response = await this.callMethod(chessRealm, 'ClaimTimeout', [ - gameID - ]); + const response = await this.callMethod('ClaimTimeout', [gameID]); // Parse the response from the node const claimTimeoutRaw: string | null = @@ -411,11 +467,9 @@ class Actions { const fetchInterval = setInterval(async () => { try { // Get the game - const getGameResponse: string = - (await this.provider?.evaluateExpression( - chessRealm, - `GetGame(${gameID})` - )) as string; + const getGameResponse: string = (await this.evaluateExpression( + `GetGame(${gameID})` + )) as string; // Parse the response const game: Game = JSON.parse(cleanUpRealmReturn(getGameResponse)); @@ -461,9 +515,7 @@ class Actions { */ async requestResign(gameID: string): Promise { // Make the request - const resignResponse = await this.callMethod(chessRealm, 'Resign', [ - gameID - ]); + const resignResponse = await this.callMethod('Resign', [gameID]); // Parse the response from the node const resignResponseRaw: string | null = @@ -482,9 +534,7 @@ class Actions { */ async declineDraw(gameID: string): Promise { // Make the request - const declineResponse = await this.callMethod(chessRealm, 'DrawRefuse', [ - gameID - ]); + const declineResponse = await this.callMethod('DrawRefuse', [gameID]); // Parse the response from the node const declineResponseRaw: string | null = @@ -503,7 +553,7 @@ class Actions { */ async acceptDraw(gameID: string): Promise { // Make the request - const acceptResponse = await this.callMethod(chessRealm, 'Draw', [gameID]); + const acceptResponse = await this.callMethod('Draw', [gameID]); // Parse the response from the node const acceptResponseRaw: string | null = @@ -536,11 +586,9 @@ class Actions { * ordered by their position in the leaderboard */ async getLeaderboard(): Promise { - const leaderboardResponse: string = - (await this.provider?.evaluateExpression( - chessRealm, - 'Leaderboard()' - )) as string; + const leaderboardResponse: string = (await this.evaluateExpression( + 'Leaderboard()' + )) as string; // Parse the response return JSON.parse(cleanUpRealmReturn(leaderboardResponse)); @@ -551,8 +599,7 @@ class Actions { * @param playerID the ID of the player (can be address or @username) */ async getPlayer(playerID: string): Promise { - const playerResponse: string = (await this.provider?.evaluateExpression( - chessRealm, + const playerResponse: string = (await this.evaluateExpression( `GetPlayer("${playerID}")` )) as string;