From 0ea6431ac6aa360b5088d0bbfdf17aef40df74f6 Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Wed, 10 Jul 2024 20:00:44 -0400 Subject: [PATCH] Adds "Wallet" custom element and signOut functionality within the app (#29) * init * tests * adds signIn and fixes tests * formatting * formatting * revert open-walletselector-button --- package.json | 1 + playwright-tests/code/auth/wallet.js | 14 ++++ .../storage-states/wallet-connected.json | 14 ++-- playwright-tests/testUtils.js | 4 +- playwright-tests/tests/auth.spec.js | 77 +++++++++++++++++++ src/App.js | 4 + src/auth/Wallet.js | 31 ++++++++ yarn.lock | 49 +++++++++--- 8 files changed, 178 insertions(+), 16 deletions(-) create mode 100644 playwright-tests/code/auth/wallet.js create mode 100644 playwright-tests/tests/auth.spec.js create mode 100644 src/auth/Wallet.js diff --git a/package.json b/package.json index 0112576..10f461d 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "LICENSE" ], "dependencies": { + "@near-wallet-selector/modal-ui-js": "^8.9.10", "@playwright/test": "^1.38.1", "@web3-onboard/injected-wallets": "^2.11.1", "@web3-onboard/ledger": "^2.7.1", diff --git a/playwright-tests/code/auth/wallet.js b/playwright-tests/code/auth/wallet.js new file mode 100644 index 0000000..a79821e --- /dev/null +++ b/playwright-tests/code/auth/wallet.js @@ -0,0 +1,14 @@ +return ( + <> +

{context.accountId}

+ { + return context.accountId ? ( + + ) : ( + + ); + }} + /> + +); diff --git a/playwright-tests/storage-states/wallet-connected.json b/playwright-tests/storage-states/wallet-connected.json index 685dd20..e3593d7 100644 --- a/playwright-tests/storage-states/wallet-connected.json +++ b/playwright-tests/storage-states/wallet-connected.json @@ -4,17 +4,17 @@ { "origin": "http://localhost:3000", "localStorage": [ + { + "name": "near-wallet-selector:recentlySignedInWallets", + "value": "[\"my-near-wallet\"]" + }, { "name": "near-wallet-selector:selectedWalletId", "value": "\"my-near-wallet\"" }, { "name": "near_app_wallet_auth_key", - "value": "{\"accountId\":\"anybody.near\",\"allKeys\":[\"ed25519:CziSGowWUKiP5N5pqGUgXCJXtqpySAk29YAU6zEs5RAi\"]}}" - }, - { - "name": "near-social-vm:v01::accountId:", - "value": "anybody.near" + "value": "{\"accountId\":\"anybody.near\",\"allKeys\":[\"ed25519:CziSGowWUKiP5N5pqGUgXCJXtqpySAk29YAU6zEs5RAi\"]}" }, { "name": "near-api-js:keystore:anybody.near:mainnet", @@ -23,6 +23,10 @@ { "name": "near-wallet-selector:contract", "value": "{\"contractId\":\"social.near\",\"methodNames\":[]}" + }, + { + "name": "near-social-vm:v01::accountId:", + "value": "anybody.near" } ] } diff --git a/playwright-tests/testUtils.js b/playwright-tests/testUtils.js index dd04514..e02bc05 100644 --- a/playwright-tests/testUtils.js +++ b/playwright-tests/testUtils.js @@ -47,14 +47,14 @@ export const useCode = async (page, filePath, props) => { const fullPath = path.join(__dirname, "code", filePath); try { const code = fs.readFileSync(fullPath, "utf8"); - const initialProps = props ? JSON.stringify(props) : ""; + const initialProps = props ? JSON.stringify(props) : null; // Set code and initialProps attribute await page.evaluate( ({ code, initialProps }) => { const viewer = document.querySelector("near-social-viewer"); viewer.setAttribute("code", code); - viewer.setAttribute("initialprops", initialProps); + initialProps && viewer.setAttribute("initialprops", initialProps); }, { code, initialProps } ); diff --git a/playwright-tests/tests/auth.spec.js b/playwright-tests/tests/auth.spec.js new file mode 100644 index 0000000..6603e11 --- /dev/null +++ b/playwright-tests/tests/auth.spec.js @@ -0,0 +1,77 @@ +import { describe, expect, test } from "@playwright/test"; +import { useCode } from "../testUtils"; + +describe("auth", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/"); + }); + + describe("User is not logged in", () => { + test.use({ + storageState: "playwright-tests/storage-states/wallet-not-connected.json", + }); + + test("'context.accountId' should be null and show login button", async ({ + page, + }) => { + await useCode(page, "auth/wallet.js"); + + const accountId = await page.textContent('[data-testid="accountId"]'); + expect(accountId).toBe(""); + }); + + test("should show wallet modal after clicking login button", async ({ + page, + }) => { + await useCode(page, "auth/wallet.js"); + + await page.click("#open-walletselector-button"); + + const modal = await page.getByText("Connect Your Wallet"); + expect(modal).not.toBeNull(); + }); + }); + + describe("User is logged in", () => { + test.use({ + storageState: "playwright-tests/storage-states/wallet-connected.json", + }); + + test("should have 'context.accountId' be non null and show logout button", async ({ + page, + }) => { + await useCode(page, "auth/wallet.js"); + + const accountId = page.getByTestId("accountId"); + expect(accountId).toHaveText("anybody.near"); + }); + + test("should prompt to disconnect wallet after clicking logout button", async ({ + page, + }) => { + await useCode(page, "auth/wallet.js"); + + // Verify auth keys exist + const authKey = await page.evaluate(() => ({ + near_app_wallet_auth_key: localStorage.getItem( + "near_app_wallet_auth_key" + ), + })); + + expect(authKey).not.toBeNull(); + + await page.getByRole("button", { name: "Log out" }).click(); + + // Verify auth keys are removed + await page.waitForFunction( + () => { + return localStorage.getItem("near_app_wallet_auth_key") === null; + }, + { timeout: 1000 } + ); + + const accountId = await page.textContent('[data-testid="accountId"]'); + expect(accountId).toBe(""); + }); + }); +}); diff --git a/src/App.js b/src/App.js index 9af4f93..3feb9a5 100644 --- a/src/App.js +++ b/src/App.js @@ -12,6 +12,7 @@ import { RouterProvider, useLocation, } from "react-router-dom"; +import Wallet from "./auth/Wallet"; import { BosWorkspaceProvider, useRedirectMap } from "./utils/bos-workspace"; import { EthersProvider } from "./utils/web3/ethers"; @@ -76,6 +77,9 @@ function App(props) { } return ; }, + Wallet: (props) => { + return ; + }, }, features: { enableComponentSrcDataKey: true, diff --git a/src/auth/Wallet.js b/src/auth/Wallet.js new file mode 100644 index 0000000..ac473fe --- /dev/null +++ b/src/auth/Wallet.js @@ -0,0 +1,31 @@ +import { setupModal } from "@near-wallet-selector/modal-ui-js"; +import { useNear } from "near-social-vm"; +import { useCallback } from "react"; + +function Wallet({ provides, config }) { + const near = useNear(); + + const signOut = useCallback(async () => { + const wallet = await (await near.selector).wallet(); + wallet.signOut(); + }, [near]); + + const signIn = useCallback(async () => { + const modal = setupModal(await near.selector, config); + modal.show(); + }, [near]); + + return provides({ signIn, signOut }); +} + +Wallet.defaultProps = { + provides: { + signIn: () => console.log("signIn"), + signOut: () => console.log("signOut"), + }, + config: { + contractId: "social.near", + }, +}; + +export default Wallet; diff --git a/yarn.lock b/yarn.lock index 4ba30db..c74d965 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2670,6 +2670,25 @@ bn.js "5.2.1" borsh "^0.7.0" +"@near-wallet-selector/core@8.9.10": + version "8.9.10" + resolved "https://registry.npmjs.org/@near-wallet-selector/core/-/core-8.9.10.tgz#0e19b22bbe69208c9e82b149682be37f6f9b76c5" + integrity sha512-do+DDahRHPzr5VKiFS7NWKyNbspXu64/w7CuSBi8IUDsDsclmV7Os6Hp5HcVAq+X3Whi//NxKGX6mPMb+SRPqw== + dependencies: + borsh "0.7.0" + events "3.3.0" + js-sha256 "0.9.0" + rxjs "7.8.1" + +"@near-wallet-selector/modal-ui-js@^8.9.10": + version "8.9.10" + resolved "https://registry.npmjs.org/@near-wallet-selector/modal-ui-js/-/modal-ui-js-8.9.10.tgz#9caa6e06e8c9a3b41227d8a018f022e28c7b40f2" + integrity sha512-NrtwRZwMotQ/XeULRe3tCfSyMwtVlnwVqUaHi0vJ1CKijSc5xR9fmVv1Bl9+xjP1bykhniWchb4O1wE1lTm3MQ== + dependencies: + "@near-wallet-selector/core" "8.9.10" + copy-to-clipboard "3.3.3" + qrcode "1.5.3" + "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": version "2.1.8-no-fsevents.3" resolved "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" @@ -5924,7 +5943,7 @@ bootstrap@^5.2.1, bootstrap@^5.3.1: resolved "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz#de35e1a765c897ac940021900fcbb831602bac38" integrity sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg== -borsh@^0.7.0: +borsh@0.7.0, borsh@^0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== @@ -6538,6 +6557,13 @@ cookies@~0.9.0: depd "~2.0.0" keygrip "~1.1.0" +copy-to-clipboard@3.3.3: + version "3.3.3" + resolved "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" + integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA== + dependencies: + toggle-selection "^1.0.6" + copy-webpack-plugin@^9.0.1: version "9.1.0" resolved "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-9.1.0.tgz#2d2c460c4c4695ec0a58afb2801a1205256c4e6b" @@ -8979,7 +9005,7 @@ joi@17.9.1: "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" -js-sha256@^0.9.0: +js-sha256@0.9.0, js-sha256@^0.9.0: version "0.9.0" resolved "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA== @@ -11906,6 +11932,13 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rxjs@7.8.1, rxjs@^7.5.2, rxjs@^7.5.5: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + rxjs@^6.6.3: version "6.6.7" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" @@ -11913,13 +11946,6 @@ rxjs@^6.6.3: dependencies: tslib "^1.9.0" -rxjs@^7.5.2, rxjs@^7.5.5: - version "7.8.1" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - sade@^1.7.3, sade@^1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" @@ -12746,6 +12772,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toggle-selection@^1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" + integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== + toidentifier@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"