diff --git a/packages/near-api-js/src/account.ts b/packages/near-api-js/src/account.ts index b2b5bced0e..afffd2913c 100644 --- a/packages/near-api-js/src/account.ts +++ b/packages/near-api-js/src/account.ts @@ -32,8 +32,8 @@ import { Connection } from './connection'; import { baseDecode, baseEncode } from 'borsh'; import { PublicKey } from './utils/key_pair'; import { logWarning, PositionalArgsError } from './utils/errors'; -import { parseRpcError, parseResultError } from './utils/rpc_errors'; -import { ServerError } from './utils/rpc_errors'; +import { printTxOutcomeLogs, printTxOutcomeLogsAndFailures } from './utils/logging'; +import { parseResultError } from './utils/rpc_errors'; import { DEFAULT_FUNCTION_CALL_GAS } from './constants'; import exponentialBackoff from './utils/exponential-backoff'; @@ -123,12 +123,6 @@ export interface ViewFunctionCallOptions extends FunctionCallOptions { blockQuery?: BlockReference; } -interface ReceiptLogWithFailure { - receiptIds: [string]; - logs: [string]; - failure: ServerError; -} - interface StakedBalance { validatorId: string; amount?: string; @@ -177,28 +171,6 @@ export class Account { }); } - /** @hidden */ - private printLogsAndFailures(contractId: string, results: [ReceiptLogWithFailure]) { - if (!process.env['NEAR_NO_LOGS']) { - for (const result of results) { - console.log(`Receipt${result.receiptIds.length > 1 ? 's' : ''}: ${result.receiptIds.join(', ')}`); - this.printLogs(contractId, result.logs, '\t'); - if (result.failure) { - console.warn(`\tFailure [${contractId}]: ${result.failure}`); - } - } - } - } - - /** @hidden */ - private printLogs(contractId: string, logs: string[], prefix = '') { - if (!process.env['NEAR_NO_LOGS']) { - for (const log of logs) { - console.log(`${prefix}Log [${contractId}]: ${log}`); - } - } - } - /** * Create a signed transaction which can be broadcast to the network * @param receiverId NEAR account receiving the transaction @@ -254,17 +226,7 @@ export class Account { throw new TypedError('nonce retries exceeded for transaction. This usually means there are too many parallel requests with the same access key.', 'RetriesExceeded'); } - const flatLogs = [result.transaction_outcome, ...result.receipts_outcome].reduce((acc, it) => { - if (it.outcome.logs.length || - (typeof it.outcome.status === 'object' && typeof it.outcome.status.Failure === 'object')) { - return acc.concat({ - 'receiptIds': it.outcome.receipt_ids, - 'logs': it.outcome.logs, - 'failure': typeof it.outcome.status.Failure != 'undefined' ? parseRpcError(it.outcome.status.Failure) : null - }); - } else return acc; - }, []); - this.printLogsAndFailures(signedTx.transaction.receiverId, flatLogs); + printTxOutcomeLogsAndFailures({ contractId: signedTx.transaction.receiverId, outcome: result }); // Should be falsy if result.status.Failure is null if (!returnError && typeof result.status === 'object' && typeof result.status.Failure === 'object' && result.status.Failure !== null) { @@ -537,7 +499,7 @@ export class Account { }); if (result.logs) { - this.printLogs(contractId, result.logs); + printTxOutcomeLogs({ contractId, logs: result.logs }); } return result.result && result.result.length > 0 && parse(Buffer.from(result.result)); diff --git a/packages/near-api-js/src/utils/logging.ts b/packages/near-api-js/src/utils/logging.ts new file mode 100644 index 0000000000..8e1e6c1a86 --- /dev/null +++ b/packages/near-api-js/src/utils/logging.ts @@ -0,0 +1,69 @@ +import { FinalExecutionOutcome } from '../providers'; +import { parseRpcError } from './rpc_errors'; + +const SUPPRESS_LOGGING = !!process.env.NEAR_NO_LOGS; + +/** + * Parse and print details from a query execution response + * @param params + * @param params.contractId ID of the account/contract which made the query + * @param params.outcome the query execution response + */ +export function printTxOutcomeLogsAndFailures({ + contractId, + outcome, +}: { contractId: string, outcome: FinalExecutionOutcome }) { + if (SUPPRESS_LOGGING) { + return; + } + + const flatLogs = [outcome.transaction_outcome, ...outcome.receipts_outcome] + .reduce((acc, it) => { + const isFailure = typeof it.outcome.status === 'object' && typeof it.outcome.status.Failure === 'object'; + if (it.outcome.logs.length || isFailure) { + return acc.concat({ + receiptIds: it.outcome.receipt_ids, + logs: it.outcome.logs, + failure: typeof it.outcome.status === 'object' && it.outcome.status.Failure !== undefined + ? parseRpcError(it.outcome.status.Failure) + : null + }); + } else { + return acc; + } + }, []); + + for (const result of flatLogs) { + console.log(`Receipt${result.receiptIds.length > 1 ? 's' : ''}: ${result.receiptIds.join(', ')}`); + printTxOutcomeLogs({ + contractId, + logs: result.logs, + prefix: '\t', + }); + + if (result.failure) { + console.warn(`\tFailure [${contractId}]: ${result.failure}`); + } + } +} + +/** + * Format and print log output from a query execution response + * @param params + * @param params.contractId ID of the account/contract which made the query + * @param params.logs log output from a query execution response + * @param params.prefix string to append to the beginning of each log + */ +export function printTxOutcomeLogs({ + contractId, + logs, + prefix = '', +}: { contractId: string, logs: string[], prefix?: string }) { + if (SUPPRESS_LOGGING) { + return; + } + + for (const log of logs) { + console.log(`${prefix}Log [${contractId}]: ${log}`); + } +} diff --git a/packages/near-api-js/src/wallet-account.ts b/packages/near-api-js/src/wallet-account.ts index 64b902c92d..8ab5a37217 100644 --- a/packages/near-api-js/src/wallet-account.ts +++ b/packages/near-api-js/src/wallet-account.ts @@ -161,20 +161,17 @@ export class WalletConnection { } /** - * Redirects current page to the wallet authentication page. - * @param options An optional options object - * @param options.contractId The NEAR account where the contract is deployed - * @param options.successUrl URL to redirect upon success. Default: current url - * @param options.failureUrl URL to redirect upon failure. Default: current url + * Returns the sign in url for the wallet. + * @see {@link requestSignIn} For accepted options. * * @example * ```js * const wallet = new WalletConnection(near, 'my-app'); - * // redirects to the NEAR Wallet - * wallet.requestSignIn({ contractId: 'account-with-deploy-contract.near' }); + * // returns the url to the NEAR Wallet + * const signInLink = await wallet.requestSignIn({ contractId: 'account-with-deploy-contract.near' }); * ``` */ - async requestSignIn({ contractId, methodNames, successUrl, failureUrl }: SignInOptions) { + async requestSignInLink({ contractId, methodNames, successUrl, failureUrl }: SignInOptions) { const currentUrl = new URL(window.location.href); const newUrl = new URL(this._walletBaseUrl + LOGIN_WALLET_URL_SUFFIX); newUrl.searchParams.set('success_url', successUrl || currentUrl.href); @@ -196,13 +193,33 @@ export class WalletConnection { }); } + return newUrl.toString(); + } + + /** + * Redirects current page to the wallet authentication page. + * @param options An optional options object + * @param options.contractId The NEAR account where the contract is deployed + * @param options.successUrl URL to redirect upon success. Default: current url + * @param options.failureUrl URL to redirect upon failure. Default: current url + * + * @example + * ```js + * const wallet = new WalletConnection(near, 'my-app'); + * // redirects to the NEAR Wallet + * wallet.requestSignIn({ contractId: 'account-with-deploy-contract.near' }); + * ``` + */ + async requestSignIn({ contractId, methodNames, successUrl, failureUrl }: SignInOptions) { + const newUrl = await this.requestSignInLink({ contractId, methodNames, successUrl, failureUrl }); + window.location.assign(newUrl.toString()); } /** - * Requests the user to quickly sign for a transaction or batch of transactions by redirecting to the NEAR wallet. + * Returns the url (to NEAR wallet) for the user to quickly sign for a transaction or batch of transactions. */ - async requestSignTransactions({ transactions, meta, callbackUrl }: RequestSignTransactionsOptions): Promise { + async requestSignTransactionsLink({ transactions, meta, callbackUrl }: RequestSignTransactionsOptions): Promise { const currentUrl = new URL(window.location.href); const newUrl = new URL('sign', this._walletBaseUrl); @@ -213,23 +230,30 @@ export class WalletConnection { newUrl.searchParams.set('callbackUrl', callbackUrl || currentUrl.href); if (meta) newUrl.searchParams.set('meta', meta); + return newUrl.toString(); + } + + /** + * Requests the user to quickly sign for a transaction or batch of transactions by redirecting to the NEAR wallet. + */ + async requestSignTransactions({ transactions, meta, callbackUrl }: RequestSignTransactionsOptions): Promise { + const newUrl = await this.requestSignTransactionsLink({ transactions, meta, callbackUrl }); + window.location.assign(newUrl.toString()); } /** - * @hidden - * Complete sign in for a given account id and public key. To be invoked by the app when getting a callback from the wallet. + * Complete sign in for a given account id and public key. + * To be invoked by the developer in case you used {@link requestSignInLink} and you grabbed the callback manually. + * Parameters should come in the callback url from the wallet. */ - async _completeSignInWithAccessKey() { - const currentUrl = new URL(window.location.href); - const publicKey = currentUrl.searchParams.get('public_key') || ''; - const allKeys = (currentUrl.searchParams.get('all_keys') || '').split(','); - const accountId = currentUrl.searchParams.get('account_id') || ''; + async completeSignInWithAccessKeyLink(publicKey: string, allKeys: string, accountId: string) { + const allKeysArr = allKeys.split(','); // TODO: Handle errors during login if (accountId) { const authData = { accountId, - allKeys + allKeysArr }; window.localStorage.setItem(this._authDataKey, JSON.stringify(authData)); if (publicKey) { @@ -237,6 +261,24 @@ export class WalletConnection { } this._authData = authData; } + } + + /** + * @hidden + * Complete sign in for a given account id and public key. To be invoked by the app when getting a callback from the wallet. + */ + async _completeSignInWithAccessKey() { + const currentUrl = new URL(window.location.href); + const publicKey = currentUrl.searchParams.get('public_key') || ''; + const allKeys = currentUrl.searchParams.get('all_keys') || ''; + const accountId = currentUrl.searchParams.get('account_id') || ''; + + await this.completeSignInWithAccessKeyLink( + publicKey, + allKeys, + accountId + ); + currentUrl.searchParams.delete('public_key'); currentUrl.searchParams.delete('all_keys'); currentUrl.searchParams.delete('account_id');