Skip to content

Commit

Permalink
Add peerContact protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Le Cam committed Jun 18, 2019
1 parent 0870d14 commit 57ad79d
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 26 deletions.
43 changes: 41 additions & 2 deletions __tests__/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
// protocols
createPeerPublisher,
createPeerSubscriber,
readPeerContact,
writePeerContact,
// channels
getPublicAddress,
getFeedReadParams,
Expand Down Expand Up @@ -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)
Expand All @@ -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)
})
})
68 changes: 51 additions & 17 deletions packages/core/src/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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,
Expand All @@ -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<T>(params: PublisherParams) {
export async function writeFeedEntity<T>(
params: WriterParams,
data: T,
): Promise<string> {
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<T>(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 })
Expand All @@ -130,15 +146,15 @@ export function createFeedPublisher<T>(params: PublisherParams) {
return createEntityPublisher(params.entityType, push)
}

export function createTimelinePublisher<T>(params: PublisherParams) {
export function createTimelinePublisher<T>(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 })
}
Expand All @@ -156,17 +172,35 @@ export function createTimelinePublisher<T>(params: PublisherParams) {
}

export interface ReaderParams extends ChannelParams {
publisher: string
writer: string
keyPair?: KeyPair
}

export async function readFeedEntity<T>(
params: ReaderParams,
): Promise<T | null> {
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<T>(res.body, { key: encryptionKey })
return payload.data
}

export interface SubscriberParams extends ReaderParams {
options: PollContentOptions
}

export function createFeedSubscriber<T>(params: SubscriberParams) {
const { feed, encryptionKey } = getFeedReadParams(
params.publisher,
params.writer,
params.name,
params.keyPair,
)
Expand All @@ -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,
)
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/protocols/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { createPeerPublisher, createPeerSubscriber } from './peer'
export {
createPeerContactSubscriber,
readPeerContact,
writePeerContact,
} from './peerContact'
16 changes: 10 additions & 6 deletions packages/core/src/protocols/peer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@ import { Peer } from '../schemas/peer'
import { createFeedPublisher, createFeedSubscriber } from '../channels'
import { PEER_NAME } from '../namespace'

export interface PeerSubscriberParams {
bzz: Bzz<any>
peer: string
options: PollContentOptions
}

export function createPeerSubscriber(
bzz: Bzz<any>,
peerKeyOrAddress: string,
options: PollContentOptions,
params: PeerSubscriberParams,
): Observable<Peer> {
return createFeedSubscriber<Peer>({
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))
Expand Down
66 changes: 66 additions & 0 deletions packages/core/src/protocols/peerContact.ts
Original file line number Diff line number Diff line change
@@ -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<any>
keyPair: any // TODO: keyPair type
peerKey: string
}

export async function readPeerContact(
params: PeerContactParams,
): Promise<PeerContact | null> {
return await readFeedEntity<PeerContact>({
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<string> {
return await writeFeedEntity<PeerContact>(
{
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<PeerContact> {
return createFeedSubscriber<PeerContact>({
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))
}

0 comments on commit 57ad79d

Please sign in to comment.