From 737debf52cb03e5d4e58e9904bc812ec5838d0be Mon Sep 17 00:00:00 2001 From: Nick Taras Date: Thu, 5 Oct 2023 16:22:09 +1100 Subject: [PATCH] added multi-hook functionality to token negotiator --- index.html | 28 +++++++-------- src/client/__tests__/client.spec.ts | 17 +++++----- src/client/__tests__/eventHookHandler.spec.ts | 27 +++++++++++++++ src/client/eventHookHandler.ts | 34 +++++++++++++++++++ src/client/index.ts | 12 ++++--- src/outlet/ticketStorage.ts | 2 ++ 6 files changed, 93 insertions(+), 27 deletions(-) create mode 100644 src/client/__tests__/eventHookHandler.spec.ts create mode 100644 src/client/eventHookHandler.ts diff --git a/index.html b/index.html index 2ac6aa93..79555196 100644 --- a/index.html +++ b/index.html @@ -122,13 +122,13 @@ // 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: "NBA", + // onChain: true, + // contract: "A.0b2a3299cc857e29.TopShot", + // chain: "mainnet", + // blockchain: "flow" + // } // { // collectionID: "devcon", // onChain: false, @@ -243,13 +243,13 @@ // chain: "bsc", // blockchain: "evm", // }, - // { - // onChain: true, - // collectionID: "tt", - // contract: '0x76be3b62873462d2142405439777e971754e8e77', - // chain: 'eth', - // 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' }, diff --git a/src/client/__tests__/client.spec.ts b/src/client/__tests__/client.spec.ts index b64efd86..2d390ca6 100644 --- a/src/client/__tests__/client.spec.ts +++ b/src/client/__tests__/client.spec.ts @@ -313,21 +313,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,6 +494,7 @@ describe('client spec', () => { const error = tokenNegotiatorClient.handleRecievedRedirectMessages() expect(error).toEqual(null) }) + }) // TODO: Reimplement cross-version test for version 3.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/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 8a484875..ad53ad82 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -36,6 +36,7 @@ import { URLNS } from '../core/messaging' import { DecodedToken, TokenType } from '../outlet/ticketStorage' import { MultiTokenAuthRequest, MultiTokenAuthResult, OutletIssuerInterface, ProofResult } from '../outlet/interfaces' import { AttestationIdClient } from '../outlet/attestationIdClient' +import { EventHookHandler } from './eventHookHandler' if (typeof window !== 'undefined') window.tn = { VERSION } @@ -105,8 +106,8 @@ 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 private uiUpdateCallbacks: { [type in UIUpdateEventType] } = { [UIUpdateEventType.ISSUERS_LOADING]: undefined, @@ -122,6 +123,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') @@ -1151,12 +1154,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/outlet/ticketStorage.ts b/src/outlet/ticketStorage.ts index 3f458d0e..db428627 100644 --- a/src/outlet/ticketStorage.ts +++ b/src/outlet/ticketStorage.ts @@ -50,6 +50,7 @@ export type DecodedToken = DecodedTokenData & { type: TokenType tokenId: string signedToken: string + image?: string } export interface DecodedTokenData { @@ -60,6 +61,7 @@ export interface DecodedTokenData { commitment?: Uint8Array easAttestation?: SignedOffchainAttestation easData?: EasFieldData + image?: string } export interface EasFieldData {