diff --git a/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.js b/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.js index b44843d3..d62064a4 100644 --- a/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.js +++ b/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.js @@ -9,7 +9,7 @@ const addressBookAppIds = [ /** * An identity provider for Address Book Entries * - * @class AddressIdentityProvider + * @extends AddressIdentityProvider */ export default class AddressBookIdentityProvider extends AddressIdentityProvider { constructor (apps, cache) { @@ -51,6 +51,42 @@ export default class AddressBookIdentityProvider extends AddressIdentityProvider }, null) } + async search (searchTerm = '') { + const isAddressSearch = searchTerm.substring(0, 2).toLowerCase() === '0x' + const identities = await this.getAll() + const results = Object.entries(identities) + .filter( + ([address, { name }]) => + (isAddressSearch && + searchTerm.length > 3 && + address.toLowerCase().indexOf(searchTerm.toLowerCase()) === 0) || + name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1 + ) + .map(([address, { name }]) => ({ name, address })) + return results + } + + /** + * get all identities from all installed address book instances + */ + async getAll () { + const addressBookApps = await this.apps.pipe( + first(), + map(apps => apps.filter(app => addressBookAppIds.includes(app.appId))) + ).toPromise() + + return addressBookApps.reduce(async (allEntries, app) => { + const cacheKey = getCacheKey(app.proxyAddress, 'state') + const { entries = [] } = await this.cache.get(cacheKey) + const allEntriesResolved = await allEntries + const entriesObject = entries.reduce((obj, entry) => { + return { ...obj, [entry.addr.toLowerCase()]: entry.data } + }, {}) + // ensure the entries retrieved from the first-installed address book aren't overwritten + return { ...entriesObject, ...allEntriesResolved } + }, Promise.resolve({})) + } + /** * Modify the identity metadata of an address * diff --git a/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.test.js b/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.test.js index 0046e09f..46fad7e2 100644 --- a/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.test.js +++ b/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.test.js @@ -22,8 +22,8 @@ test.before(async t => { cache = new Cache('stubbedAddressBook') await cache.init() - cache.set('0x0.state', { entries: [{ addr: '0x3', data: { name: 'testEntity' } }] }) - cache.set('0x11.state', { entries: [{ addr: '0x3', data: { name: 'testEntity2' } }] }) + cache.set('0x0.state', { entries: [{ addr: '0x3a', data: { name: 'testEntity' } }, { addr: '0x33', data: { name: 'testDude' } } ] }) + cache.set('0x11.state', { entries: [{ addr: '0x3a', data: { name: 'testEntity2' } }] }) }) test.beforeEach(async t => { @@ -33,7 +33,7 @@ test.beforeEach(async t => { test('should resolve identity from first address book in app array', async t => { const provider = t.context.addressBookIdentityProvider - const identityMetadata = await provider.resolve('0x3') + const identityMetadata = await provider.resolve('0x3a') t.is(identityMetadata.name, 'testEntity') }) @@ -47,3 +47,26 @@ test('should throw error on any modify attempt', async t => { const provider = t.context.addressBookIdentityProvider await t.throwsAsync(() => provider.modify('0x9', { name: 'newEntity' })) }) + +test('getAll should return a combined Object containing all entries', async t => { + const provider = t.context.addressBookIdentityProvider + const allIdentities = await provider.getAll() + t.deepEqual(allIdentities, { + '0x3a': { name: 'testEntity' }, + '0x33': { name: 'testDude' } + }) +}) + +test('search should return an aray of results of freely matching identities', async t => { + t.plan(3) + const provider = t.context.addressBookIdentityProvider + let result = await provider.search('0x3a') + t.deepEqual(result, [ { name: 'testEntity', address: '0x3a' } ]) + + result = await provider.search('test') + t.deepEqual(result, [ { name: 'testEntity', address: '0x3a' }, + { name: 'testDude', address: '0x33' } ]) + + result = await provider.search('testd') + t.deepEqual(result, [{ name: 'testDude', address: '0x33' } ]) +}) diff --git a/packages/aragon-wrapper/src/index.js b/packages/aragon-wrapper/src/index.js index e3ba6207..0bab22b3 100644 --- a/packages/aragon-wrapper/src/index.js +++ b/packages/aragon-wrapper/src/index.js @@ -946,13 +946,23 @@ export default class Aragon { * @param {string} searchTerm * @return {Promise} Resolves with the identity or null if not found */ - searchIdentities (searchTerm) { - const providerName = 'local' // TODO - get provider - const provider = this.identityProviderRegistrar.get(providerName) - if (provider && typeof provider.search === 'function') { - return provider.search(searchTerm) - } - return Promise.reject(new Error(`Provider (${providerName}) not installed`)) + async searchIdentities (searchTerm) { + const providerNames = [ 'local', 'addressBook' ] // TODO - get provider + const resolvedResults = await Promise.all( + providerNames.map( (providerName) => { + const provider = this.identityProviderRegistrar.get(providerName) + if (provider && typeof provider.search === 'function') { + return provider.search(searchTerm) + } + return Promise.reject(new Error(`Provider (${providerName}) not installed`)) + }) + ) + return resolvedResults.reduce( + (combinedResults, providerResult) => { + return [ ...combinedResults, ...providerResult ] + }, + [] + ) } /** diff --git a/packages/aragon-wrapper/src/index.test.js b/packages/aragon-wrapper/src/index.test.js index bbe57b41..4685d6e5 100644 --- a/packages/aragon-wrapper/src/index.test.js +++ b/packages/aragon-wrapper/src/index.test.js @@ -808,6 +808,86 @@ test('should init the identity providers correctly', async (t) => { t.is(instance.identityProviderRegistrar.size, 2, 'Should have only two providers') }) +test('should search identities correctly', async (t) => { + const { createAragon } = t.context + + t.plan(2) + // arrange + const instance = createAragon() + instance.apps = of([ + { + appId: '0x32ec8cc9f3136797e0ae30e7bf3740905b0417b81ff6d4a74f6100f9037425de', + proxyAddress: '0x001' + }, { + appId: 'votingApp', + isForwarder: false + } + ]) + await instance.cache.init() + await instance.cache.set('0x001.state', { + entries: [ + { addr: '0x789', data: { name: 'testEntity' } }, + { addr: '0x456', data: { name: 'testDude' } } + ] + }) + await instance.initIdentityProviders() + + // act + await instance.modifyAddressIdentity('0x123', { name: 'testperson' }) + await instance.modifyAddressIdentity('0x456', { name: 'testDao' }) + // assert + //console.log(await instance.cache.get('0x001.state')) + let result = await instance.searchIdentities('test') + + t.deepEqual(result, [ { name: 'testperson', address: '0x123' }, + { name: 'testDao', address: '0x456' }, + { name: 'testEntity', address: '0x789' }, + { name: 'testDude', address: '0x456' } ]) + + result = await instance.searchIdentities('0x456') + t.deepEqual(result, [ + { name: 'testDao', address: '0x456' }, + { name: 'testDude', address: '0x456' } + ]) +}) + +test('should resolve identity correctly', async (t) => { + const { createAragon } = t.context + + t.plan(2) + // arrange + const instance = createAragon() + instance.apps = of([ + { + appId: '0x32ec8cc9f3136797e0ae30e7bf3740905b0417b81ff6d4a74f6100f9037425de', + proxyAddress: '0x001' + }, { + appId: 'votingApp', + isForwarder: false + } + ]) + await instance.cache.init() + await instance.cache.set('0x001.state', { + entries: [ + { addr: '0x789', data: { name: 'testEntity' } }, + { addr: '0x456', data: { name: 'testDude' } } + ] + }) + await instance.initIdentityProviders() + + // act + await instance.modifyAddressIdentity('0x123', { name: 'testperson' }) + await instance.modifyAddressIdentity('0x456', { name: 'testDao' }) + // assert + //console.log(await instance.cache.get('0x001.state')) + let result = await instance.resolveAddressIdentity('0x456') + console.log('wrapper result: ', result) + t.is(result.name, 'testDao', 'should resolve to local label') + + result = await instance.resolveAddressIdentity('0x789') + t.is(result.name, 'testEntity', 'should resolve to address book entry') +}) + test('should emit an intent when requesting address identity modification', async (t) => { const { createAragon } = t.context const expectedAddress = '0x123'