${title}
diff --git a/src/client/views/utils/wallet-info.ts b/src/client/views/utils/wallet-info.ts
index db33167c..1d544fb5 100644
--- a/src/client/views/utils/wallet-info.ts
+++ b/src/client/views/utils/wallet-info.ts
@@ -54,7 +54,6 @@ const flowSVG = `
`
-// its bigger resolution image
const imToken = {
imgSmall: `
@@ -192,7 +191,6 @@ export function getWalletInfo(providerType: SupportedWalletProviders): WalletInf
function getEvmInjectedProviderInfo() {
const browser = getBrowserData()
- // default fallback icon
let injectedWallet = ethereumWallet
if (browser.brave) injectedWallet = braveWallet
diff --git a/src/client/views/view-interface.ts b/src/client/views/view-interface.ts
index a777c114..64456a30 100644
--- a/src/client/views/view-interface.ts
+++ b/src/client/views/view-interface.ts
@@ -40,9 +40,7 @@ export abstract class AbstractView implements ViewInterface {
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
- public init(): void {
- // Init can be used to implement extra constructor code without overriding the constructor
- }
+ public init(): void {}
abstract render(): void
diff --git a/src/core/messaging.ts b/src/core/messaging.ts
index fb834f51..cfc0d376 100644
--- a/src/core/messaging.ts
+++ b/src/core/messaging.ts
@@ -24,8 +24,7 @@ export interface ResponseInterfaceBase {
export enum ResponseActionBase {
COOKIE_CHECK = 'cookie-check',
ERROR = 'error',
- SHOW_FRAME = 'show-frame', // User input required in the iframe - don't resolve promise yet, setup iframe view if required.
- // USER_CANCEL = "user_cancel" Could be handled different to an error
+ SHOW_FRAME = 'show-frame',
}
declare global {
@@ -63,14 +62,9 @@ export class Messaging {
// TODO do we need this verificaton?
// if (document.location.hash !== "#safari-iframe-test")
- //
- // this.iframeStorageSupport = !isMacOrIOS() && !isBrave();
this.iframeStorageSupport = !browserBlocksIframeStorage()
}
- // Uncomment to test popup mode
- // this.iframeStorageSupport = false;
-
if (!forceTab && this.iframeStorageSupport !== false) {
try {
return await this.sendIframe(request)
@@ -90,7 +84,6 @@ export class Messaging {
let newLocation = this.constructUrl(id, request)
// TODO change in MAJOR release
- // stay non-prefixed items, because its breaking change, we can remove non-prefixed params until MAJOR version released
for (const prefix of PREFIXES) {
newLocation += `&${prefix}redirect=true&${prefix}requestor=${encodeURIComponent(redirectUrl)}`
}
@@ -155,7 +148,6 @@ export class Messaging {
let listener = (event: any) => {
if (event.data.target) {
- // we dont use this field at the moment
return
}
diff --git a/src/index.ts b/src/index.ts
index 2d3eec6b..eca183ff 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,7 +1,6 @@
export { Client } from './client/index'
export { AbstractView, ViewInterface } from './client/views/view-interface'
export { Outlet } from './outlet/index'
-// Allow users to easily extend in-built views
export { Start } from './client/views/start'
export { SelectWallet } from './client/views/select-wallet'
export { SelectIssuers } from './client/views/select-issuers'
diff --git a/src/outlet/__tests__/outlet.spec.ts b/src/outlet/__tests__/outlet.spec.ts
index 86e621db..a2fb5128 100644
--- a/src/outlet/__tests__/outlet.spec.ts
+++ b/src/outlet/__tests__/outlet.spec.ts
@@ -17,6 +17,19 @@ const outlet = new Outlet({
'MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABL+y43T1OJFScEep69/yTqpqnV/jzONz9Sp4TEHyAJ7IPN9+GHweCX1hT4OFxt152sBN3jJc1s0Ymzd8pNGZNoQ=',
})
+describe('Test modal dialog', () => {
+ // TODO refactor the logic to enable unit tests to have an expected result
+ // via user event trigger
+ test('Expect modalDialogEventHandler to trigger user-accept', async () => {
+ const output = await outlet.modalDialogEventHandler('1', 'write')
+ })
+ // TODO refactor the logic to enable unit tests to have an expected result
+ // via user event trigger
+ test('Expect modalDialogEventHandler to trigger user-abort', async () => {
+ const output = await outlet.modalDialogEventHandler('1', 'read')
+ })
+})
+
describe('Test magic link token merging', () => {
// TicketId: 1235; Email 1
const ticket1 = {
@@ -47,9 +60,9 @@ describe('Test magic link token merging', () => {
const tokens = outlet.mergeNewToken(ticket3, [ticket1]) as any[]
expect(tokens.length).toBe(2)
})
+ // TODO refactor the logic to enable unit tests to have an expected result
test('Expect existing attestation with same ID to be overwritten', () => {
- const tokens = outlet.mergeNewToken(ticket2, [ticket1]) as any[]
- expect(true).toBe(true)
+ outlet.mergeNewToken(ticket2, [ticket1]) as any[]
})
test('Expect duplicate attestation to be skipped', () => {
const tokens = outlet.mergeNewToken(ticket1, [ticket1])
diff --git a/src/outlet/auth-handler.ts b/src/outlet/auth-handler.ts
index f47e55f6..e23f43dc 100644
--- a/src/outlet/auth-handler.ts
+++ b/src/outlet/auth-handler.ts
@@ -5,7 +5,7 @@ import { OutletAction } from '../client/messaging'
import { Outlet, OutletInterface } from './index'
import { Authenticator } from '@tokenscript/attestation'
import { logger, removeUrlSearchParams } from '../utils'
-import { isBrave, isMacOrIOS } from '../utils/support/getBrowserData'
+import { isBrave } from '../utils/support/getBrowserData'
export interface DevconToken {
ticketBlob: string
@@ -108,9 +108,7 @@ export class AuthHandler {
this.attestationOrigin = tokenObj.attestationOrigin
- // disable attestationInTab by default
this.attestationInTab = tokenObj.attestationInTab
- // !== undefined ? tokenObj.attestationInTab : (isBrave() || isMacOrIOS());
}
openAttestationApp() {
@@ -133,9 +131,6 @@ export class AuthHandler {
button.innerHTML = 'Click to get Email Attestation'
button.addEventListener('click', () => {
- // let winParams = preparePopupCenter(800, 700);
- // this.attestationTabHandler = window.open(this.attestationOrigin,"Attestation",winParams);
-
this.attestationTabHandler = window.open(this.attestationOrigin, 'Attestation')
button.remove()
@@ -155,8 +150,6 @@ export class AuthHandler {
this.rejectHandler(new Error('User closed TAB'))
}
}, 2000)
-
- // this.buttonOverlay.remove();
})
let wrapperID = this.wrapperBase + '_wrap_' + Date.now()
@@ -225,7 +218,6 @@ export class AuthHandler {
this.buttonOverlay.appendChild(button)
this.buttonOverlay.appendChild(styles)
document.body.appendChild(this.buttonOverlay)
- // button.click();
} else {
logger(2, 'open attestation in iframe')
this.createIframe()
@@ -240,20 +232,17 @@ export class AuthHandler {
if (this.redirectUrl) {
const curParams = new URLSearchParams(document.location.hash.substring(1))
- // Request parameters for attestation.id
const params = new URLSearchParams()
params.set('email', this.email)
params.set('address', this.address)
params.set('wallet', this.wallet)
- // Add extra params that will be required for the Attestation.id -> Outlet callback URL
const callbackUrl = new URL(this.redirectUrl)
const callbackParams = removeUrlSearchParams(new URLSearchParams(callbackUrl.hash.substring(1)))
callbackParams.set(URLNS + 'action', OutletAction.EMAIL_ATTEST_CALLBACK)
callbackParams.set(URLNS + 'issuer', this.tokenDef.collectionID)
callbackParams.set(URLNS + 'token', JSON.stringify(this.unsignedToken))
- // Outlet -> Client callback
const requestor = curParams.get(URLNS + 'requestor')
if (requestor) {
callbackParams.set(URLNS + 'requestor', requestor)
@@ -271,7 +260,6 @@ export class AuthHandler {
return
}
- // don't do it for brave, brave doesn't support access to indexDB through iframe
if (this.attestationInTab && !isBrave()) {
this.tryingToGetAttestationInBackground = true
}
@@ -382,14 +370,9 @@ export class AuthHandler {
}
if (typeof event.data.display !== 'undefined') {
- // force display/hide iframe/tab
-
if (event.data.display === true) {
- // display works for iframe only
if (this.iframeWrap) {
if (this.tryingToGetAttestationInBackground) {
- // doesnt have ready attestation,
- // lets open attestation in the new tab
this.tryingToGetAttestationInBackground = false
this.iframe.remove()
this.iframeWrap.remove()
@@ -399,13 +382,10 @@ export class AuthHandler {
this.iframeWrap.style.display = 'flex'
- // ask parent to show this iframe
if (this.outlet)
this.outlet.sendMessageResponse({
evtid: this.evtid,
evt: ResponseActionBase.SHOW_FRAME,
- // max_width: "700px",
- // min_height: "600px"
})
}
} else {
@@ -415,7 +395,6 @@ export class AuthHandler {
if (this.buttonOverlay) this.buttonOverlay.remove()
}
- // display works for iframe only
if (this.iframeWrap) {
this.iframeWrap.style.display = 'none'
}
@@ -427,7 +406,6 @@ export class AuthHandler {
}
if (this.attestationTabHandler) {
- // console.log("tab close disabled for now");
this.attestationTabHandler.close()
}
@@ -444,17 +422,5 @@ export class AuthHandler {
} catch (e: any) {
reject(e)
}
-
- // construct UseDevconTicket, see
- // https://github.com/TokenScript/attestation/blob/main/data-modules/src/UseDevconTicket.asd
-
- // TODO we dont have ready UseDevconTicket constructor yet
- // let useDevconTicket = new UseDevconTicket({
- // signedDevconTicket: signedDevonTicket,
- // identifierAttestation: identifierAttestation,
- // proof: proof
- // })
- // // Serialise it (for use as a transaction parameter) and return it
- // return useDevconTicket.serialize();
}
}
diff --git a/src/outlet/index.ts b/src/outlet/index.ts
index c59b1889..9faaabba 100644
--- a/src/outlet/index.ts
+++ b/src/outlet/index.ts
@@ -12,7 +12,6 @@ import {
import { logger, requiredParams, removeUrlSearchParams } from '../utils'
import { OutletAction, OutletResponseAction } from '../client/messaging'
import { AuthHandler } from './auth-handler'
-// requred for default TicketDecoder.
import { SignedDevconTicket } from '@tokenscript/attestation/dist/asn1/shemas/SignedDevconTicket'
import { AsnParser } from '@peculiar/asn1-schema'
import { ResponseActionBase, ResponseInterfaceBase, URLNS } from '../core/messaging'
@@ -31,7 +30,6 @@ export interface OutletInterface {
whitelistDialogHeight: string
whitelistDialogRenderer?: (permissionTxt: string, acceptBtn: string, denyBtn: string) => string
- // Possibly deprecated parameters which have defaults
tokenUrlName?: string
tokenSecretName?: string
tokenIdName?: string
@@ -74,7 +72,6 @@ export class Outlet {
this.tokenConfig = Object.assign(defaultConfig, config)
this.singleUse = singleUse
- // set default tokenReader
if (!this.tokenConfig.tokenParser) {
this.tokenConfig.tokenParser = readSignedTicket
}
@@ -94,9 +91,12 @@ export class Outlet {
this.urlParams = new URLSearchParams(params)
}
- // to avoid duplicate run in syncOutlet()
if (!this.singleUse) {
this.pageOnLoadEventHandler()
+ .then()
+ .catch((e) => {
+ logger(2, 'Outlet pageOnLoadEventHandler error: ' + e.message)
+ })
}
}
@@ -110,6 +110,13 @@ export class Outlet {
return filter ? JSON.parse(filter) : {}
}
+ async modalDialogEventHandler(evtid: any, access: string) {
+ const action = await this.whitelistCheck(evtid, access === 'write' ? 'write' : 'read')
+ if (action === 'user-accept') this.sendTokens(evtid)
+ else if (action === 'user-abort')
+ this.sendErrorResponse(evtid, 'USER_ABORT', this.getDataFromQuery('issuer'), 'offchain-issuer-connection')
+ }
+
async pageOnLoadEventHandler() {
const evtid = this.getDataFromQuery('evtid')
const action = this.getDataFromQuery('action')
@@ -119,22 +126,14 @@ export class Outlet {
if (requester) this.redirectCallbackUrl = new URL(requester)
- // disable this check, because mostly user will open MagicLink from QR code reader or by MagicLink click at email, so document.referrer will be empty
- // if (!document.referrer && !this.getDataFromQuery('DEBUG'))
- // return;
-
logger(2, 'Outlet received event ID ' + evtid + ' action ' + action + ' at ' + document.location.href)
- // Outlet Page OnLoad Event Handler
// TODO: should issuer be validated against requested issuer?
try {
switch (action) {
case OutletAction.GET_ISSUER_TOKENS: {
- await this.whitelistCheck(evtid, access === 'write' ? 'write' : 'read')
-
- this.sendTokens(evtid)
-
+ await this.modalDialogEventHandler(evtid, access)
break
}
case OutletAction.EMAIL_ATTEST_CALLBACK: {
@@ -145,7 +144,6 @@ export class Outlet {
const tokenString = this.getDataFromQuery('token')
let token = JSON.parse(tokenString)
- // Note: these params come from attestation.id and are not namespaced
const attestationBlob = this.getDataFromQuery('attestation', false)
const attestationSecret = '0x' + this.getDataFromQuery('requestSecret', false)
@@ -153,7 +151,6 @@ export class Outlet {
this,
evtid,
this.tokenConfig,
- // callbackParams.token,
await rawTokenCheck(token, this.tokenConfig),
null,
null,
@@ -162,7 +159,6 @@ export class Outlet {
const useToken = await authHandler.getUseToken(attestationBlob, attestationSecret)
- // re-direct back to origin
if (requesterURL) {
const params = new URLSearchParams(requesterURL.hash.substring(1))
params.set(URLNS + 'action', 'proof-callback')
@@ -173,8 +169,6 @@ export class Outlet {
params.delete('email')
params.delete('#email')
- // add tokens to avoid redirect loop
- // when use redirect to get tokens
let outlet = new Outlet(this.tokenConfig, true)
let issuerTokens = outlet.prepareTokenOutput({})
@@ -189,7 +183,6 @@ export class Outlet {
return
}
- // Same origin request, emit event
this.dispatchAuthCallbackEvent(issuer, useToken, null)
} catch (e: any) {
if (requesterURL) return this.proofRedirectError(issuer, e.message)
@@ -202,28 +195,20 @@ export class Outlet {
break
}
case OutletAction.GET_PROOF: {
- // This will re-direct with the params
const token: string = this.getDataFromQuery('token')
const wallet: string = this.getDataFromQuery('wallet')
const address: string = this.getDataFromQuery('address')
requiredParams(token, 'unsigned token is missing')
- this.sendTokenProof(evtid, token, address, wallet)
+ await this.sendTokenProof(evtid, token, address, wallet)
break
}
default: {
- // store local storage item that can be later used to check if third party cookies are allowed.
- // Note: This test can only be performed when the localstorage / cookie is assigned, then later requested.
- /* localStorage.setItem("cookie-support-check", "test");
- this.sendCookieCheck(evtid);*/
-
// TODO: Remove singleUse - this is only needed in negotiator that calls readMagicLink.
// move single link somewhere that it can be used by both Outlet & LocalOutlet
if (!this.singleUse) {
- await this.whitelistCheck(evtid, 'write')
await this.readMagicLink()
- this.sendTokens(evtid)
+ await this.modalDialogEventHandler(evtid, 'write')
}
-
break
}
}
@@ -248,8 +233,6 @@ export class Outlet {
const event = new Event('tokensupdated')
- // Dispatch the event to force negotiator to reread tokens.
- // MagicLinkReader part of Outlet usually works in the parent window, same as Client, so it use same document
document.body.dispatchEvent(event)
} catch (e) {
console.warn(e)
@@ -276,14 +259,12 @@ export class Outlet {
const tokenId = this.getUniqueTokenId(decodedTokenData)
- // Overwrite existing token
if (newTokenId === tokenId) {
existingTokens[index] = newToken
return existingTokens
}
}
- // Add as new token
existingTokens.push(newToken)
return existingTokens
}
@@ -327,11 +308,11 @@ export class Outlet {
(!accessWhitelist[origin] || (accessWhitelist[origin].type === 'read' && whiteListType === 'write'))
if (needsPermission /* || storageAccessRequired */) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve, reject) => {
const typeTxt = whiteListType === 'read' ? 'read' : 'read & write'
const permissionTxt = `${origin} is requesting ${typeTxt} access to your ${this.tokenConfig.title} tickets`
- const acceptBtn = 'Accept '
- const denyBtn = 'Deny '
+ const acceptBtn = 'Accept '
+ const denyBtn = 'Deny '
const content = this.tokenConfig.whitelistDialogRenderer
? this.tokenConfig.whitelistDialogRenderer(permissionTxt, acceptBtn, denyBtn)
@@ -358,32 +339,18 @@ export class Outlet {
document.body.insertAdjacentHTML('beforeend', content)
- document.getElementById('tn-access-accept').addEventListener('click', async () => {
- /* if (storageAccessRequired) {
- try {
- await document.requestStorageAccess()
- } catch (e) {
- console.error(e)
- reject(new Error('IFRAME_STORAGE'))
- return
- }
- // Ensure whitelist is loaded from top-level storage context
- // this is not required if already granted or using a browser without the storageAccess API
- accessWhitelist = JSON.parse(localStorage.getItem('tn-whitelist')) ?? {}
- }*/
-
+ document.getElementById('tn-access-accept').addEventListener('click', () => {
if (!accessWhitelist[origin] || whiteListType !== accessWhitelist[origin].type) {
accessWhitelist[origin] = {
type: whiteListType,
}
localStorage.setItem('tn-whitelist', JSON.stringify(accessWhitelist))
}
-
- resolve()
+ resolve('user-accept')
})
document.getElementById('tn-access-deny').addEventListener('click', () => {
- reject('USER_ABORT')
+ resolve('user-abort')
})
this.sendMessageResponse({
@@ -394,18 +361,9 @@ export class Outlet {
})
})
}
+ return 'user-accept'
}
- /* sendCookieCheck(evtid: string){
- this.sendMessageResponse({
- evtid: evtid,
- evt: ResponseActionBase.COOKIE_CHECK,
- data: {
- thirdPartyCookies: localStorage.getItem("cookie-support-check"),
- }
- });
- }*/
-
prepareTokenOutput(filter: any) {
const storageTokens = localStorage.getItem(this.tokenConfig.itemStorageKey)
@@ -419,7 +377,6 @@ export class Outlet {
const decodedTokens = decodeTokens(storageTokens, this.tokenConfig.tokenParser, this.tokenConfig.unsignedTokenDataName, includeSigned)
- // remove duplicates check
return filterTokens(decodedTokens, filter)
}
@@ -431,7 +388,6 @@ export class Outlet {
const redirect = this.getDataFromQuery('redirect') === 'true' ? document.location.href : false
try {
- // check if token issuer
let tokenObj = await rawTokenCheck(unsignedToken, this.tokenConfig)
let authHandler = new AuthHandler(this, evtid, this.tokenConfig, tokenObj, address, wallet, redirect, unsignedToken)
@@ -506,13 +462,14 @@ export class Outlet {
})
}
- public sendErrorResponse(evtid: any, error: string, issuer?: string) {
+ public sendErrorResponse(evtid: any, error: string, issuer?: string, type = 'error') {
if (this.redirectCallbackUrl) {
let url = this.redirectCallbackUrl
const params = new URLSearchParams(url.hash.substring(1))
params.set(URLNS + 'action', ResponseActionBase.ERROR)
- params.set(URLNS + 'issuer', issuer)
+ params.set(URLNS + 'issuer', issuer || this.tokenConfig.collectionID)
+ params.set(URLNS + 'type', type)
params.set(URLNS + 'error', error)
url.hash = '#' + params.toString()
@@ -553,32 +510,24 @@ export class Outlet {
}
public sendMessageResponse(response: ResponseInterfaceBase) {
- // dont send Message if no referrer defined
if (!document.referrer) {
return
}
let target
- // opened by JS
if (window.opener && window.opener !== window) {
target = window.opener
- // iframe
} else if (window.parent !== window) {
target = window.parent
}
- // let pUrl = new URL(document.referrer);
- // origin = pUrl.origin;
-
if (target) {
target.postMessage(response, '*')
}
// TODO: this is probably no longer needed as brave is set to always use redirect mode now
if (!this.isSameOrigin()) {
- // At least Brave iOS browser blocks close(), so user have to see the message and close tab.
- // Message appears when tokens succesully sent with postMessage
let style = `
background: #eee;
padding: 10px;
diff --git a/src/outlet/localOutlet.ts b/src/outlet/localOutlet.ts
index b750b101..56caba48 100644
--- a/src/outlet/localOutlet.ts
+++ b/src/outlet/localOutlet.ts
@@ -9,7 +9,6 @@ export class LocalOutlet {
constructor(config: OutletInterface & OffChainTokenConfig) {
this.tokenConfig = Object.assign(defaultConfig, config)
- // set default tokenReader
if (!this.tokenConfig.tokenParser) {
this.tokenConfig.tokenParser = readSignedTicket
}
@@ -26,7 +25,6 @@ export class LocalOutlet {
}
async authenticate(unsignedToken: any, address: string, wallet: string, redirectMode: false | string = false) {
- // check if token issuer
let tokenObj = await rawTokenCheck(unsignedToken, this.tokenConfig)
let authHandler = new AuthHandler(null, null, this.tokenConfig, tokenObj, address, wallet, redirectMode, unsignedToken)
diff --git a/src/theme/common.scss b/src/theme/common.scss
index e851f3a1..6354eb04 100644
--- a/src/theme/common.scss
+++ b/src/theme/common.scss
@@ -835,6 +835,9 @@ li.issuer-connect-banner-tn .fungible-token-btn {
outline: inherit;
width: 132px;
opacity: 0.8;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
}
.overlay-tn .skeleton-box {
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 66b6e7d0..f5b59362 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -15,7 +15,6 @@ export const requiredParams = (item: any, msg: string) => {
if (!item) throw new Error(msg)
}
-// shallow equality comparison
export const compareObjects = (o1: any, o2: any) => {
const keys1 = Object.keys(o1)
const keys2 = Object.keys(o2)
@@ -23,7 +22,6 @@ export const compareObjects = (o1: any, o2: any) => {
return false
}
for (let key of keys1) {
- // compare commitment (array)
if (typeof o2[key] === 'object') {
if (JSON.stringify(o1[key]) !== JSON.stringify(o2[key])) {
return false
diff --git a/src/utils/support/__tests__/getBrowswerData.spec.ts b/src/utils/support/__tests__/getBrowswerData.spec.ts
index 1f3fc162..f5d9bd33 100644
--- a/src/utils/support/__tests__/getBrowswerData.spec.ts
+++ b/src/utils/support/__tests__/getBrowswerData.spec.ts
@@ -1,8 +1,7 @@
import { getBrowserData } from './../getBrowserData'
Object.defineProperty(global.navigator, 'userAgent', {
- value:
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36',
+ value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36',
writable: true,
configurable: true,
})
diff --git a/src/utils/support/getBrowserData.ts b/src/utils/support/getBrowserData.ts
index 363b282d..a0bf08b0 100644
--- a/src/utils/support/getBrowserData.ts
+++ b/src/utils/support/getBrowserData.ts
@@ -69,7 +69,7 @@ export const getBrowserData = () => {
iOS: isIOS,
mac: isMac,
windows: isWindows,
- desktop: navigator.userAgent?.indexOf("Mobi") === -1,
+ desktop: navigator.userAgent?.indexOf('Mobi') === -1,
touchDevice: isTouchDevice,
metaMask: isMetaMask,
anyMetamask: !!windowEthereum.isMetaMask && !isTrust && !windowEthereum.isBraveWallet,
@@ -90,10 +90,7 @@ export const getBrowserData = () => {
}
export function isMacOrIOS() {
- return (
- !!window.safari ||
- /iphone|ipad|ipod|ios/.test(window.navigator.userAgent ? window.navigator.userAgent.toLowerCase() : '')
- )
+ return !!window.safari || /iphone|ipad|ipod|ios/.test(window.navigator.userAgent ? window.navigator.userAgent.toLowerCase() : '')
}
export function isBrave() {
@@ -103,12 +100,7 @@ export function isBrave() {
export function isIosSafari() {
let userAgent = window.navigator.userAgent ? window.navigator.userAgent.toLowerCase() : ''
- return (
- /ip(ad|od|hone)/i.test(userAgent) &&
- /webkit/i.test(userAgent) &&
- !/(crios|fxios|opios|mercury)/i.test(userAgent) &&
- !isBrave()
- )
+ return /ip(ad|od|hone)/i.test(userAgent) && /webkit/i.test(userAgent) && !/(crios|fxios|opios|mercury)/i.test(userAgent) && !isBrave()
}
export function isMacSafari() {
diff --git a/src/wallet/FlowProvider.ts b/src/wallet/FlowProvider.ts
index 985a3d43..313875a0 100644
--- a/src/wallet/FlowProvider.ts
+++ b/src/wallet/FlowProvider.ts
@@ -1,9 +1,5 @@
import * as fcl from '@onflow/fcl/dist/fcl.umd.min'
-// const unsubscribe = fcl.currentUser.subscribe(currentUser => {
-// console.log("The Current User", currentUser)
-// });
-
fcl.config({
'accessNode.api': 'https://rest-mainnet.onflow.org',
'discovery.wallet': 'https://fcl-discovery.onflow.org/authn',
diff --git a/src/wallet/Web3WalletProvider.ts b/src/wallet/Web3WalletProvider.ts
index 2e81e338..827b735a 100644
--- a/src/wallet/Web3WalletProvider.ts
+++ b/src/wallet/Web3WalletProvider.ts
@@ -85,12 +85,9 @@ export class Web3WalletProvider {
if (walletConnect?.wc?._connected) {
walletConnect
.disconnect()
- .then(() => {
- // console.log('WalletConnect session disconnected');
- })
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ .then(() => {})
.catch((error) => {
- console.error(error)
- // dirty way to remove session from local storage
localStorage.removeItem('walletconnect')
})
}
@@ -106,12 +103,9 @@ export class Web3WalletProvider {
if (universalWalletConnect.session) {
universalWalletConnect
.disconnect()
- .then(() => {
- // console.log('WalletConnect2 session disconnected');
- })
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ .then(() => {})
.catch((error) => {
- console.error(error)
- // dirty way to remove session from local storage
localStorage.removeItem('wc@2:client:0.3//session')
})
}
@@ -125,7 +119,6 @@ export class Web3WalletProvider {
}
localStorage.removeItem(Web3WalletProvider.LOCAL_STORAGE_KEY)
- // remove session storage for the case of flow network
sessionStorage.removeItem('CURRENT_USER')
}
@@ -144,7 +137,6 @@ export class Web3WalletProvider {
try {
await this.connectWith(connection.providerType, true)
} catch (e) {
- console.log("Wallet couldn't connect: " + e.message)
delete state[address]
this.saveConnections()
this.emitSavedConnection(address)
@@ -160,7 +152,6 @@ export class Web3WalletProvider {
// @ts-ignore
let address = await this[walletType as keyof Web3WalletProvider](checkConnectionOnly)
- // if user rejected connect to account
if (!address) throw new Error("Wallet didn't connect")
this.saveConnections()
@@ -242,47 +233,9 @@ export class Web3WalletProvider {
provider: any,
blockchain: SupportedBlockchainsParam,
) {
- // some blockchains, like solana, use addresses in base58
- // its case-sensitive, but chance to have collision
- // for single user almost zero
this.connections[address.toLowerCase()] = { address, chainId, providerType, provider, blockchain, ethers }
switch (blockchain) {
case 'solana':
- provider.on('connect', (publicKey) => {
- let newAddress = publicKey.toBase58()
- logger(2, 'connected wallet: ', newAddress)
- this.registerNewWalletAddress(newAddress, 'mainnet-beta', 'phantom', window.solana, 'solana')
- })
-
- // Forget user's public key once they disconnect
- provider.on('disconnect', () => {
- logger(2, 'disconnected wallet.')
- delete this.connections[address.toLowerCase()]
- /**
- * TODO do we need to disconnect all wallets?
- * for now user cant connect to multiple wallets
- * but do we need it for future?
- */
- this.client.disconnectWallet()
- })
-
- provider.on('accountChanged', (publicKey) => {
- delete this.connections[address.toLowerCase()]
- if (publicKey) {
- // Set new public key and continue as usual
- logger(2, `Switched to account ${publicKey.toBase58()}`)
- this.registerNewWalletAddress(publicKey.toBase58(), 'mainnet-beta', 'phantom', window.solana, 'solana')
- } else {
- logger(2, 'Disconnected from wallet')
- delete this.connections[address.toLowerCase()]
- /**
- * TODO do we need to disconnect all wallets?
- * for now user cant connect to multiple wallets
- * but do we need it for future?
- */
- this.client.disconnectWallet()
- }
- })
break
case 'flow':
provider.currentUser().subscribe((user) => {
@@ -291,55 +244,6 @@ export class Web3WalletProvider {
})
break
case 'evm':
- // @ts-ignore
- provider.on('accountsChanged', (accounts) => {
- logger(2, 'accountsChanged: ', accounts)
- if (!accounts || accounts.length === 0) {
- /**
- * TODO do we need to disconnect all wallets?
- * for now user cant connect to multiple wallets
- * but do we need it for future?
- */
- this.client.disconnectWallet()
- return
- }
-
- if (address === accounts[0]) return
-
- delete this.connections[address.toLowerCase()]
-
- address = accounts[0]
-
- this.registerNewWalletAddress(address, chainId, providerType, provider, 'evm')
-
- this.saveConnections()
-
- this.emitSavedConnection(address)
-
- this.client.getTokenStore().clearCachedTokens()
- this.client.enrichTokenLookupDataOnChainTokens()
- })
-
- // @ts-ignore
- provider.on('chainChanged', (_chainId: any) => {
- this.registerNewWalletAddress(address, _chainId, providerType, provider, 'evm')
-
- this.saveConnections()
-
- this.emitNetworkChange(_chainId)
- })
-
- // @ts-ignore
- // walletconnect
- provider.on('disconnect', (reason: any) => {
- if (reason?.message && reason.message.indexOf('MetaMask: Disconnected from chain') > -1) return
- /**
- * TODO do we need to disconnect all wallets?
- * for now user cant connect to multiple wallets
- * but do we need it for future?
- */
- this.client.disconnectWallet()
- })
break
default:
logger(2, 'Unknown blockchain, dont attach listeners')
@@ -359,14 +263,96 @@ export class Web3WalletProvider {
this.registerNewWalletAddress(curAccount, chainId, providerName, provider, 'evm')
+ // @ts-ignore
+ provider.provider.on('accountsChanged', (newAccounts) => {
+ logger(2, 'accountsChanged: ', newAccounts)
+ if (!newAccounts || newAccounts.length === 0) {
+ this.client.disconnectWallet()
+ return
+ }
+
+ if (curAccount === newAccounts[0]) return
+
+ delete this.connections[curAccount.toLowerCase()]
+ curAccount = newAccounts[0]
+
+ this.registerNewWalletAddress(curAccount, chainId, providerName, provider, 'evm')
+
+ this.saveConnections()
+
+ this.emitSavedConnection(curAccount)
+
+ this.client.getTokenStore().clearCachedTokens()
+ this.client.enrichTokenLookupDataOnChainTokens()
+ })
+
+ // @ts-ignore
+ provider.provider.on('chainChanged', (_chainId: any) => {
+ this.registerNewWalletAddress(curAccount, _chainId, providerName, provider, 'evm')
+
+ this.saveConnections()
+
+ this.emitNetworkChange(_chainId)
+ })
+
+ // @ts-ignore
+ provider.provider.on('disconnect', (reason: any) => {
+ if (reason?.message && reason.message.indexOf('MetaMask: Disconnected from chain') > -1) return
+ /**
+ * TODO do we need to disconnect all wallets?
+ * for now user cant connect to multiple wallets
+ * but do we need it for future?
+ */
+ this.client.disconnectWallet()
+ })
+
return curAccount
}
+ private async registerSolanaProvider(provider: any, providerName: string) {
+ const connection = await provider.connect()
+ const accountAddress: string = connection.publicKey.toBase58()
+
+ let curAccount = accountAddress
+ this.registerNewWalletAddress(accountAddress, 'mainnet-beta', providerName, provider, 'solana')
+
+ // event hooks
+ provider.on('connect', (publicKey) => {
+ let newAddress = publicKey.toBase58()
+ logger(2, 'connected wallet: ', newAddress)
+ this.registerNewWalletAddress(newAddress, 'mainnet-beta', 'phantom', window.solana, 'solana')
+ })
+
+ provider.on('disconnect', () => {
+ logger(2, 'disconnected wallet.')
+ this.client.disconnectWallet()
+ })
+
+ provider.on('accountChanged', (publicKey) => {
+ if (publicKey) {
+ delete this.connections[curAccount.toLowerCase()]
+ // Set new public key and continue as usual
+ const newAccountAddress = publicKey.toBase58()
+ curAccount = newAccountAddress
+ this.registerNewWalletAddress(curAccount, 'mainnet-beta', 'phantom', window.solana, 'solana')
+ this.saveConnections()
+ this.emitSavedConnection(curAccount)
+ this.client.getTokenStore().clearCachedTokens()
+ this.client.enrichTokenLookupDataOnChainTokens()
+ } else {
+ logger(2, 'disconnected wallet.')
+ this.client.disconnectWallet()
+ }
+ })
+
+ return accountAddress
+ }
+
async MetaMask(checkConnectionOnly: boolean) {
logger(2, 'connect MetaMask')
if (typeof window.ethereum !== 'undefined') {
- await window.ethereum.enable() // fall back may be needed for FF to open Extension Prompt.
+ await window.ethereum.enable()
const provider = new ethers.providers.Web3Provider(window.ethereum, 'any')
@@ -380,9 +366,9 @@ export class Web3WalletProvider {
logger(2, 'connect Wallet Connect')
const walletConnectProvider = await import('./WalletConnectProvider')
-
const walletConnect = await walletConnectProvider.getWalletConnectProviderInstance(checkConnectionOnly)
+ let connecting = true
return new Promise((resolve, reject) => {
if (checkConnectionOnly) {
walletConnect.connector.on('display_uri', (err, payload) => {
@@ -390,14 +376,26 @@ export class Web3WalletProvider {
})
}
+ // this is for the edge case when user rejects connection from wallet
+ walletConnect.connector.on('disconnect', (err, payload) => {
+ if (connecting) {
+ connecting = false
+ reject(new Error('User rejected connection'))
+ }
+ })
+
walletConnect
.enable()
.then(() => {
+ connecting = false
const provider = new ethers.providers.Web3Provider(walletConnect, 'any')
resolve(this.registerEvmProvider(provider, 'WalletConnect'))
})
- .catch((e) => reject(e))
+ .catch((e) => {
+ connecting = false
+ reject(e)
+ })
})
}
@@ -418,7 +416,6 @@ export class Web3WalletProvider {
})
})
- // Subscribe to session delete
universalWalletConnect.on('session_delete', ({ id, topic }: { id: number; topic: string }) => {
// TODO: There is currently a bug in the universal provider that prevents this handler from being called.
// After this is fixed, this should handle the event correctly
@@ -432,8 +429,6 @@ export class Web3WalletProvider {
if (checkConnectionOnly && !universalWalletConnect.session) {
reject('Not connected')
} else {
- // let pairing
-
let connect
if (universalWalletConnect.session) {
@@ -448,21 +443,18 @@ export class Web3WalletProvider {
rpcMap: preSavedWalletOptions?.walletConnectV2?.rpcMap ?? walletConnectProvider.WC_DEFAULT_RPC_MAP,
},
},
- // pairingTopic: pairing?.topic,
})
}
connect
.then(() => {
logger(2, 'WC2 connected.....')
- // in case of enable() QRCodeModal undefined
QRCodeModal?.close()
const provider = new ethers.providers.Web3Provider(universalWalletConnect, 'any')
resolve(this.registerEvmProvider(provider, 'WalletConnectV2'))
})
.catch((e) => {
logger(2, 'WC2 connect error...', e)
- // in case of enable() QRCodeModal undefined
QRCodeModal?.close()
reject(e)
})
@@ -488,14 +480,7 @@ export class Web3WalletProvider {
logger(2, 'connect Phantom')
if (typeof window.solana !== 'undefined') {
- const connection = await window.solana.connect()
-
- const accountAddress: string = connection.publicKey.toBase58()
-
- // mainnet-beta
- // TODO: Create registerSolanaProvider method to create event listeners (see registerEvmProvider)
- this.registerNewWalletAddress(accountAddress, 'mainnet-beta', 'phantom', window.solana, 'solana')
- return accountAddress
+ return await this.registerSolanaProvider(window.solana, 'phantom')
} else {
throw new Error('Phantom is not available. Please check the extension is supported and active.')
}
diff --git a/src/wallet/__test__/wallet.spec.ts b/src/wallet/__test__/wallet.spec.ts
index 273023e3..efe73a05 100644
--- a/src/wallet/__test__/wallet.spec.ts
+++ b/src/wallet/__test__/wallet.spec.ts
@@ -1,4 +1,5 @@
/* eslint-disable no-mixed-spaces-and-tabs */
+import { ethers } from 'ethers'
import { TextDecoder, TextEncoder } from 'text-encoding'
global.TextEncoder = TextEncoder
global.TextDecoder = TextDecoder
@@ -119,3 +120,40 @@ describe('wallet spec', () => {
expect(safeConnectProvider).toBeDefined()
})
})
+
+describe('Provider tests', () => {
+ let web3WalletProvider: Web3WalletProvider
+ test('web3WalletProvider method connectWith - MetaMask', async () => {
+ web3WalletProvider = new Web3WalletProvider(tokenNegotiatorClient, null, null)
+ window.ethereum = {
+ enable: () => ['0x1263b90F4e1DFe89A8f9E623FF57adb252851fC3'],
+ }
+ try {
+ await web3WalletProvider.connectWith('MetaMask')
+ } catch (err) {
+ expect(err).toEqual(new Error('unsupported provider (argument="provider", value={}, code=INVALID_ARGUMENT, version=providers/5.7.2)'))
+ }
+ })
+
+ test('web3WalletProvider connect with void method for ETH connnection register wallet address', async () => {
+ web3WalletProvider = new Web3WalletProvider(tokenNegotiatorClient, null, null)
+ window.ethereum = {
+ enable: () => ['0x1263b90F4e1DFe89A8f9E623FF57adb252851fC3'],
+ // provider: {
+ // events: {
+ // accountsChanged: () => {},
+ // chainChanged: () => {},
+ // disconnect: () => {},
+ // },
+ // },
+ }
+ web3WalletProvider.registerNewWalletAddress(
+ '0x1263b90F4e1DFe89A8f9E623FF57adb252851fC3'.toLocaleLowerCase(),
+ '1',
+ 'MetaMask',
+ window.ethereum,
+ 'evm',
+ )
+ expect(web3WalletProvider.connections['0x1263b90f4e1dfe89a8f9e623ff57adb252851fc3'].provider).toBeDefined()
+ })
+})