diff --git a/__tests__/core.ts b/__tests__/core.ts index bc48544..d5d7853 100644 --- a/__tests__/core.ts +++ b/__tests__/core.ts @@ -5,6 +5,8 @@ import { // protocols createPeerPublisher, createPeerSubscriber, + readPeerContact, + writePeerContact, // channels getPublicAddress, getFeedReadParams, @@ -194,8 +196,12 @@ describe('protocols', () => { }, } - const subscription = createPeerSubscriber(bzz, pubKey, { - interval: 1000, + const subscription = createPeerSubscriber({ + bzz, + peer: pubKey, + options: { + interval: 1000, + }, }).subscribe({ next: loadedPeer => { expect(loadedPeer).toEqual(peer) @@ -207,4 +213,37 @@ describe('protocols', () => { const publish = createPeerPublisher(bzz, keyPair) await publish(peer) }) + + test('peerContact', async () => { + const bzz = new Bzz({ + url: 'http://localhost:8500', + signBytes: async (bytes, key) => sign(bytes, key), + }) + + const aliceKeyPair = createKeyPair() + const bobKeyPair = createKeyPair() + + const sendPeerContact = { + contactPublicKey: createKeyPair().getPublic('hex'), + peerAddress: getPublicAddress(aliceKeyPair), + } + + // Write Alice -> Bob peerContact using Alice's private key and Bob's public key + await writePeerContact( + { + bzz, + keyPair: aliceKeyPair, + peerKey: bobKeyPair.getPublic('hex'), + }, + sendPeerContact, + ) + + // Read Alice -> Bob peerContact using Alice's public key and Bob's private key + const receivedPeerContact = await readPeerContact({ + bzz, + keyPair: bobKeyPair, + peerKey: aliceKeyPair.getPublic('hex'), + }) + expect(receivedPeerContact).toEqual(sendPeerContact) + }) }) diff --git a/packages/core/src/channels.ts b/packages/core/src/channels.ts index 49ae577..cc7414c 100644 --- a/packages/core/src/channels.ts +++ b/packages/core/src/channels.ts @@ -43,20 +43,20 @@ export interface FeedReadParams { } export function getFeedReadParams( - publisher: string, // Can be a public key (130 chars long) or an address + writer: string, // Can be a public key (130 chars long) or an address name?: string, keyPair?: any, // TODO: KeyPair type ): FeedReadParams { - const pubKey = publisher.length === 130 ? createPublic(publisher) : null + const pubKey = writer.length === 130 ? createPublic(writer) : null const feed: FeedParams = { - user: pubKey === null ? publisher : getPublicAddress(pubKey), + user: pubKey === null ? writer : getPublicAddress(pubKey), } let encryptionKey: Buffer | undefined if (keyPair != null) { if (pubKey === null) { throw new Error( - 'publisher argument must be a public key when keyPair is provided to derive the shared key', + 'writer argument must be a public key when keyPair is provided to derive the shared key', ) } encryptionKey = keyPair.derive(pubKey.getPublic()).toBuffer() @@ -81,14 +81,14 @@ export interface FeedWriteParams extends FeedReadParams { export function getFeedWriteParams( keyPair: any, // TODO: KeyPair type name?: string, - subscriber?: string, + reader?: string, ): FeedWriteParams { const user = getPublicAddress(keyPair) const feed: FeedParams = { user } let encryptionKey: Buffer | undefined - if (subscriber != null) { - const pubKey = createPublic(subscriber) + if (reader != null) { + const pubKey = createPublic(reader) encryptionKey = keyPair.derive(pubKey.getPublic()).toBuffer() feed.topic = getFeedTopic({ name, @@ -111,17 +111,33 @@ export interface ChannelParams { name?: string } -export interface PublisherParams extends ChannelParams { +export interface WriterParams extends ChannelParams { keyPair: KeyPair options?: UploadOptions - subscriber?: string + reader?: string } -export function createFeedPublisher(params: PublisherParams) { +export async function writeFeedEntity( + params: WriterParams, + data: T, +): Promise { const { feed, encryptionKey, signParams } = getFeedWriteParams( params.keyPair, params.name, - params.subscriber, + params.reader, + ) + const payload = await encodePayload( + { type: params.entityType, data }, + { key: encryptionKey }, + ) + return await params.bzz.setFeedContent(feed, payload, undefined, signParams) +} + +export function createFeedPublisher(params: WriterParams) { + const { feed, encryptionKey, signParams } = getFeedWriteParams( + params.keyPair, + params.name, + params.reader, ) const push = async (content: T) => { const payload = await encodePayload(content, { key: encryptionKey }) @@ -130,15 +146,15 @@ export function createFeedPublisher(params: PublisherParams) { return createEntityPublisher(params.entityType, push) } -export function createTimelinePublisher(params: PublisherParams) { +export function createTimelinePublisher(params: WriterParams) { const { feed, encryptionKey, signParams } = getFeedWriteParams( params.keyPair, params.name, - params.subscriber, + params.reader, ) let encode - if (params.subscriber != null) { + if (params.reader != null) { encode = async (chapter: PartialChapter) => { return await encodePayload(chapter, { key: encryptionKey }) } @@ -156,17 +172,35 @@ export function createTimelinePublisher(params: PublisherParams) { } export interface ReaderParams extends ChannelParams { - publisher: string + writer: string keyPair?: KeyPair } +export async function readFeedEntity( + params: ReaderParams, +): Promise { + const { feed, encryptionKey } = getFeedReadParams( + params.writer, + params.name, + params.keyPair, + ) + + const res = await params.bzz.getFeedContent(feed, { mode: 'raw' }) + if (res === null) { + return null + } + + const payload = await decodeEntityStream(res.body, { key: encryptionKey }) + return payload.data +} + export interface SubscriberParams extends ReaderParams { options: PollContentOptions } export function createFeedSubscriber(params: SubscriberParams) { const { feed, encryptionKey } = getFeedReadParams( - params.publisher, + params.writer, params.name, params.keyPair, ) @@ -192,7 +226,7 @@ export function createTimelineDecoder(params?: DecodeParams) { export function createReadTimeline(params: ReaderParams) { const { feed, encryptionKey } = getFeedReadParams( - params.publisher, + params.writer, params.name, params.keyPair, ) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index eabf23d..ed4810d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,10 @@ -export { createPeerPublisher, createPeerSubscriber } from './protocols/peer' +export { + createPeerPublisher, + createPeerSubscriber, + createPeerContactSubscriber, + readPeerContact, + writePeerContact, +} from './protocols' export { Peer, peerSchema } from './schemas/peer' export { createEntityPublisher, diff --git a/packages/core/src/protocols/index.ts b/packages/core/src/protocols/index.ts new file mode 100644 index 0000000..cd7e796 --- /dev/null +++ b/packages/core/src/protocols/index.ts @@ -0,0 +1,6 @@ +export { createPeerPublisher, createPeerSubscriber } from './peer' +export { + createPeerContactSubscriber, + readPeerContact, + writePeerContact, +} from './peerContact' diff --git a/packages/core/src/protocols/peer.ts b/packages/core/src/protocols/peer.ts index e31ad69..a0a1da9 100644 --- a/packages/core/src/protocols/peer.ts +++ b/packages/core/src/protocols/peer.ts @@ -6,19 +6,23 @@ import { Peer } from '../schemas/peer' import { createFeedPublisher, createFeedSubscriber } from '../channels' import { PEER_NAME } from '../namespace' +export interface PeerSubscriberParams { + bzz: Bzz + peer: string + options: PollContentOptions +} + export function createPeerSubscriber( - bzz: Bzz, - peerKeyOrAddress: string, - options: PollContentOptions, + params: PeerSubscriberParams, ): Observable { return createFeedSubscriber({ - bzz, + bzz: params.bzz, entityType: PEER_NAME, name: PEER_NAME, - publisher: peerKeyOrAddress, + writer: params.peer, options: { whenEmpty: 'ignore', - ...options, + ...params.options, mode: 'raw', }, }).pipe(map((payload: any) => payload.data)) diff --git a/packages/core/src/protocols/peerContact.ts b/packages/core/src/protocols/peerContact.ts new file mode 100644 index 0000000..4e553db --- /dev/null +++ b/packages/core/src/protocols/peerContact.ts @@ -0,0 +1,66 @@ +import Bzz, { PollContentOptions } from '@erebos/api-bzz-base' +import { Observable } from 'rxjs' +import { map } from 'rxjs/operators' + +import { PeerContact } from '../schemas/peerContact' +import { + createFeedSubscriber, + readFeedEntity, + writeFeedEntity, +} from '../channels' +import { PEER_CONTACT_NAME } from '../namespace' + +export interface PeerContactParams { + bzz: Bzz + keyPair: any // TODO: keyPair type + peerKey: string +} + +export async function readPeerContact( + params: PeerContactParams, +): Promise { + return await readFeedEntity({ + bzz: params.bzz, + entityType: PEER_CONTACT_NAME, + keyPair: params.keyPair, + name: PEER_CONTACT_NAME, + writer: params.peerKey, + }) +} + +export async function writePeerContact( + params: PeerContactParams, + data: PeerContact, +): Promise { + return await writeFeedEntity( + { + bzz: params.bzz, + entityType: PEER_CONTACT_NAME, + keyPair: params.keyPair, + name: PEER_CONTACT_NAME, + reader: params.peerKey, + }, + data, + ) +} + +export interface PeerContactSubscriberParams extends PeerContactParams { + options: PollContentOptions +} + +export function createPeerContactSubscriber( + params: PeerContactSubscriberParams, +): Observable { + return createFeedSubscriber({ + bzz: params.bzz, + entityType: PEER_CONTACT_NAME, + keyPair: params.keyPair, + name: PEER_CONTACT_NAME, + options: { + whenEmpty: 'ignore', + ...params.options, + mode: 'raw', + }, + writer: params.peerKey, + }).pipe(map((payload: any) => payload.data)) +}