From b04751c0a9d3e4e1082841e299893c34456c0fcd Mon Sep 17 00:00:00 2001 From: Paul Le Cam Date: Fri, 28 Jun 2019 11:23:05 +0100 Subject: [PATCH] Add messaging protocol --- __tests__/core.ts | 47 +++++++++++++++++++ packages/core/src/channels.ts | 18 ++++--- packages/core/src/index.ts | 5 ++ packages/core/src/namespace.ts | 2 + packages/core/src/protocols/index.ts | 6 +++ packages/core/src/protocols/messaging.ts | 36 ++++++++++++++ packages/core/src/schemas/contact.ts | 6 +-- packages/core/src/schemas/ethereumAddress.ts | 4 -- packages/core/src/schemas/fileSystem.ts | 3 +- packages/core/src/schemas/messaging.ts | 26 +++------- packages/core/src/schemas/peer.ts | 2 +- packages/core/src/schemas/peerContact.ts | 3 +- packages/core/src/schemas/profile.ts | 2 +- packages/core/src/schemas/publicKey.ts | 4 -- .../src/schemas/{swarmFeed.ts => scalars.ts} | 15 +++++- packages/core/src/schemas/swarmHash.ts | 4 -- packages/core/src/validation.ts | 9 +++- 17 files changed, 143 insertions(+), 49 deletions(-) create mode 100644 packages/core/src/protocols/messaging.ts delete mode 100644 packages/core/src/schemas/ethereumAddress.ts delete mode 100644 packages/core/src/schemas/publicKey.ts rename packages/core/src/schemas/{swarmFeed.ts => scalars.ts} (55%) delete mode 100644 packages/core/src/schemas/swarmHash.ts diff --git a/__tests__/core.ts b/__tests__/core.ts index bc1876e..b2dbb43 100644 --- a/__tests__/core.ts +++ b/__tests__/core.ts @@ -6,6 +6,8 @@ import { // protocols readContact, writeContact, + createMailboxPublisher, + createMailboxReader, FileSystemReader, FileSystemWriter, downloadFile, @@ -233,6 +235,51 @@ describe('protocols', () => { expect(receivedContact).toEqual(sendContact) }) + test('messaging', async done => { + jest.setTimeout(10000) + + const aliceMailboxKeyPair = createKeyPair() + const bobKeyPair = createKeyPair() + + const publish = createMailboxPublisher({ + bzz, + keyPair: aliceMailboxKeyPair, + reader: bobKeyPair.getPublic('hex'), + }) + + const firstMessage = { title: 'test', body: 'first' } + const chapter = await publish(firstMessage) + + const reader = createMailboxReader({ + bzz, + keyPair: bobKeyPair, + writer: aliceMailboxKeyPair.getPublic('hex'), + }) + + const firstChapter = await reader.getLatestChapter() + expect(firstChapter).toBeDefined() + expect(firstChapter.content.data).toEqual(firstMessage) + + const secondMessage = { thread: chapter.id, title: 'test', body: 'second' } + + const sub = reader.pollLatestChapter({ interval: 1000 }).subscribe({ + next: chapter => { + const { data } = chapter.content + if (data.thread != null) { + expect(data).toEqual(secondMessage) + sub.unsubscribe() + done() + } + }, + error: err => { + sub.unsubscribe() + throw err + }, + }) + + await publish(secondMessage) + }) + describe('fileSystem', () => { test('uploadFile() and downloadFile()', async () => { const data = 'Hello test' diff --git a/packages/core/src/channels.ts b/packages/core/src/channels.ts index 59306b8..6ceb017 100644 --- a/packages/core/src/channels.ts +++ b/packages/core/src/channels.ts @@ -240,7 +240,7 @@ export function createEntityFeedSubscriber(params: SubscriberParams) { export function createEntityTimelineDecoder(params?: DecodeParams) { return async function decode(res: Readable) { - const stream = await getBodyStream(res, params) + const stream = await getBodyStream(res.body, params) const body = await getStream(stream) const chapter = validateChapter(JSON.parse(body)) await validateEntity(chapter.content) @@ -248,25 +248,29 @@ export function createEntityTimelineDecoder(params?: DecodeParams) { } } -export function createEntityReadTimeline(params: ReaderParams) { +export function createEntityReadTimeline(params: ReaderParams) { const { feed, encryptionKey } = getFeedReadParams( params.writer, params.name, params.keyPair, ) - return new Timeline({ + return new Timeline({ bzz: params.bzz, feed, decode: createEntityTimelineDecoder({ key: encryptionKey }), }) } -export function createEntityTimelineLatestSubscriber(params: SubscriberParams) { - const timeline = createEntityReadTimeline(params) +export function createEntityTimelineLatestSubscriber( + params: SubscriberParams, +) { + const timeline = createEntityReadTimeline(params) return timeline.pollLatestChapter(params.options) } -export function createEntityTimelineLiveSubscriber(params: SubscriberParams) { - const timeline = createEntityReadTimeline(params) +export function createEntityTimelineLiveSubscriber( + params: SubscriberParams, +) { + const timeline = createEntityReadTimeline(params) return timeline.live(params.options) } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7a12917..1c9aa04 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -3,6 +3,11 @@ export { createContactSubscriber, readContact, writeContact, + // Messaging + MailboxReaderParams, + MailboxWriterParams, + createMailboxPublisher, + createMailboxReader, // FileSystem FileSystemReader, FileSystemReaderParams, diff --git a/packages/core/src/namespace.ts b/packages/core/src/namespace.ts index cd32f23..8fee86a 100644 --- a/packages/core/src/namespace.ts +++ b/packages/core/src/namespace.ts @@ -17,5 +17,7 @@ export function namespace( export const CONTACT_NAME = namespace('contact') export const FILE_SYSTEM_NAME = namespace('fileSystem') +export const MAILBOX_NAME = namespace('mailbox') +export const MESSAGE_NAME = namespace('message') export const PEER_NAME = namespace('peer') export const PEER_CONTACT_NAME = namespace('peerContact') diff --git a/packages/core/src/protocols/index.ts b/packages/core/src/protocols/index.ts index bddbe22..0c57617 100644 --- a/packages/core/src/protocols/index.ts +++ b/packages/core/src/protocols/index.ts @@ -1,4 +1,10 @@ export { createContactSubscriber, readContact, writeContact } from './contact' +export { + MailboxReaderParams, + MailboxWriterParams, + createMailboxReader, + createMailboxPublisher, +} from './messaging' export { FileSystemReader, FileSystemReaderParams, diff --git a/packages/core/src/protocols/messaging.ts b/packages/core/src/protocols/messaging.ts new file mode 100644 index 0000000..4f4c2ec --- /dev/null +++ b/packages/core/src/protocols/messaging.ts @@ -0,0 +1,36 @@ +import Bzz from '@erebos/api-bzz-base' + +import { + createEntityReadTimeline, + createEntityTimelinePublisher, +} from '../channels' +import { MAILBOX_NAME, MESSAGE_NAME } from '../namespace' + +interface MailboxCommonParams { + bzz: Bzz + keyPair: any // TODO: keyPair type +} + +export interface MailboxReaderParams extends MailboxCommonParams { + writer: string +} + +export function createMailboxReader(params: MailboxReaderParams) { + return createEntityReadTimeline({ + ...params, + entityType: MESSAGE_NAME, + name: MAILBOX_NAME, + }) +} + +export interface MailboxWriterParams extends MailboxCommonParams { + reader: string +} + +export function createMailboxPublisher(params: MailboxWriterParams) { + return createEntityTimelinePublisher({ + ...params, + entityType: MESSAGE_NAME, + name: MAILBOX_NAME, + }) +} diff --git a/packages/core/src/schemas/contact.ts b/packages/core/src/schemas/contact.ts index fc37cb2..91f46b2 100644 --- a/packages/core/src/schemas/contact.ts +++ b/packages/core/src/schemas/contact.ts @@ -1,11 +1,11 @@ import { CONTACT_NAME, getID } from '../namespace' -import { FileSystem, fileSystemProperty } from './fileSystem' import { Mailboxes, mailboxesProperty } from './messaging' import { Profile, profileProperty } from './profile' +import { publicKeyProperty } from './scalars' export interface Contact { - files?: FileSystem + fileSystemKey?: string mailboxes?: Mailboxes profile?: Profile } @@ -15,7 +15,7 @@ export const contactSchema = { $id: getID(CONTACT_NAME), type: 'object', properties: { - files: fileSystemProperty, + fileSystemKey: publicKeyProperty, mailboxes: mailboxesProperty, profile: profileProperty, }, diff --git a/packages/core/src/schemas/ethereumAddress.ts b/packages/core/src/schemas/ethereumAddress.ts deleted file mode 100644 index 9bea49c..0000000 --- a/packages/core/src/schemas/ethereumAddress.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const ethereumAddressProperty = { - type: 'string', - pattern: '^0x[0-9a-f]{40}$', -} diff --git a/packages/core/src/schemas/fileSystem.ts b/packages/core/src/schemas/fileSystem.ts index 7c1904c..6795031 100644 --- a/packages/core/src/schemas/fileSystem.ts +++ b/packages/core/src/schemas/fileSystem.ts @@ -1,7 +1,7 @@ import { FILE_SYSTEM_NAME, getID } from '../namespace' import { EncryptionParams } from '../types' -import { swarmHashProperty } from './swarmHash' +import { swarmHashProperty } from './scalars' export interface FileEncryption extends EncryptionParams { key: string @@ -29,6 +29,7 @@ export interface File extends FileMetadata { } export const fileProperty = { + type: 'object', required: ['hash'], properties: { hash: swarmHashProperty, diff --git a/packages/core/src/schemas/messaging.ts b/packages/core/src/schemas/messaging.ts index b5f7694..bd5d797 100644 --- a/packages/core/src/schemas/messaging.ts +++ b/packages/core/src/schemas/messaging.ts @@ -1,7 +1,7 @@ +import { MESSAGE_NAME, getID } from '../namespace' + import { File, fileProperty } from './fileSystem' -import { publicKeyProperty } from './publicKey' -import { SwarmFeed, swarmFeedProperty } from './swarmFeed' -import { swarmHashProperty } from './swarmHash' +import { publicKeyProperty, swarmHashProperty } from './scalars' export interface MessageAttachment { file: File @@ -27,6 +27,7 @@ export interface Message { export const messageSchema = { $async: true, + $id: getID(MESSAGE_NAME), type: 'object', required: ['body'], properties: { @@ -41,27 +42,12 @@ export const messageSchema = { }, } -export interface Mailbox { - timeline: SwarmFeed - publicKey?: string -} - -export const mailboxProperty = { - type: 'object', - required: ['timeline'], - properties: { - timeline: swarmFeedProperty, - publicKey: publicKeyProperty, - }, - additionalProperties: false, -} - -export type Mailboxes = Record +export type Mailboxes = Record export const mailboxesProperty = { type: 'object', patternProperties: { - '^[0-9a-zA-Z-_. ]{1,50}$': swarmFeedProperty, + '^[0-9a-zA-Z-_. ]{1,50}$': publicKeyProperty, }, additionalProperties: false, } diff --git a/packages/core/src/schemas/peer.ts b/packages/core/src/schemas/peer.ts index fb43a3e..2b7e1d2 100644 --- a/packages/core/src/schemas/peer.ts +++ b/packages/core/src/schemas/peer.ts @@ -1,7 +1,7 @@ import { PEER_NAME, getID } from '../namespace' import { Profile, profileProperty } from './profile' -import { publicKeyProperty } from './publicKey' +import { publicKeyProperty } from './scalars' export interface Peer { profile?: Profile diff --git a/packages/core/src/schemas/peerContact.ts b/packages/core/src/schemas/peerContact.ts index 8eef9f5..a5ff77d 100644 --- a/packages/core/src/schemas/peerContact.ts +++ b/packages/core/src/schemas/peerContact.ts @@ -1,7 +1,6 @@ import { PEER_CONTACT_NAME, getID } from '../namespace' -import { ethereumAddressProperty } from './ethereumAddress' -import { publicKeyProperty } from './publicKey' +import { ethereumAddressProperty, publicKeyProperty } from './scalars' export interface PeerContact { contactPublicKey: string diff --git a/packages/core/src/schemas/profile.ts b/packages/core/src/schemas/profile.ts index 4e698e4..64eec11 100644 --- a/packages/core/src/schemas/profile.ts +++ b/packages/core/src/schemas/profile.ts @@ -1,4 +1,4 @@ -import { ethereumAddressProperty } from './ethereumAddress' +import { ethereumAddressProperty } from './scalars' export interface Profile { displayName?: string diff --git a/packages/core/src/schemas/publicKey.ts b/packages/core/src/schemas/publicKey.ts deleted file mode 100644 index 1349a67..0000000 --- a/packages/core/src/schemas/publicKey.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const publicKeyProperty = { - type: 'string', - pattern: '^[0-9a-f]{130}$', -} diff --git a/packages/core/src/schemas/swarmFeed.ts b/packages/core/src/schemas/scalars.ts similarity index 55% rename from packages/core/src/schemas/swarmFeed.ts rename to packages/core/src/schemas/scalars.ts index b9bc8bc..e24e1b6 100644 --- a/packages/core/src/schemas/swarmFeed.ts +++ b/packages/core/src/schemas/scalars.ts @@ -1,6 +1,14 @@ import { FeedParams } from '@erebos/api-bzz-base' -import { ethereumAddressProperty } from './ethereumAddress' +export const ethereumAddressProperty = { + type: 'string', + pattern: '^0x[0-9a-f]{40}$', +} + +export const publicKeyProperty = { + type: 'string', + pattern: '^[0-9a-f]{130}$', +} export interface SwarmFeed extends FeedParams {} @@ -12,3 +20,8 @@ export const swarmFeedProperty = { topic: { type: 'string', pattern: '^0x[0-9a-f]{64}$' }, }, } + +export const swarmHashProperty = { + type: 'string', + pattern: '^[0-9a-f]{64}$', +} diff --git a/packages/core/src/schemas/swarmHash.ts b/packages/core/src/schemas/swarmHash.ts deleted file mode 100644 index e42a1f6..0000000 --- a/packages/core/src/schemas/swarmHash.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const swarmHashProperty = { - type: 'string', - pattern: '^[0-9a-f]{64}$', -} diff --git a/packages/core/src/validation.ts b/packages/core/src/validation.ts index b852269..cd512f3 100644 --- a/packages/core/src/validation.ts +++ b/packages/core/src/validation.ts @@ -7,12 +7,19 @@ import { fromBuffer } from './utils' import { EntityPayload } from './types' import { contactSchema } from './schemas/contact' +import { messageSchema } from './schemas/messaging' import { fileSystemSchema } from './schemas/fileSystem' import { peerSchema } from './schemas/peer' import { peerContactSchema } from './schemas/peerContact' const ajv = new Ajv() -ajv.addSchema([contactSchema, fileSystemSchema, peerSchema, peerContactSchema]) +ajv.addSchema([ + contactSchema, + messageSchema, + fileSystemSchema, + peerSchema, + peerContactSchema, +]) export async function validateEntity( entity: EntityPayload,