diff --git a/CHANGELOG.md b/CHANGELOG.md index 31ec8d42..71b76caa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ > Description -Minor release for off chain token attestations +Feature release including multi-hook support, improved UX for off chain token holders and general improvements. ### Upgrade Steps -- Update NPM package to version 3.1.1 +- Update NPM package to version 3.2.0 ### Breaking Changes @@ -12,19 +12,26 @@ Minor release for off chain token attestations ### New Features -- Upgrade attestation lib to support validation of new EAS off-chain attestation version -- Prevent delete of signedToken property on token object -- Change single token auth to use tokenId instead of sending the entire token object in URL -- Add labels to default ticket schema +- Extended the configuration options for 'active' mode ui text +- Off Chain Token Issuer UX improvements to notify the end user when the page will re-direct +- Re-direct view state UX improvements for 'active' mode +- Re-direct accept / deny options given to end user (to cancel the loading of tokens) +- Multi-Hook support added +- Added selected token issuer keys to 'tokens-selected' event hook +- Explicitly include ethers library availabilty via Token Negotiator library interface `client.externalUtils.evm.ethers` +- Aligned on chain authentication with off chain user interface (for single token authentication on the client side). Multi token on chain authentication is not yet supported (via the current library features). ### Bug Fixes -[none] +- TS interface custom view +- Single token off chain authentication incompatibility interface fix +- EAS authentication fixed via attestation.id updated dependency @tokenscript/attestation to version "0.7.0-rc.2" -### Performance Improvements +### Performance / Quality Improvements -[none] +- Reduced the duplication of variable definitions (moved to a common constants file). +- Increased unit test coverage **Full Change log**: -https://github.com/TokenScript/token-negotiator/compare/v3.1.0....v3.1.1 +https://github.com/TokenScript/token-negotiator/compare/v3.1.1....v3.2.0 diff --git a/README.md b/README.md index 86b9c781..edec7f96 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # `token-negotiator` -The Token Negotiator provides the client gateway to connect user digital tokens from on or off chain sources, enabling developers to create bespoke tokenised web experiences. +Token Negotiator is an open source technology that enables you build web experiences for users around the tokens, collectibles, coins and attestations they hold. Enabled across EVM, Solana, Flow, Chiliz and EOS (Ultra). Create logic flows to enable personalized web experiences. ## Usage @@ -29,8 +29,8 @@ const negotiator = new Client({ ​​ negotiator.negotiate(); ​ ​ -negotiator.on("tokens-selected", (tokens) => { - console.log('owner tokens found: ', tokens); +negotiator.on("tokens-selected", ({ selectedTokens, selectedIssuerKeys }) => { + console.log('user selected tokens: ', selectedTokens); }); ​ ``` diff --git a/index.html b/index.html index 21972878..e7f29069 100644 --- a/index.html +++ b/index.html @@ -20,108 +20,115 @@ window.negotiator = new negotiator.Client({ type: "active", issuers: [ - { - onChain: true, - fungible: true, - chain: "eth", - blockchain: "evm", - collectionID: "socios", - contract: "0x3506424F91fD33084466F402d5D97f05F8e3b4AF", - oAuth2options: { - consumerKey: "YOUR_CONSUMER_KEY", - partnerTag: "YOUR_PARTNER_TAG", - endpoints: { - redirectURI: { - path: "http://localhost:5000/user-login-callback", - params: {} - }, - userBalance: { - path: 'http://localhost:5000/user-balance', - params: {} - }, - userNfts: { - path: 'http://localhost:5000/user-nfts', - params: {} - }, - userLogout: { - path: 'http://localhost:5000/user-logout', - params: {} - }, - }, - }, - }, - { - hideToggle: true, - noTokenMsg: - "

If you hold a Devcon ticket, please visit your magicLink email again and open the link in this browser.

You may reload this page after and try again.

", - onChain: false, - tokenName: "devcon6", - collectionID: "devcon6", - title: "Devcon VI Ticket Attestation", - image: "https://devcon-vi.attest.tickets/img/nft_bg.png", - tokenOrigin: "https://devcon-vi.attest.tickets/outlet.html", - attestationOrigin: "https://test.attestation.id/", - tokenUrlName: "ticket", - cryptoVerify: "https://form.smarttokenlabs.com", - tokenSecretName: "secret", - tokenIdName: "mail", - unsignedTokenDataName: "ticket", - whitelistDialogRenderer: (permissionTxt, acceptBtn, denyBtn) => { - return ` -
-
- devcon -
-
-

${permissionTxt}

- ${acceptBtn} - ${denyBtn} -
-
- `; - }, - signedTokenWhitelist: [ - "https://devcon-vi.attest.tickets/", - "https://dev.loc", - "https://devconnect.loc/", - "https://devconnect.antopolbus.rv.ua/", - "https://perks.antopolbus.rv.ua/", - "https://stage-perks.smarttokenlabs.com/", - "http://localhost:5000/", + // { + // onChain: true, + // fungible: true, + // chain: "eth", + // blockchain: "evm", + // collectionID: "socios", + // contract: "0x3506424F91fD33084466F402d5D97f05F8e3b4AF", + // oAuth2options: { + // consumerKey: "YOUR_CONSUMER_KEY", + // partnerTag: "YOUR_PARTNER_TAG", + // endpoints: { + // redirectURI: { + // path: "http://localhost:5000/user-login-callback", + // params: {} + // }, + // userBalance: { + // path: 'http://localhost:5000/user-balance', + // params: {} + // }, + // userNfts: { + // path: 'http://localhost:5000/user-nfts', + // params: {} + // }, + // userLogout: { + // path: 'http://localhost:5000/user-logout', + // params: {} + // }, + // }, + // }, + // }, + // { + // hideToggle: true, + // noTokenMsg: + // "

If you hold a Devcon ticket, please visit your magicLink email again and open the link in this browser.

You may reload this page after and try again.

", + // onChain: false, + // tokenName: "devcon6", + // collectionID: "devcon6", + // title: "Devcon VI Ticket Attestation", + // image: "https://devcon-vi.attest.tickets/img/nft_bg.png", + // tokenOrigin: "https://devcon-vi.attest.tickets/outlet.html", + // attestationOrigin: "https://test.attestation.id/", + // tokenUrlName: "ticket", + // cryptoVerify: "https://form.smarttokenlabs.com", + // tokenSecretName: "secret", + // tokenIdName: "mail", + // unsignedTokenDataName: "ticket", + // whitelistDialogRenderer: (permissionTxt, acceptBtn, denyBtn) => { + // return ` + //
+ //
+ // devcon + //
+ //
+ //

${permissionTxt}

+ // ${acceptBtn} + // ${denyBtn} + //
+ //
+ // `; + // }, + // signedTokenWhitelist: [ + // "https://devcon-vi.attest.tickets/", + // "https://dev.loc", + // "https://devconnect.loc/", + // "https://devconnect.antopolbus.rv.ua/", + // "https://perks.antopolbus.rv.ua/", + // "https://stage-perks.smarttokenlabs.com/", + // "http://localhost:5000/", - "https://hashkey.smarttokenlabs.com/", - "https://fayre-devcon.webflow.io/", - "https://fenbushi-devcon.webflow.io/", - "https://metasearch-devcon.webflow.io/", - "https://conspicuis-devcon.webflow.io/", - "https://devcon-vi.brandextender.io/", - "https://www.edcon.io/", - ], - itemStorageKey: "devconnectTokens", - base64senderPublicKeys: { - 4: "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEAGJAHCiHbrCNAY9fAMdom4dGD6v/KkTIgRCkwLCjXFTkXWGrCEXHaZ8kWwdqlu0oYCrNQ2vdlqOl0s26/LzO8A==|MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE-N-jGYo1OuO77XItd3zT-oIhZEVC44uqOhtJkmBsBNDTp3Seu_vmuLB-b4whNeFNBuJTsT7AHUiUe3JOwCcegA==", - }, - base64attestorPubKey: - "MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABL+y43T1OJFScEep69/yTqpqnV/jzONz9Sp4TEHyAJ7IPN9+GHweCX1hT4OFxt152sBN3jJc1s0Ymzd8pNGZNoQ=", - }, - { - collectionID: "devcon", - onChain: false, - title: "Devcon", - tokenIdName: "id", - image: - "https://raw.githubusercontent.com/TokenScript/token-negotiator/main/mock-images/devcon.svg", - tokenOrigin: "http://localhost:3000/", - attestationOrigin: "https://test.attestation.id/", - base64senderPublicKeys: { - 6: "MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABGMxHraqggr2keTXszIcchTjYjH5WXpDaBOYgXva82mKcGnKgGRORXSmcjWN2suUCMkLQj3UNlZCFWF10wIrrlw=", - 55: "MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABGMxHraqggr2keTXszIcchTjYjH5WXpDaBOYgXva82mKcGnKgGRORXSmcjWN2suUCMkLQj3UNlZCFWF10wIrrlw=", - }, - base64attestorPubKey: - "MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABL+y43T1OJFScEep69/yTqpqnV/jzONz9Sp4TEHyAJ7IPN9+GHweCX1hT4OFxt152sBN3jJc1s0Ymzd8pNGZNoQ=", - ticketIssuersUrlWebsitePrivateKey: - "MIICSwIBADCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBBIIBVTCCAVECAQEEIM/T+SzcXcdtcNIqo6ck0nJTYzKL5ywYBFNSpI7R8AuBoIHjMIHgAgEBMCwGByqGSM49AQECIQD////////////////////////////////////+///8LzBEBCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcEQQR5vmZ++dy7rFWgYpXOhwsHApv82y3OKNlZ8oFbFvgXmEg62ncmo8RlXaT7/A4RCKj9F7RIpoVUGZxH0I/7ENS4AiEA/////////////////////rqu3OavSKA7v9JejNA2QUECAQGhRANCAARjMR62qoIK9pHk17MyHHIU42Ix+Vl6Q2gTmIF72vNpinBpyoBkTkV0pnI1jdrLlAjJC0I91DZWQhVhddMCK65c", - }, + // "https://hashkey.smarttokenlabs.com/", + // "https://fayre-devcon.webflow.io/", + // "https://fenbushi-devcon.webflow.io/", + // "https://metasearch-devcon.webflow.io/", + // "https://conspicuis-devcon.webflow.io/", + // "https://devcon-vi.brandextender.io/", + // "https://www.edcon.io/", + // ], + // itemStorageKey: "devconnectTokens", + // base64senderPublicKeys: { + // 4: "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEAGJAHCiHbrCNAY9fAMdom4dGD6v/KkTIgRCkwLCjXFTkXWGrCEXHaZ8kWwdqlu0oYCrNQ2vdlqOl0s26/LzO8A==|MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE-N-jGYo1OuO77XItd3zT-oIhZEVC44uqOhtJkmBsBNDTp3Seu_vmuLB-b4whNeFNBuJTsT7AHUiUe3JOwCcegA==", + // }, + // base64attestorPubKey: + // "MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABL+y43T1OJFScEep69/yTqpqnV/jzONz9Sp4TEHyAJ7IPN9+GHweCX1hT4OFxt152sBN3jJc1s0Ymzd8pNGZNoQ=", + // }, + // { + // collectionID: "devcon", + // onChain: false, + // title: "Devcon", + // tokenIdName: "id", + // image: + // "https://raw.githubusercontent.com/TokenScript/token-negotiator/main/mock-images/devcon.svg", + // tokenOrigin: "http://localhost:3002/", + // attestationOrigin: "https://test.attestation.id/", + // base64senderPublicKeys: { + // 6: "MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABGMxHraqggr2keTXszIcchTjYjH5WXpDaBOYgXva82mKcGnKgGRORXSmcjWN2suUCMkLQj3UNlZCFWF10wIrrlw=", + // 55: "MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABGMxHraqggr2keTXszIcchTjYjH5WXpDaBOYgXva82mKcGnKgGRORXSmcjWN2suUCMkLQj3UNlZCFWF10wIrrlw=", + // }, + // base64attestorPubKey: + // "MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABL+y43T1OJFScEep69/yTqpqnV/jzONz9Sp4TEHyAJ7IPN9+GHweCX1hT4OFxt152sBN3jJc1s0Ymzd8pNGZNoQ=", + // ticketIssuersUrlWebsitePrivateKey: + // "MIICSwIBADCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBBIIBVTCCAVECAQEEIM/T+SzcXcdtcNIqo6ck0nJTYzKL5ywYBFNSpI7R8AuBoIHjMIHgAgEBMCwGByqGSM49AQECIQD////////////////////////////////////+///8LzBEBCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcEQQR5vmZ++dy7rFWgYpXOhwsHApv82y3OKNlZ8oFbFvgXmEg62ncmo8RlXaT7/A4RCKj9F7RIpoVUGZxH0I/7ENS4AiEA/////////////////////rqu3OavSKA7v9JejNA2QUECAQGhRANCAARjMR62qoIK9pHk17MyHHIU42Ix+Vl6Q2gTmIF72vNpinBpyoBkTkV0pnI1jdrLlAjJC0I91DZWQhVhddMCK65c", + // }, + // { + // collectionID: "NBA", + // onChain: true, + // contract: "A.0b2a3299cc857e29.TopShot", + // chain: "mainnet", + // blockchain: "flow" + // } // { // collectionID: "devcon", // onChain: false, @@ -167,20 +174,20 @@ // "MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABL+y43T1OJFScEep69/yTqpqnV/jzONz9Sp4TEHyAJ7IPN9+GHweCX1hT4OFxt152sBN3jJc1s0Ymzd8pNGZNoQ=", // }, // { - // hideToggle : false, - // noTokenMsg: "

If you have a token please:

1. Open your magic link inside this browser.
2. Refresh this page.

", - // collectionID: 'devcon', - // title: "Devcon", - // onChain: false, - // tokenOrigin: "http://localhost:3002/", - // attestationOrigin: "https://attestation.id/", - // unEndPoint: "https://crypto-verify.herokuapp.com/use-devcon-ticket", - // image: "https://raw.githubusercontent.com/TokenScript/token-negotiator/main/mock-images/devcon.svg", - // base64senderPublicKeys: - // { - // "AttestationDAO" : 'MFYwEAYHKoZIzj0CAQYFK...' - // }, - // base64attestorPubKey: "MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABL+y43T1OJFScEep69/yTqpqnV/jzONz9Sp4TEHyAJ7IPN9+GHweCX1hT4OFxt152sBN3jJc1s0Ymzd8pNGZNoQ=" + // hideToggle: false, + // noTokenMsg: "

If you have a token please:

1. Open your magic link inside this browser.
2. Refresh this page.

", + // collectionID: 'devcon', + // title: "Devcon", + // onChain: false, + // tokenOrigin: "http://localhost:3002/", + // attestationOrigin: "https://attestation.id/", + // unEndPoint: "https://crypto-verify.herokuapp.com/use-devcon-ticket", + // image: "https://raw.githubusercontent.com/TokenScript/token-negotiator/main/mock-images/devcon.svg", + // base64senderPublicKeys: + // { + // "AttestationDAO": 'MFYwEAYHKoZIzj0CAQYFK...' + // }, + // base64attestorPubKey: "MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////////////////////////////////////v///C8wRAQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHBEEEeb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5hIOtp3JqPEZV2k+/wOEQio/Re0SKaFVBmcR9CP+xDUuAIhAP////////////////////66rtzmr0igO7/SXozQNkFBAgEBA0IABL+y43T1OJFScEep69/yTqpqnV/jzONz9Sp4TEHyAJ7IPN9+GHweCX1hT4OFxt152sBN3jJc1s0Ymzd8pNGZNoQ=" // }, // { // collectionID: "crypto-cowboy-country", @@ -204,7 +211,7 @@ // blockchain: "solana", // }, // { - // hideToggle : false, + // hideToggle: false, // collectionID: "pixel", // collectionAddress: "166424b16c4c7f40eb852f9eb2d0d6869a9ed7ac54a8e6afbd516676bfbbeb19", // onChain: true, @@ -212,37 +219,37 @@ // chain: "mainnet", // blockchain: "solana", // }, - { - onChain: true, - collectionID: "expansion-punks", - contract: "0x0d0167a823c6619d430b1a96ad85b888bcf97c37", - chain: "eth", - blockchain: "evm", - }, - { - onChain: true, - collectionID: "fantom-collection-test", - contract: "0x94e22c14118353651636f9af43cd0a5a08b93da3", - chain: "fantom", - blockchain: "evm", - }, - { - hideToggle: true, - noTokenMsg: - "

If you have a token please:

1. Open your magic link inside this browser.
2. Refresh this page.

", - onChain: true, - collectionID: "bsc-collection-test", - contract: "0xF5db804101d8600c26598A1Ba465166c33CdAA4b", - chain: "bsc", - blockchain: "evm", - }, // { // onChain: true, - // collectionID: "tt", - // contract: '0x76be3b62873462d2142405439777e971754e8e77', - // chain: 'eth', + // collectionID: "expansion-punks", + // contract: "0x0d0167a823c6619d430b1a96ad85b888bcf97c37", + // chain: "eth", // blockchain: "evm", // }, + // { + // onChain: true, + // collectionID: "fantom-collection-test", + // contract: "0x94e22c14118353651636f9af43cd0a5a08b93da3", + // chain: "fantom", + // blockchain: "evm", + // }, + // { + // hideToggle: true, + // noTokenMsg: + // "

If you have a token please:

1. Open your magic link inside this browser.
2. Refresh this page.

", + // onChain: true, + // collectionID: "bsc-collection-test", + // contract: "0xF5db804101d8600c26598A1Ba465166c33CdAA4b", + // chain: "bsc", + // blockchain: "evm", + // }, + { + onChain: true, + collectionID: "tt", + contract: '0x76be3b62873462d2142405439777e971754e8e77', + chain: 'eth', + blockchain: "evm", + }, // { onChain: true, collectionID: "Perion", contract: '0x96af92ae2d822a0f191455ceca4d4e7ee227668e', chain: 'mumbai', blockchain: "evm" }, // { collectionID: 'COOLCATS-#2426-14', onChain: true, contract: '0x3C7e352481F4b2fdEc1e642a3f0018661c77513D', chain: 'eth', openSeaSlug: 'devcon-vi-suit-up-collection' }, // { collectionID: 'Town-Hall', onChain: true, contract: '0x81b30ff521D1fEB67EDE32db726D95714eb00637', chain: 'Optimism' }, @@ -259,11 +266,11 @@ // chain: "Arbitrum", // }, // { - // collectionID: "NBA", - // onChain: true, - // contract: "A.0b2a3299cc857e29.TopShot", - // chain: "mainnet", - // blockchain: "flow", + // collectionID: "NBA", + // onChain: true, + // contract: "A.0b2a3299cc857e29.TopShot", + // chain: "mainnet", + // blockchain: "flow", // }, // { // onChain: true, @@ -276,35 +283,48 @@ // // Add fungible as a boolean // // symbol: 'USX' // }, - // { - // contract: "0x107065a122f92636a1358a70a0efe0f1a080a7e5", - // onChain: true, - // fungible: true, - // collectionID: "USX", - // chain: "matic", - // }, - // { - // contract: "0x429f49faec3d568ef83ec803e02df78e25d5ee7d", - // onChain: true, - // fungible: true, - // collectionID: "Ella", - // chain: "matic", - // }, - // { - // contract: "0xaaa5b9e6c589642f98a1cda99b9d024b8407285a", - // onChain: true, - // fungible: true, - // collectionID: "TITAN", - // chain: "matic", - // }, + { + contract: "0x107065a122f92636a1358a70a0efe0f1a080a7e5", + onChain: true, + fungible: true, + collectionID: "USX", + chain: "matic", + }, + { + contract: "0x429f49faec3d568ef83ec803e02df78e25d5ee7d", + onChain: true, + fungible: true, + collectionID: "Ella", + chain: "matic", + }, + { + contract: "0xaaa5b9e6c589642f98a1cda99b9d024b8407285a", + onChain: true, + fungible: true, + collectionID: "TITAN", + chain: "matic", + }, ], uiOptions: { openingHeading: "Open a new world of perks, benefits and opportunities with your attestation, collectible or token.", issuerHeading: "Get discount with Ticket", - repeatAction: "try again", + repeatAction: "Retry", + dismissAction: 'Dismiss', + loadAction: 'Load Collection', + noTokensFoundEvent: 'No Tokens Found', + balancesFoundEvent: 'Balance Found', + nftsFoundEvent: 'Token(s) Found', + reDirectIssuerHeadingEvent: 'Connecting to Issuers...', + reDirectIssuerBodyEvent: "Your browser will re-direct shortly", + authenticationHeadingEvent: 'Authenticating...', + authenticationBodyEvent: "You may need to sign a new challenge in your wallet", + walletDidntConnectAction: "Wallet didn't connect", + cancelAction: "Cancel", + openingAction: "Let's Go!", theme: theme, position: "top-right", + userCancelIssuerAutoRedirectTimer: 2000, }, unSupportedUserAgent: { authentication: { @@ -334,7 +354,13 @@ }); window.negotiator.on("tokens-selected", (tokens) => { - console.log("tokens-selected", tokens); + console.log("tokens-selected 1", tokens); + }); + window.negotiator.on("tokens-selected", (tokens) => { + console.log("tokens-selected 2", tokens); + }); + window.negotiator.on("tokens-selected", (tokens) => { + console.log("tokens-selected 3", tokens); }); window.negotiator.on("connected-wallet", (wallet) => { console.log("connected wallet", wallet); diff --git a/package-lock.json b/package-lock.json index 0399de40..9313037f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,18 @@ { "name": "@tokenscript/token-negotiator", - "version": "3.1.1", + "version": "3.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@tokenscript/token-negotiator", - "version": "3.1.1", + "version": "3.2.0", "license": "MIT", "dependencies": { "@onflow/fcl": "^1.3.2", "@onflow/types": "^1.0.5", "@peculiar/asn1-schema": "^2.2.0", - "@tokenscript/attestation": "0.7.0-rc.1", + "@tokenscript/attestation": "0.7.0-rc.2", "@toruslabs/torus-embed": "^2.2.5", "@walletconnect/qrcode-modal": "^1.8.0", "@walletconnect/types": "^2.1.5", @@ -70,6 +70,14 @@ "webpack-cli": "^4.9.2" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -1818,21 +1826,21 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", - "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -1853,9 +1861,9 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", "dependencies": { "type-fest": "^0.20.2" }, @@ -1889,9 +1897,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", - "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", + "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -2711,9 +2719,9 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -4669,9 +4677,9 @@ } }, "node_modules/@tokenscript/attestation": { - "version": "0.7.0-rc.1", - "resolved": "https://registry.npmjs.org/@tokenscript/attestation/-/attestation-0.7.0-rc.1.tgz", - "integrity": "sha512-FDA41LBbhYphXBDXl/kec4N7MLtBo91LLrwuF0I0PSnoC4Y4ZukbHl4lpVgSjGnu4vrs6QIjXRG2APsaScIYLw==", + "version": "0.7.0-rc.2", + "resolved": "https://registry.npmjs.org/@tokenscript/attestation/-/attestation-0.7.0-rc.2.tgz", + "integrity": "sha512-5C9KLU0cyT9gkvb6RYXZIlp/VWCZKq+Isev/aPtwcU/sR3N9ZYobJRQsayTUnxYERRq8YOmADxZjr7K2mCnEjQ==", "hasInstallScript": true, "dependencies": { "@ethereum-attestation-service/eas-sdk": "^0.29.1", @@ -4918,9 +4926,9 @@ } }, "node_modules/@types/create-hash": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/create-hash/-/create-hash-1.2.2.tgz", - "integrity": "sha512-Fg8/kfMJObbETFU/Tn+Y0jieYewryLrbKwLCEIwPyklZZVY2qB+64KFjhplGSw+cseZosfFXctXO+PyIYD8iZQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/create-hash/-/create-hash-1.2.3.tgz", + "integrity": "sha512-dh2NzQVdUY3+C3+ZnQ1T6JEa4RdyiWfTcL/MX/XfE8dCT//TG9rYDliDgPr3SJ9qnVek9Lzn1HCU/pH+KzrJXg==", "dev": true, "dependencies": { "@types/node": "*" @@ -6636,9 +6644,9 @@ } }, "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "bin": { "acorn": "bin/acorn" }, @@ -8907,26 +8915,26 @@ } }, "node_modules/eslint": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", - "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", + "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.38.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.50.0", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -8934,22 +8942,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -9076,9 +9081,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -9148,15 +9153,18 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/estraverse": { @@ -9266,13 +9274,13 @@ } }, "node_modules/espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -10522,7 +10530,13 @@ "node_modules/grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, "node_modules/har-schema": { "version": "2.0.0", @@ -15408,16 +15422,16 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -18455,9 +18469,9 @@ "dev": true }, "node_modules/ts-loader": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", - "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", + "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -19727,6 +19741,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -20054,6 +20069,11 @@ } }, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==" + }, "@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -21283,18 +21303,18 @@ } }, "@eslint-community/regexpp": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", - "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==" + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==" }, "@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -21309,9 +21329,9 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", "requires": { "type-fest": "^0.20.2" } @@ -21332,9 +21352,9 @@ } }, "@eslint/js": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", - "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==" + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", + "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==" }, "@ethereum-attestation-service/eas-contracts": { "version": "0.27.1", @@ -21816,9 +21836,9 @@ } }, "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", "requires": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -23328,9 +23348,9 @@ } }, "@tokenscript/attestation": { - "version": "0.7.0-rc.1", - "resolved": "https://registry.npmjs.org/@tokenscript/attestation/-/attestation-0.7.0-rc.1.tgz", - "integrity": "sha512-FDA41LBbhYphXBDXl/kec4N7MLtBo91LLrwuF0I0PSnoC4Y4ZukbHl4lpVgSjGnu4vrs6QIjXRG2APsaScIYLw==", + "version": "0.7.0-rc.2", + "resolved": "https://registry.npmjs.org/@tokenscript/attestation/-/attestation-0.7.0-rc.2.tgz", + "integrity": "sha512-5C9KLU0cyT9gkvb6RYXZIlp/VWCZKq+Isev/aPtwcU/sR3N9ZYobJRQsayTUnxYERRq8YOmADxZjr7K2mCnEjQ==", "requires": { "@ethereum-attestation-service/eas-sdk": "^0.29.1", "@peculiar/asn1-schema": "^2.3.3", @@ -23522,9 +23542,9 @@ } }, "@types/create-hash": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/create-hash/-/create-hash-1.2.2.tgz", - "integrity": "sha512-Fg8/kfMJObbETFU/Tn+Y0jieYewryLrbKwLCEIwPyklZZVY2qB+64KFjhplGSw+cseZosfFXctXO+PyIYD8iZQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/create-hash/-/create-hash-1.2.3.tgz", + "integrity": "sha512-dh2NzQVdUY3+C3+ZnQ1T6JEa4RdyiWfTcL/MX/XfE8dCT//TG9rYDliDgPr3SJ9qnVek9Lzn1HCU/pH+KzrJXg==", "dev": true, "requires": { "@types/node": "*" @@ -25009,9 +25029,9 @@ } }, "acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==" }, "acorn-globals": { "version": "7.0.1", @@ -26803,26 +26823,26 @@ } }, "eslint": { - "version": "8.38.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", - "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", + "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", "requires": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.38.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.50.0", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -26830,22 +26850,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "dependencies": { @@ -26890,9 +26907,9 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -27041,18 +27058,18 @@ } }, "eslint-visitor-keys": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", - "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==" + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" }, "espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "requires": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^3.4.1" } }, "esprima": { @@ -28121,7 +28138,13 @@ "grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, "har-schema": { "version": "2.0.0", @@ -31788,16 +31811,16 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" } }, "os-browserify": { @@ -34048,9 +34071,9 @@ } }, "ts-loader": { - "version": "9.4.2", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", - "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", + "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -35033,7 +35056,8 @@ "word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true }, "wordwrapjs": { "version": "4.0.1", diff --git a/package.json b/package.json index c8e88ef0..71828dd9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@tokenscript/token-negotiator", - "version": "3.1.1", - "description": "Token-negotiator a token attestation bridge between web 2.0 and 3.0.", + "version": "3.2.0", + "description": "Token Negotiator is an open source technology that enables you build web experiences for users around the tokens, collectibles, coins and attestations they hold. Enabled across EVM, Solana, Flow, Chiliz and EOS (Ultra). Create logic flows to enable personalized web experiences.", "module": "dist/index.js", "types": "dist/index.d.ts", "files": [ @@ -49,7 +49,7 @@ "@onflow/fcl": "^1.3.2", "@onflow/types": "^1.0.5", "@peculiar/asn1-schema": "^2.2.0", - "@tokenscript/attestation": "0.7.0-rc.1", + "@tokenscript/attestation": "0.7.0-rc.2", "@toruslabs/torus-embed": "^2.2.5", "@walletconnect/qrcode-modal": "^1.8.0", "@walletconnect/types": "^2.1.5", diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts new file mode 100644 index 00000000..e332377a --- /dev/null +++ b/src/__tests__/index.spec.ts @@ -0,0 +1,7 @@ +import { Client } from '../index' + +describe('src spec', () => { + test('ensure client is defined', async () => { + expect(Client).toBeDefined() + }) +}) diff --git a/src/client/__tests__/client.spec.ts b/src/client/__tests__/client.spec.ts index b64efd86..1ecae022 100644 --- a/src/client/__tests__/client.spec.ts +++ b/src/client/__tests__/client.spec.ts @@ -1,17 +1,11 @@ // @ts-nocheck import { AbstractAuthentication } from '../auth/abstractAuthentication' -import { AttestedAddress } from '../auth/attestedAddress' import { Client } from '../index' import { TicketZKProof } from '../auth/ticketZKProof' -import { URLNS } from '../../core/messaging' -import { Outlet, defaultConfig } from '../../outlet/index' -import { Client as client_2_0, Outlet as outlet_2_0 } from 'tn2_0' -import { Client as client_2_2, Outlet as outlet_2_2 } from 'tn2_2' import { OffChainTokenConfig } from '../interface' +import { TextEncoder, TextDecoder } from 'util'; -function delay(time) { - return new Promise((resolve) => setTimeout(resolve, time)) -} +Object.assign(global, { TextDecoder, TextEncoder }); let tokenIssuer: OffChainTokenConfig = { collectionID: 'devcon', @@ -27,37 +21,8 @@ let tokenIssuer: OffChainTokenConfig = { base64attestorPubKey: '', } -let tokenIssuer2: OffChainTokenConfig = { - collectionID: 'edcon', - title: 'Devcon', - onChain: false, - tokenOrigin: 'http://some.url/', - attestationOrigin: 'https://stage.attestation.id/', - unEndPoint: 'https://crypto-verify.herokuapp.com/use-devcon-ticket', - image: 'https://raw.githubusercontent.com/TokenScript/token-negotiator/main/mock-images/devcon.svg', - base64senderPublicKeys: { - 10: 'MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEAGJAHCiHbrCNAY9fAMdom4dGD6v/KkTIgRCkwLCjXFTkXWGrCEXHaZ8kWwdqlu0oYCrNQ2vdlqOl0s26/LzO8A==|MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEAGJAHCiHbrCNAY9fAMdom4dGD6v/KkTIgRCkwLCjXFTkXWGrCEXHaZ8kWwdqlu0oYCrNQ2vdlqOl0s26/LzO8B==', - }, - base64attestorPubKey: '', -} - -let tokenIssuer3: OffChainTokenConfig = { - collectionID: 'devconnect', - title: 'Devcon', - onChain: false, - tokenOrigin: 'http://some.url/', - attestationOrigin: 'https://stage.attestation.id/', - unEndPoint: 'https://crypto-verify.herokuapp.com/use-devcon-ticket', - image: 'https://raw.githubusercontent.com/TokenScript/token-negotiator/main/mock-images/devcon.svg', - base64senderPublicKeys: { - 55: 'MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEAGJAHCiHbrCNAY9fAMdom4dGD6v/KkTIgRCkwLCjXFTkXWGrCEXHaZ8kWwdqlu0oYCrNQ2vdlqOl0s26/LzO8A==', - }, - base64attestorPubKey: '', -} - const config = { type: 'passive', - // requred to force redirect mode for Client 2.2 enableOffChainRedirectMode: true, issuers: [tokenIssuer], } @@ -126,6 +91,7 @@ class LocalStorageMock { } describe('client spec', () => { + test('tokenNegotiatorClient a failed new instance of client - missing issuers key', () => { let client = new Client({ type: 'passive', @@ -137,6 +103,54 @@ describe('client spec', () => { }) }) + test('client library interface method switch theme can be used', () => { + const tokenNegotiatorClient = getOffChainConfigClient() + let client = new Client({ + type: 'active', + issuers: tokenNegotiatorClient.getTokenStore().getCurrentIssuers(), + options: {}, + }) + client.negotiate().then(() => { + client.switchTheme('dark'); + }); + }) + + test('client library can add token via magic link', () => { + const tokenNegotiatorClient = getOffChainConfigClient() + let client = new Client({ + type: 'active', + issuers: tokenNegotiatorClient.getTokenStore().getCurrentIssuers(), + options: {}, + }) + client.negotiate().then(() => { + client.addTokenViaMagicLink('https://outlet-stage.brandconnector.io/#type=asn&ticket=MIGTME0MATYCAgTUAgECBEEEDoIoocY3CRCPqbHzou84M7C-cWAfzrTnPwz7qWGWv3UVn53jWeTG8CSykYVk-SUdNwlGTiD-h8Xsf2cGzniNPQNCAK6Q153BMkBc9E2AR3MjEWNU_f-15Cl2w0EIvaqGoqYrHKSMm3CIjKik_KBRTzRrbQqM5lGe_jUivjhmfwR5HOUc&secret=6191295225924896663249851881131284553005448461740837624904453538708620527945&id=bob%40alice.com') + }); + }) + + test('client can trigger promise to manage', () => { + const tokenNegotiatorClient = getOffChainConfigClient() + let client = new Client({ + type: 'active', + issuers: tokenNegotiatorClient.getTokenStore().getCurrentIssuers(), + options: {}, + }) + client.negotiate().then(() => { + client.handleWalletRequired([]); + }); + }) + + test('client can handle proof error', () => { + const tokenNegotiatorClient = getOffChainConfigClient() + let client = new Client({ + type: 'active', + issuers: tokenNegotiatorClient.getTokenStore().getCurrentIssuers(), + options: {}, + }) + client.negotiate().then(() => { + client.handleProofError({ error: 'something went wrong' }, 'devconVi'); + }); + }) + test('tokenNegotiatorClient a failed new instance of client - missing issuers length', () => { let client = new Client({ type: 'passive', @@ -313,21 +327,21 @@ describe('client spec', () => { }) test('tokenNegotiatorClient on callback with event type tokens-selected ', () => { + const mockCallback = jest.fn(); const tokenNegotiatorClient = getOffChainConfigClient() const event = 'tokens-selected' - tokenNegotiatorClient.on(event, () => { - logger(2, event) - }) - expect(tokenNegotiatorClient.clientCallBackEvents[event]).toBeDefined() + tokenNegotiatorClient.on(event, mockCallback) + tokenNegotiatorClient.eventSender(event, { selectedTokens: {} }); + expect(mockCallback).toHaveBeenCalled(); }) test('tokenNegotiatorClient on callback with event type tokens-loaded', () => { + const mockCallback = jest.fn(); const tokenNegotiatorClient = getOffChainConfigClient() const event = 'tokens-loaded' - tokenNegotiatorClient.on(event, () => { - logger(2, event) - }) - expect(tokenNegotiatorClient.clientCallBackEvents[event]).toBeDefined() + tokenNegotiatorClient.on(event, mockCallback); + tokenNegotiatorClient.eventSender(event, null); + expect(mockCallback).toHaveBeenCalled(); }) test('tokenNegotiatorClient on callback must have an event type', () => { @@ -494,122 +508,5 @@ describe('client spec', () => { const error = tokenNegotiatorClient.handleRecievedRedirectMessages() expect(error).toEqual(null) }) -}) - -// TODO: Reimplement cross-version test for version 3.1 -/* describe('client spec cross-version', () => { - let originalDocument = document - let originalLocation = window.location - - const nonLocalUrl = 'https://non-local.url' - - beforeAll(() => { - Object.defineProperty(global, 'document', { - value: { - location: { - href: '', - referrer: '', - hash: '', - search: '', - origin: '', - }, - addEventListener: () => { - return true - }, - }, - }) - - Object.defineProperty(window, 'location', { - value: { - href: nonLocalUrl, - origin: nonLocalUrl, - }, - writable: true, // possibility to override - }) - - // required to force redirect mode - window.navigator.brave = 1 - }) - - afterAll(() => { - Object.defineProperty(global, 'document', { - value: originalDocument, - }) - - Object.defineProperty(window, 'location', { - value: originalLocation, - }) - }) - - test('Outlet_2_0 save magicLink', async () => { - let magicLinkParams = - '?ticket=MIGTME0MATYCAgFNAgEBBEEEF6_tKK2dCfLQiwS4FuqmiQDVrafJ05vCOkYN4iT28JULCClrvI2_kGTxrL12sXlH9w9mohLQlMdmaWvFzaZVlgNCAKu7SESOLf7L5sjZPcTQVkAu9YTC88mNK8oyUjiP2gsnTUxr0BGr0eWSTYmbDqNlX3JXOEqvEH39LEQjWsXn44oc&secret=45845870684&mail=oleh.hryb.us@gmail.com' - - window.location.search = magicLinkParams - - // LocalStorage must be empty - expect(localStorage.getItem(defaultConfig.itemStorageKey)).toBe(null) - new outlet_2_0(tokenIssuer) - - expect(localStorage.getItem(defaultConfig.itemStorageKey)).toContain(magicLinkParams) - }) - - test('Redirect Client_lastest -> Outlet 2.2', async () => { - let client = new Client(config) - - // prepare Redirect URL - await client.negotiate() - let url = new URL(window.location.href) - expect(new URLSearchParams(url.hash.substring(1)).get('action')).toBe('get-issuer-tokens') - - window.location.hash = url.hash - new outlet_2_2(tokenIssuer) - - // need delay, because pageOnLoadEventHandler() is async - await delay(1000) - - let hash = new URL(document.location.href).hash.substring(1) - expect(new URLSearchParams(hash).get('action')).toBe('get-issuer-tokens-response') - }) - test('Redirect Client_2.2 -> Outlet_lastest', async () => { - let client = new client_2_2(config) - - window.location.hash = '' - window.location.href = 'http://localhost' - document.location.hash = '' - document.location.href = 'http://localhost' - // prepare Redirect URL - await client.negotiate() - let url = new URL(document.location.href) - expect(new URLSearchParams(url.hash.substring(1)).get('action')).toBe('get-issuer-tokens') - - // console.log( - // `window.location.hash = "${window.location.hash}", - // window.location.href = "${window.location.href}", - // document.location.hash = "${document.location.hash}", - // document.location.href = "${document.location.href}",` - // ) - - window.location.hash = url.hash - document.location.hash = url.hash - document.referrer = nonLocalUrl - localStorage.setItem('tn-whitelist', '{"https://non-local.url":{"type":"read"}}') - - new Outlet(tokenIssuer) - - // need delay, because pageOnLoadEventHandler() is async - await delay(1000) - - let hash = new URL(window.location.href).hash.substring(1) - expect(new URLSearchParams(hash).get('action')).toBe('get-issuer-tokens-response') - }) - - test('tokenNegotiatorClient read prefixed param', async () => { - window.location.hash = `p1=1&${URLNS}p2=2` - let client = getOffChainConfigClient() - - expect(client.getDataFromQuery('p2')).toBe('2') - expect(client.getDataFromQuery('p1')).toBe('1') - }) -})*/ +}) diff --git a/src/client/__tests__/eventHookHandler.spec.ts b/src/client/__tests__/eventHookHandler.spec.ts new file mode 100644 index 00000000..a6e2feba --- /dev/null +++ b/src/client/__tests__/eventHookHandler.spec.ts @@ -0,0 +1,27 @@ +// @ts-nocheck +import { EventHookHandler } from '../eventHookHandler' + +const mockCallback = jest.fn(); + +describe('client spec', () => { + test('eventHookHandler can subscribe and trigger events', async () => { + const eventHookHandler = new EventHookHandler() + eventHookHandler.subscribe('selected-tokens', mockCallback); + eventHookHandler.trigger('selected-tokens', { data: "It's not a bug; it's an undocumented feature" }); + expect(mockCallback).toHaveBeenCalled(); + }) + test('eventHookHandler can unsubscribe', async () => { + const eventHookHandler = new EventHookHandler() + const unsubscribe = eventHookHandler.subscribe('selected-tokens', mockCallback); + unsubscribe(); + eventHookHandler.trigger('selected-tokens', { data: "Make it work, make it right, make it fast." }); + expect(mockCallback).toHaveLength(0); + }) + test('eventHookHandler can unsubscribe all subscriptions of event type', async () => { + const eventHookHandler = new EventHookHandler() + eventHookHandler.subscribe('selected-tokens', mockCallback); + eventHookHandler.unsubscribe('selected-tokens'); + eventHookHandler.trigger('selected-tokens', { data: "Make it work, make it right, make it fast." }); + expect(mockCallback).toHaveLength(0); + }) +}) \ No newline at end of file diff --git a/src/client/__tests__/selectIssuers.spec.ts b/src/client/__tests__/selectIssuers.spec.ts index 2985b80e..4647c950 100644 --- a/src/client/__tests__/selectIssuers.spec.ts +++ b/src/client/__tests__/selectIssuers.spec.ts @@ -22,7 +22,19 @@ describe('select issuers spec', () => { blockchain: 'evm', }, ], - options: {}, + uiOptions: { + openingHeading: + "Open a new world of perks, benefits and opportunities with your attestation, collectible or token.", + issuerHeading: "Get discount with Ticket", + repeatAction: "Retry", + dismissAction: 'Dismiss', + loadAction: 'Load Collection', + noTokensFoundEvent: 'No Tokens Found', + balancesFoundEvent: 'Balance Found', + nftsFoundEvent: 'Token(s) Found', + openingAction: "Let's Go!", + position: "top-right", + }, }) } diff --git a/src/client/auth/abstractAuthentication.ts b/src/client/auth/abstractAuthentication.ts index bf3fd098..e1c7e307 100644 --- a/src/client/auth/abstractAuthentication.ts +++ b/src/client/auth/abstractAuthentication.ts @@ -1,5 +1,6 @@ import { AuthenticateInterface, MultiTokenInterface, OffChainTokenConfig, OnChainTokenConfig } from '../interface' import { Client } from '../index' +import { LOCAL_STORAGE_PROOF_KEY } from '../../constants' export interface AuthenticationResult { type: string @@ -40,8 +41,6 @@ export interface AuthenticationMethodMulti { export abstract class AbstractAuthentication { public abstract TYPE: string - public static STORAGE_KEY = 'tn-proof' - protected client: Client constructor(client?: Client) { @@ -58,7 +57,7 @@ export abstract class AbstractAuthentication { challenges[this.getFullKey(key)] = proof - localStorage.setItem(AbstractAuthentication.STORAGE_KEY, JSON.stringify(challenges)) + localStorage.setItem(LOCAL_STORAGE_PROOF_KEY, JSON.stringify(challenges)) } protected getSavedProof(key: string) { @@ -78,7 +77,7 @@ export abstract class AbstractAuthentication { if (challenges[fullKey]) delete challenges[fullKey] - localStorage.setItem(AbstractAuthentication.STORAGE_KEY, JSON.stringify(challenges)) + localStorage.setItem(LOCAL_STORAGE_PROOF_KEY, JSON.stringify(challenges)) } private getFullKey(key: string) { @@ -86,8 +85,7 @@ export abstract class AbstractAuthentication { } private getProofs(): { [key: string]: AuthenticationResult } { - const data = localStorage.getItem(AbstractAuthentication.STORAGE_KEY) - - return data && data.length ? JSON.parse(data) : {} + const data = localStorage.getItem(LOCAL_STORAGE_PROOF_KEY) + return data?.length ? JSON.parse(data) : {} } } diff --git a/src/client/auth/signedUNChallenge.ts b/src/client/auth/signedUNChallenge.ts index 37cc4195..0e476ce7 100644 --- a/src/client/auth/signedUNChallenge.ts +++ b/src/client/auth/signedUNChallenge.ts @@ -34,7 +34,7 @@ export class SignedUNChallenge extends AbstractAuthentication implements Authent } } - if (!currentProof){ + if (!currentProof) { let walletConnection = connection.provider currentProof = { diff --git a/src/client/auth/ticketZKProof.ts b/src/client/auth/ticketZKProof.ts index cb996da7..7fadedb3 100644 --- a/src/client/auth/ticketZKProof.ts +++ b/src/client/auth/ticketZKProof.ts @@ -10,7 +10,7 @@ import { shouldUseRedirectMode } from '../../utils/support/getBrowserData' import { EasZkProof } from '@tokenscript/attestation/dist/eas/EasZkProof' import { DEFAULT_EAS_SCHEMA, TokenType } from '../../outlet/ticketStorage' import { OutletIssuerInterface } from '../../outlet/interfaces' -import { DEFAULT_RPC_MAP } from '../../core/constants' +import { DEFAULT_RPC_MAP } from '../../constants' export class TicketZKProof extends AbstractAuthentication implements AuthenticationMethod { TYPE = 'ticketZKProof' @@ -123,7 +123,14 @@ export class TicketZKProof extends AbstractAuthentication implements Authenticat if (type === 'eas') { const schema = issuerConfig.eas ? { fields: issuerConfig.eas.fields } : DEFAULT_EAS_SCHEMA const easZkProof = new EasZkProof(schema, { ...DEFAULT_RPC_MAP, ...ethRPCMap }) - await easZkProof.validateUseTicket(proof, issuerConfig.base64attestorPubKey, issuerConfig.base64senderPublicKeys, ethAddress) + await easZkProof.validateUseTicket( + proof, + issuerConfig.base64attestorPubKey, + issuerConfig.base64senderPublicKeys, + ethAddress, + 'asn', + 'asn', + ) } else { Authenticator.validateUseTicket(proof, issuerConfig.base64attestorPubKey, issuerConfig.base64senderPublicKeys, ethAddress) } diff --git a/src/client/auth/util/SafeConnect.ts b/src/client/auth/util/SafeConnect.ts index f2f829fd..a6b75dc9 100644 --- a/src/client/auth/util/SafeConnect.ts +++ b/src/client/auth/util/SafeConnect.ts @@ -1,6 +1,7 @@ import { KeyStore } from '@tokenscript/attestation/dist/safe-connect/KeyStore' import { uint8tohex } from '@tokenscript/attestation/dist/libs/utils' import { EthereumKeyLinkingAttestation } from '@tokenscript/attestation/dist/safe-connect/EthereumKeyLinkingAttestation' +import { HOLDING_KEY_ALGORITHM } from '../../../constants' export interface ChallengeInterface { expiry: number @@ -24,17 +25,16 @@ export interface ProofResponseInterface { } export class SafeConnect { - public static HOLDING_KEY_ALGORITHM = 'RSASSA-PKCS1-v1_5' public static keyStore = new KeyStore() public static async getLinkPrivateKey() { - let keys = await SafeConnect.keyStore.getOrCreateKey(SafeConnect.HOLDING_KEY_ALGORITHM) + let keys = await SafeConnect.keyStore.getOrCreateKey(HOLDING_KEY_ALGORITHM) return keys.attestHoldingKey.privateKey } public static async getLinkPublicKey() { - let keys = await SafeConnect.keyStore.getOrCreateKey(SafeConnect.HOLDING_KEY_ALGORITHM) + let keys = await SafeConnect.keyStore.getOrCreateKey(HOLDING_KEY_ALGORITHM) return uint8tohex(keys.holdingPubKey) } diff --git a/src/client/auth/util/UN.ts b/src/client/auth/util/UN.ts index b874d238..d714cc38 100644 --- a/src/client/auth/util/UN.ts +++ b/src/client/auth/util/UN.ts @@ -3,6 +3,7 @@ import { sign } from 'tweetnacl' import { base58ToUint8Array, hexStringToUint8Array, strToHexStr, strToUtfBytes } from '../../../utils' import * as flowTypes from '@onflow/types' import { ecc } from 'eosjs/dist/eosjs-ecc-migration' +import { DEFAULT_UN_ENDPOINT, COMMON_UN_API_KEY } from '../../../constants'; export interface UNInterface { expiration: number @@ -17,21 +18,17 @@ export interface UNInterface { } export class UN { - private static DEFAULT_ENDPOINT = 'https://api.smarttokenlabs.com/un' - private static COMMON_API_KEY = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9qZWN0IjoidG9rZW4tbmVnb3RpYXRvciIsImlhdCI6MTY4OTc1NzQ4Nn0.ELE1OVvVFY1yrWlbnxtQur6dgeVxmKlPb9LZ_8cMOs8' - public static async getNewUN(endPoint: string): Promise { try { let response if (endPoint) { response = await fetch(endPoint) } else { - response = await fetch(this.DEFAULT_ENDPOINT, { + response = await fetch(DEFAULT_UN_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', - 'x-stl-key': this.COMMON_API_KEY, + 'x-stl-key': COMMON_UN_API_KEY, }, body: JSON.stringify({ targetDomain: window.location.origin }), }) diff --git a/src/client/eventHookHandler.ts b/src/client/eventHookHandler.ts new file mode 100644 index 00000000..1c86c5b6 --- /dev/null +++ b/src/client/eventHookHandler.ts @@ -0,0 +1,34 @@ +export class EventHookHandler { + subIds: number + subscriptions: { [eventName: string]: { [token: number]: (data: any) => void } } + + constructor() { + this.subIds = 0 + this.subscriptions = {} + } + + subscribe(eventName: string, fn: (data: any) => void) { + if (!this.subscriptions[eventName]) this.subscriptions[eventName] = {} + const token = ++this.subIds + this.subscriptions[eventName][token] = fn + return () => this.unsubscribe(eventName, token) + } + + unsubscribe(eventName: string, token: number) { + if (!token) delete this.subscriptions[eventName] + this.subscriptions[eventName] && delete this.subscriptions[eventName][token] + } + + trigger(eventName: string, data: any) { + this.publish(eventName, data) + return data + } + + publish(eventName, data) { + const subs = this.subscriptions[eventName] + if (!subs) { + return false + } + Object.values(subs).forEach((sub) => sub(data)) + } +} diff --git a/src/client/index.ts b/src/client/index.ts index 5e9c38a0..3e1aa689 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -8,6 +8,7 @@ import { removeUrlSearchParams, createIssuerHashMap, createIssuerHashArray, + sleep, } from '../utils' import { getNftCollection, getNftTokens } from '../utils/token/nftProvider' import { TokenStore } from './tokenStore' @@ -32,9 +33,12 @@ import { shouldUseRedirectMode } from '../utils/support/getBrowserData' import { VERSION } from '../version' import { getFungibleTokenBalances, getFungibleTokensMeta } from '../utils/token/fungibleTokenProvider' import { URLNS } from '../core/messaging' -import { DecodedToken, TokenType } from '../outlet/ticketStorage' +import { TokenType } from '../outlet/ticketStorage' import { MultiTokenAuthRequest, MultiTokenAuthResult, OutletIssuerInterface, ProofResult } from '../outlet/interfaces' import { AttestationIdClient } from '../outlet/attestationIdClient' +import { EventHookHandler } from './eventHookHandler' +import { ethers } from 'ethers' +import { TokenListItemInterface } from './views/token-list' if (typeof window !== 'undefined') window.tn = { VERSION } @@ -104,14 +108,20 @@ export class Client { public config: NegotiationInterface private web3WalletProvider: Web3WalletProvider private messaging: Messaging + private eventHookHandler: EventHookHandler protected ui: UiInterface - private clientCallBackEvents: { [key: string]: (data: any) => Promise | void } = {} protected tokenStore: TokenStore + public externalUtils = { + evm: { + ethers, + }, + } private uiUpdateCallbacks: { [type in UIUpdateEventType] } = { [UIUpdateEventType.ISSUERS_LOADING]: undefined, [UIUpdateEventType.ISSUERS_LOADED]: undefined, [UIUpdateEventType.WALLET_DISCONNECTED]: undefined, } + private userCancelTokenAutoload: boolean private urlParams: URLSearchParams @@ -120,6 +130,8 @@ export class Client { }*/ constructor(config: NegotiationInterface) { + this.eventHookHandler = new EventHookHandler() + if (window.location.hash) { this.urlParams = new URLSearchParams(window.location.hash.substring(1)) let action = this.getDataFromQuery('action') @@ -131,6 +143,9 @@ export class Client { this.config = this.mergeConfig(defaultConfig, config) + // TODO investigate if this works correctly. + this.config.autoLoadTokens = localStorage.getItem('tn-autoload-tokens') === 'false' ? false : this.config.autoLoadTokens + this.tokenStore = new TokenStore(this.config.autoEnableTokens, this.config.tokenPersistenceTTL) // @ts-ignore if (this.config.issuers?.length > 0) this.tokenStore.updateIssuers(this.config.issuers) @@ -292,20 +307,18 @@ export class Client { // TODO: Move to token store OR select-wallet view - this method is very similar to getCurrentBlockchains() public hasIssuerForBlockchain(blockchain: 'evm' | 'solana' | 'flow' | 'ultra', useOauth = false) { + const _blockchain = blockchain.toLocaleLowerCase() return ( this.config.issuers.filter((issuer: OnChainTokenConfig) => { - const oAuthIssuer = useOauth && issuer.oAuth2options - if (blockchain === 'evm' && !issuer.onChain) { - return true - } - if (blockchain === 'solana' && typeof window.solana === 'undefined') { - return false - } - if (blockchain === 'ultra' && typeof window.ultra === 'undefined') { - return false - } - const blockChainNameMatch = issuer.blockchain ? issuer.blockchain.toLowerCase() : 'evm' === blockchain - return blockChainNameMatch && (oAuthIssuer || !issuer.oAuth2options) + const issuerBlockChain = issuer.blockchain?.toLocaleLowerCase() + const blockChainUsed = issuerBlockChain === blockchain + const solanaEnabled = blockChainUsed && _blockchain === 'solana' && typeof window.solana !== 'undefined' + const ultraEnabled = blockChainUsed && _blockchain === 'ultra' && typeof window.ultra !== 'undefined' + const flowEnabled = blockChainUsed && _blockchain === 'flow' + const evmEnabled = blockChainUsed && _blockchain === 'evm' && !issuer.oAuth2options && !useOauth + const sociosEnabled = blockChainUsed && _blockchain === 'evm' && issuer.oAuth2options && useOauth + const fallBackToEVM = _blockchain === 'evm' && !issuerBlockChain && !useOauth + return solanaEnabled || ultraEnabled || evmEnabled || sociosEnabled || flowEnabled || fallBackToEVM }).length > 0 ) } @@ -326,6 +339,7 @@ export class Client { this.tokenStore.clearCachedTokens() this.eventSender('connected-wallet', null) this.eventSender('disconnected-wallet', null) + localStorage.removeItem('tn-autoload-tokens') this.triggerUiUpdateCallback(UIUpdateEventType.WALLET_DISCONNECTED) } catch (e) { logger(2, 'Failed to disconnect wallet', e) @@ -465,14 +479,17 @@ export class Client { autoOpenPopup = true } - if (this.config.autoEnableTokens && Object.keys(this.tokenStore.getSelectedTokens()).length) - this.eventSender('tokens-selected', { - selectedTokens: this.tokenStore.getSelectedTokens(), - }) - + if (this.config.autoEnableTokens && Object.keys(this.tokenStore.getSelectedTokens()).length) this.tokensSelectedCallBackHandler() if (openPopup || (this.config.uiOptions.autoPopup === true && autoOpenPopup)) this.ui.openOverlay() } + private tokensSelectedCallBackHandler = () => { + this.eventSender('tokens-selected', { + selectedTokens: this.tokenStore.getSelectedTokens(), + selectedIssuerKeys: Object.keys(this.tokenStore.getSelectedTokens()), + }) + } + private cancelAutoload = true async tokenAutoLoad(onLoading: (issuer: string) => void, onComplete: (issuer: string, tokens: any[]) => void, refresh: boolean) { @@ -515,10 +532,16 @@ export class Client { } this.eventSender('tokens-loaded', { loadedCollections: Object.keys(this.tokenStore.getCurrentIssuers()).length }) + + // use retry logic here too + // document.querySelectorAll('.connect-btn-tn .lds-ellipsis').forEach((el) => { + // el.parentElement.innerHTML = this.config.uiOptions?.loadAction ?? 'Load Collection' + // }) } - cancelTokenAutoload() { + public cancelTokenAutoload() { this.cancelAutoload = true + localStorage.setItem('tn-autoload-tokens', 'false') } async setPassiveNegotiationWebTokens(): Promise { @@ -556,7 +579,7 @@ export class Client { } } } catch (error) { - errorHandler('popup error', 'error', () => this.eventSender('error', { issuer, error }), null, true, false) + errorHandler(error, 'error', () => this.eventSender('error', { issuer, error }), null, true, false) continue } @@ -655,12 +678,8 @@ export class Client { logger(2, 'Emit tokens') logger(2, tokens) - for (let issuer in tokens) { - tokens[issuer] = { tokens: tokens[issuer] } - } - - this.eventSender('tokens-selected', { selectedTokens: tokens }) - this.eventSender('tokens-loaded', { loadedCollections: Object.keys(tokens).length }) + this.tokensSelectedCallBackHandler() + this.eventSender('tokens-loaded', { loadedCollections: Object.keys(this.tokenStore.getCurrentTokens()).length }) // Feature not supported when an end users third party cookies are disabled // because the use of a tab requires a user gesture. @@ -683,9 +702,7 @@ export class Client { } if (this.config.autoEnableTokens) { - this.eventSender('tokens-selected', { - selectedTokens: this.tokenStore.getSelectedTokens(), - }) + this.tokensSelectedCallBackHandler() } return tokens @@ -710,6 +727,7 @@ export class Client { tokens.map((token) => { token.walletAddress = walletAddress + token.collectionId = issuer.collectionID return token }) @@ -750,9 +768,24 @@ export class Client { private async loadRemoteOutletTokens(issuer: OffChainTokenConfig): Promise { const redirectRequired = shouldUseRedirectMode(this.config.offChainRedirectMode) - if (redirectRequired) this.tokenStore.setTokens(issuer.collectionID, []) - + if (this.ui && !issuer.onChain) { + await waitForElementToExist('.load-container-tn') + this.ui.showLoader( + `

${this.config.uiOptions?.reDirectIssuerEventHeading ?? 'Connecting to Issuers...'}

`, + `${this.config.uiOptions?.reDirectIssuerBodyEvent ?? 'Your browser will re-direct shortly'}`, + ``, + ) + this.enableTokenAutoLoadCancel() + this.eventSender('page-redirecting', { collectionId: issuer.collectionID, tokenOrigin: issuer.tokenOrigin }) + await sleep(this.config.uiOptions.userCancelIssuerAutoRedirectTimer ?? 2500) + if (this.userCancelTokenAutoload) { + this.userCancelTokenAutoload = false + return {} + } + } const res = await this.messaging.sendMessage( { action: OutletAction.GET_ISSUER_TOKENS, @@ -765,9 +798,7 @@ export class Client { this.config.type === 'active' ? this.ui : null, redirectRequired ? window.location.href : false, ) - if (!res) return // Site is redirecting - return res.data?.tokens ?? {} } @@ -782,17 +813,20 @@ export class Client { updateSelectedTokens(selectedTokens) { this.tokenStore.setSelectedTokens(selectedTokens) - this.eventSender('tokens-selected', { selectedTokens }) + this.tokensSelectedCallBackHandler() } async prepareToAuthenticateToken(authRequest: AuthenticateInterface) { await this.checkUserAgentSupport('authentication') - const { unsignedToken, tokenId } = authRequest - if (!authRequest.issuer) authRequest.issuer = unsignedToken?.collectionId - requiredParams(authRequest.issuer && (unsignedToken || tokenId), 'Issuer and unsigned token required.') - const config = this.tokenStore.getCurrentIssuers()[authRequest.issuer] + let unsignedToken = authRequest?.unsignedToken + if (!unsignedToken && authRequest.collectionId) unsignedToken = authRequest + const issuer = authRequest?.issuer ?? unsignedToken?.collectionId ?? authRequest?.collectionId + const tokenId = authRequest?.tokenId + console.log('tokenIssuer', issuer, 'unsignedToken', unsignedToken) + requiredParams(issuer && (unsignedToken || tokenId), 'Issuer and unsigned token required MUTLI.') + const config = this.tokenStore.getCurrentIssuers()[issuer] if (!config) errorHandler('Provided issuer was not found.', 'error', null, null, true, true) - return authRequest + return { unsignedToken, issuer, tokenId } } async getMultiRequestBatch(authRequests: AuthenticateInterface[]) { @@ -800,41 +834,29 @@ export class Client { onChain: {}, offChain: {}, } - // build a list of the batches for each token origin. At this point when this loop is complete - // we will have a list of all the tokens that need to be authenticated and the origin they need to be authenticated against. await Promise.all( authRequests.map(async (authRequestItem) => { const reqItem = await this.prepareToAuthenticateToken(authRequestItem) - - if (!reqItem.tokenId && reqItem.unsignedToken?.tokenId) reqItem.tokenId = reqItem.unsignedToken?.tokenId - const issuerConfig = this.tokenStore.getCurrentIssuers()[reqItem.issuer] as OffChainTokenConfig - // Off Chain - // Setup for Token Collection. e.g. authRequestBatch.offChain['https://mywebsite.com']['devcon'] - /** - * Always generate a batch - */ if (issuerConfig.onChain === false) { if (!authRequestBatch.offChain[issuerConfig.tokenOrigin]) authRequestBatch.offChain[issuerConfig.tokenOrigin] = {} - if (!authRequestBatch.offChain[issuerConfig.tokenOrigin][reqItem.issuer]) { authRequestBatch.offChain[issuerConfig.tokenOrigin][reqItem.issuer] = { tokenIds: [], issuerConfig: issuerConfig, } } - // Push token into the request batch - authRequestBatch.offChain[issuerConfig.tokenOrigin][reqItem.issuer].tokenIds.push(reqItem.tokenId) + authRequestBatch.offChain[issuerConfig.tokenOrigin][reqItem.issuer].tokenIds.push( + reqItem.tokenId ?? reqItem.unsignedToken.tokenId, + ) return } - throw new Error('On-chain token are not supported by batch authentication at this time.') }), ) - - if (Object.keys(authRequestBatch.offChain).length > 1) + if (Object.keys(authRequestBatch.offChain).length > 1) { throw new Error('Only a single token origin is supported by batch authentication at this time.') - + } return authRequestBatch } @@ -845,19 +867,21 @@ export class Client { if (this.ui) { this.ui.showLoaderDelayed( [ - '

Authenticating...

', - 'You may need to sign a new challenge in your wallet', - "", + `

${this.config.uiOptions?.authenticationHeadingEvent ?? 'Authenticating...'}

`, + `${this.config.uiOptions?.authenticationBodyEvent ?? 'You may need to sign a new challenge in your wallet'}`, + ``, ], 600, true, ) + this.enableAuthCancel(authRequests) } const authRequestBatch = await this.getMultiRequestBatch(authRequests) let issuerProofs = {} - // Send the request batches to each token origin: // Off Chain: // ['https://devcon.com']['issuer'][list of tokenIds] for (const tokenOrigin in authRequestBatch.offChain) { let AuthType = TicketZKProofMulti @@ -875,7 +899,7 @@ export class Client { if (err.message === 'WALLET_REQUIRED') { return this.handleWalletRequired(authRequest) } - // errorHandler(err, 'error', () => this.handleProofError(err, `multi issuer authentication via ${tokenOrigin}`), null, false, true) + // errorHandler(err, 'error', () => this.handleProofError(err, `multi issuer authentication via ${ tokenOrigin }`), null, false, true) console.error(err) throw err } @@ -900,13 +924,15 @@ export class Client { else return this.authenticateToken(authRequest as AuthenticateInterface) } - async authenticateToken(authRequest: AuthenticateInterface) { + async authenticateToken(authRequest: AuthenticateInterface | any) { await this.checkUserAgentSupport('authentication') - const { unsignedToken, issuer } = authRequest - const tokenIssuer = issuer ?? unsignedToken.collectionId + const tokenIssuer = authRequest.issuer ?? authRequest.unsignedToken?.collectionId ?? authRequest.collectionId + let unsignedToken = authRequest.unsignedToken + if (!unsignedToken && authRequest.collectionId) unsignedToken = authRequest - requiredParams(tokenIssuer && unsignedToken, 'Issuer and unsigned token required.') + console.log('tokenIssuer', tokenIssuer, 'unsignedToken', unsignedToken) + requiredParams(tokenIssuer && unsignedToken, 'Issuer and unsigned token required. SINGLE') const config = this.tokenStore.getCurrentIssuers()[tokenIssuer] @@ -917,9 +943,11 @@ export class Client { if (this.ui) { this.ui.showLoaderDelayed( [ - '

Authenticating...

', - 'You may need to sign a new challenge in your wallet', - "", + `

${this.config.uiOptions?.authenticationHeadingEvent ?? 'Authenticating...'}

`, + `${this.config.uiOptions?.authenticationBodyEvent ?? 'You may need to sign a new challenge in your wallet'}`, + ``, ], 600, true, @@ -980,6 +1008,7 @@ export class Client { cancelAuthButton.onclick = () => { const err = 'User cancelled authentication' this.ui.showError(err) + this.eventSender('user-cancel', { eventType: 'authentication' }) this.eventSender('token-proof', { issuer, error: err, data: null }) } }) @@ -988,6 +1017,26 @@ export class Client { }) } + public enableTokenAutoLoadCancel(): void { + waitForElementToExist('.cancel-autoload-btn') + .then((cancelAuthButton: HTMLElement) => { + cancelAuthButton.onclick = () => { + this.userCancelTokenAutoload = true + this.cancelTokenAutoload() + this.ui.dismissLoader() + // TODO implement communication (pub/sub events) + // to de-couple this logic for default and custom views to utilise. + document.querySelectorAll('.connect-btn-tn .lds-ellipsis').forEach((el) => { + el.parentElement.innerHTML = this.config.uiOptions?.loadAction ?? 'Load Collection' + }) + this.eventSender('user-cancel', { eventType: 'page-redirect' }) + } + }) + .catch((err) => { + logger(2, err) + }) + } + private async handleWalletRequired(authRequest) { if (this.ui) { this.ui.dismissLoader() @@ -1083,6 +1132,7 @@ export class Client { if (issuerConfig) this.storeOutletTokenResponse(res.data.tokens) this.eventSender('tokens-selected', { selectedTokens: this.tokenStore.getSelectedTokens(), + selectedIssuerKeys: Object.keys(this.tokenStore.getSelectedTokens()), }) return res.data.tokens } @@ -1114,12 +1164,11 @@ export class Client { } } + // callback defined when hook added. if (callback) { - this.clientCallBackEvents[type] = callback + this.eventHookHandler.subscribe(type, callback) } else { - if (this.clientCallBackEvents[type]) { - return this.clientCallBackEvents[type].call(type, data) - } + this.eventHookHandler.trigger(type, data) } } diff --git a/src/client/interface.ts b/src/client/interface.ts index 5bb34a80..db6dd224 100644 --- a/src/client/interface.ts +++ b/src/client/interface.ts @@ -34,6 +34,7 @@ export interface OnChainTokenConfig extends IssuerConfigInterface { openSeaSlug?: string blockchain?: SupportedBlockchainsParam oAuth2options?: any + abi?: string } export interface UltraIssuerConfig extends OnChainTokenConfig { @@ -50,12 +51,7 @@ export interface SolanaIssuerConfig extends OnChainTokenConfig { updateAuthority?: string } -export interface Oauth2IssuerConfig { - collectionID: string - onChain: boolean - contract: string - chain: string - blockchain?: SupportedBlockchainsParam +export interface Oauth2IssuerConfig extends OnChainTokenConfig { oAuth2options: { consumerKey: string partnerTag: string @@ -143,6 +139,7 @@ export interface MultiTokenInterface { } export interface AuthenticateInterface { + collectionId?: string issuer: string tokenId?: number | string unsignedToken: any @@ -159,15 +156,21 @@ export interface TokenNegotiatorEventsArgs { 'opened-overlay': EventSenderOpenedOverlay 'closed-overlay': EventSenderClosedOverlay loaded: EventSenderViewLoaded - 'token-proof': any // EventSenderTokenProof // TODO: Update this + 'token-proof': any 'connected-wallet': EventSenderConnectedWallet 'disconnected-wallet': EventSenderDisconnectedWallet 'tokens-selected': EventSenderTokensSelected 'tokens-loaded': EventSenderTokensLoaded 'network-change': string + 'page-redirecting': PageRedirecting + 'user-cancel': { eventType: string } error: EventSenderError } +export interface PageRedirecting { + collectionId: string + tokenOrigin: string +} export interface EventSenderViewLoaded { data: any } diff --git a/src/client/tokenStore.ts b/src/client/tokenStore.ts index 42796a24..0adbcef5 100644 --- a/src/client/tokenStore.ts +++ b/src/client/tokenStore.ts @@ -1,4 +1,5 @@ -import { OffChainTokenConfig, OnChainIssuer, OnChainTokenConfig, SolanaIssuerConfig, UltraIssuerConfig } from './interface' +import { OffChainTokenConfig, OnChainTokenConfig, SolanaIssuerConfig, UltraIssuerConfig } from './interface' +import { LOCAL_STORAGE_TOKEN_STORE_KEY } from './../constants' import { logger } from '../utils' import { DecodedToken } from '../outlet/ticketStorage' @@ -8,12 +9,13 @@ interface IssuerLookup { } interface TokenLookup { - [issuer: string]: { timestamp: number; tokens: TokenData[] | null } + [issuer: string]: { loadAttempts: number; timestamp: number; tokens: TokenData[] | null } } export interface TokenData { tokenId: string | number walletAddress?: string + image?: string // TODO: add more common fields to this interface [key: string]: any } @@ -23,8 +25,6 @@ type TokenConfig = OnChainTokenConfig | OffChainTokenConfig | SolanaIssuerConfig type SelectedTokens = { [collectionId: string]: { tokens: DecodedToken[] | TokenData[] } } export class TokenStore { - public static LOCAL_STORAGE_KEY = 'tn-tokenStore' - private currentIssuers: { [issuer: string]: boolean } = {} // mapping of issuer to on/off chain private tokenData: TokenLookup = {} @@ -42,11 +42,11 @@ export class TokenStore { } public clearTokenStore() { - localStorage.removeItem(TokenStore.LOCAL_STORAGE_KEY) + localStorage.removeItem(LOCAL_STORAGE_TOKEN_STORE_KEY) } private loadTokenStore() { - const tokenStoreData = JSON.parse(localStorage.getItem(TokenStore.LOCAL_STORAGE_KEY)) + const tokenStoreData = JSON.parse(localStorage.getItem(LOCAL_STORAGE_TOKEN_STORE_KEY)) if (!tokenStoreData) return @@ -59,7 +59,7 @@ export class TokenStore { } for (let collectionId in tokenStoreData.tokenData) { - const tokenData = tokenStoreData.tokenData[collectionId] as { timestamp: number; tokens: [] } + const tokenData = tokenStoreData.tokenData[collectionId] as { timestamp: number; tokens: []; loadAttempts: number } if (tokenData.timestamp + this.tokenPersistenceTTL * 1000 > Date.now()) { this.tokenData[collectionId] = tokenData @@ -72,7 +72,7 @@ export class TokenStore { private saveTokenStore() { if (this.tokenPersistenceTTL > 0) localStorage.setItem( - TokenStore.LOCAL_STORAGE_KEY, + LOCAL_STORAGE_TOKEN_STORE_KEY, JSON.stringify({ tokenLookup: this.tokenLookup, tokenData: this.tokenData, @@ -182,11 +182,18 @@ export class TokenStore { } public setTokens(issuer: string, tokens: TokenData[] | DecodedToken[]) { - this.tokenData[issuer] = { timestamp: Date.now(), tokens } + this.tokenData[issuer] = { timestamp: Date.now(), tokens, loadAttempts: this.getCollectionLoadAttempts(issuer) } + this.saveTokenStore() + if (this.autoEnableTokens) this.selectedTokens[issuer] = { tokens: tokens } + } + public setIncrementCollectionLoadAttempts(issuer: string) { + this.tokenData[issuer].loadAttempts = this.tokenData[issuer]?.loadAttempts >= 0 ? (this.tokenData[issuer].loadAttempts += 1) : 0 this.saveTokenStore() + } - if (this.autoEnableTokens) this.selectedTokens[issuer] = { tokens: tokens } + public getCollectionLoadAttempts(issuer: string) { + return this.tokenData[issuer]?.loadAttempts ?? 0 } public getSelectedTokens() { diff --git a/src/client/ui.ts b/src/client/ui.ts index a858e7e8..c4215a04 100644 --- a/src/client/ui.ts +++ b/src/client/ui.ts @@ -2,11 +2,10 @@ import { Start } from './views/start' import { logger, requiredParams } from '../utils' import { Client, ClientError } from './index' -import { ViewInterface, ViewComponent, ViewFactory, ViewConstructor } from './views/view-interface' -import { TokenStore } from './tokenStore' +import { ViewInterface, ViewComponent, ViewFactory, ViewConstructor, AbstractView } from './views/view-interface' import { SelectIssuers } from './views/select-issuers' import { SelectWallet } from './views/select-wallet' - +import { LOCAL_STORAGE_TOKEN_STORE_KEY } from '../constants' export type UIType = 'popup' | 'inline' // TODO: implement modal too export type PopupPosition = 'bottom-right' | 'bottom-left' | 'top-left' | 'top-right' export type UItheme = 'light' | 'dark' @@ -25,10 +24,18 @@ export interface UIOptionsInterface { openingAction?: string issuerHeading?: string repeatAction?: string + cancelAction?: string + walletDidntConnectAction?: string + authenticationHeadingEvent?: string + authenticationBodyEvent?: string + reDirectIssuerEventHeading?: string + reDirectIssuerBodyEvent?: string + theme?: UItheme position?: PopupPosition autoPopup?: boolean alwaysShowStartScreen?: boolean + userCancelIssuerAutoRedirectTimer?: number viewOverrides?: { [type: string]: { component?: ViewComponent @@ -148,13 +155,9 @@ export class Ui implements UiInterface { } public async getStartScreen() { - if ( - this.options.alwaysShowStartScreen || - !localStorage.getItem(TokenStore.LOCAL_STORAGE_KEY) || - !this.client.getTokenStore().getTotalTokenCount() - ) + if (this.options.alwaysShowStartScreen || !localStorage.getItem(LOCAL_STORAGE_TOKEN_STORE_KEY)) { return 'start' - + } if (await this.canSkipWalletSelection()) { this.client.enrichTokenLookupDataOnChainTokens() return 'main' @@ -397,9 +400,7 @@ export class Ui implements UiInterface { loader.style.display = 'block' const dismissBtn = this.loadContainer.querySelector('.dismiss-error-tn') as HTMLDivElement dismissBtn.style.display = 'none' - this.loadContainer.querySelector('.loader-msg-tn').innerHTML = message.join('\n') - this.loadContainer.style.display = 'flex' } diff --git a/src/client/views/select-issuers.ts b/src/client/views/select-issuers.ts index 68e4cf29..f7772e49 100644 --- a/src/client/views/select-issuers.ts +++ b/src/client/views/select-issuers.ts @@ -4,7 +4,6 @@ import { IconView } from './icon-view' import { logger } from '../../utils' import { UIUpdateEventType } from '../index' import { Issuer } from '../interface' -import { applyHTMLElementsInnerText } from './utils/index' export class SelectIssuers extends AbstractView { issuerListContainer: any @@ -18,7 +17,6 @@ export class SelectIssuers extends AbstractView { this.client.registerUiUpdateCallback(UIUpdateEventType.ISSUERS_LOADED, () => { this.ui.dismissLoader() - this.client.cancelTokenAutoload() this.render() }) @@ -111,7 +109,9 @@ export class SelectIssuers extends AbstractView { protected afterRender() { if (this.client.issuersLoaded) { - if (this.client.getTokenStore().hasUnloadedTokens()) this.autoLoadTokens() + if (this.client.getTokenStore().hasUnloadedTokens()) { + this.autoLoadTokens() + } } else { this.issuersLoading() } @@ -158,9 +158,10 @@ export class SelectIssuers extends AbstractView { new IconView(elem, params).render() } - this.issuerListContainer.addEventListener('click', (e: any) => { + this.issuerListContainer.addEventListener('click', async (e: any) => { if (e.target.classList.contains('connect-btn-tn')) { - this.connectTokenIssuer(e) + await this.connectTokenIssuer(e) + this.client.getTokenStore().setIncrementCollectionLoadAttempts(e.target.dataset.issuer) } else if (e.target.classList.contains('tokens-btn-tn')) { const issuer = e.target.parentNode.dataset.issuer this.navigateToTokensView(issuer) @@ -170,10 +171,14 @@ export class SelectIssuers extends AbstractView { issuerConnectMarkup(title: string, image: string | undefined, issuer: string, tokens: any[], data: Issuer) { let buttonText = '' - + const collectLoadAttempts = this.client.getTokenStore().getCollectionLoadAttempts(issuer) + let issuerButtonText = this.params.options?.loadAction ?? 'Load Collection' + if (collectLoadAttempts >= 1) issuerButtonText = this.params.options?.repeatAction ?? 'Retry' // @ts-ignore - if (tokens?.length) buttonText = data?.fungible ? this.params.options.balanceFoundEvent ?? 'Balance found' : `${tokens.length} ${this.params.options?.nftsFoundEvent ?? 'Token(s) Available'}` - + if (tokens?.length) + buttonText = data?.fungible + ? this.params.options?.balanceFoundEvent ?? 'Balance found' + : `${tokens.length} ${this.params.options?.nftsFoundEvent ?? 'Token(s) Available'}` return `