Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: streams in browser #116

Merged
merged 4 commits into from
Nov 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ matrix:
- ./start_swarm_node.sh -d
- os: osx
env:
- SWARM_VERSION=swarm-darwin-amd64-0.5.0-c1c233d1
- SWARM_VERSION=swarm-darwin-amd64-0.5.2-d8b56ff0
before_install:
- mkdir ~/data && echo "password" > ~/password
- curl "https://ethswarm.blob.core.windows.net/builds/$SWARM_VERSION.tar.gz" | tar -x
Expand Down
23 changes: 23 additions & 0 deletions __tests__/api-bzz-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
resOrError,
resJSON,
resText,
isDirectoryData,
} from '@erebos/api-bzz-base'
import { Readable } from 'readable-stream'

describe('api-bzz-base', () => {
const TEST_URL = 'https://example.com/swarm-gateways/'
Expand All @@ -17,6 +19,27 @@ describe('api-bzz-base', () => {
fetch.resetMocks()
})

it('isDirectoryData should correctly identify structures ', function() {
const data: Array<Array<any>> = [
[1, false],
[undefined, false],
[null, false],
[[], false],
[new Date(), false],
[{ some: 'object' }, false],
['', false],
['data', false],
[Buffer.from('data'), false],
[new Readable(), false],
[{ 'some/path': { data: '' } }, true],
[{}, true],
]

data.forEach(([input, result]) =>
expect(isDirectoryData(input)).toEqual(result),
)
})

it('exports getModeProtocol() utility function', () => {
expect(getModeProtocol('default')).toBe('bzz:/')
expect(getModeProtocol('immutable')).toBe('bzz-immutable:/')
Expand Down
50 changes: 50 additions & 0 deletions __tests__/api-bzz-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import * as os from 'os'
import * as path from 'path'
import { Readable } from 'stream'
import * as crypto from 'crypto'
import * as fs from 'fs-extra'
import { Subject } from 'rxjs'
import * as tar from 'tar-stream'
Expand Down Expand Up @@ -68,6 +70,30 @@ describe('api-bzz-node', () => {
expect(await response.text()).toBe(uploadContent)
})

it('uploading and downloading single file using bzz and streams', async () => {
const value = crypto.randomBytes(60).toString('hex')
const s = new Readable()
s.push(value)
s.push(null)

const manifestHash = await bzz.upload(s, {
contentType: 'text/plain',
})

const data: Array<Uint8Array> = []
const responseStream = await bzz.downloadStream(manifestHash)
responseStream.on('data', (d: Uint8Array) => {
data.push(d)
})

return new Promise(resolve => {
responseStream.on('end', () => {
expect(Buffer.concat(data).toString()).toBe(value)
resolve()
})
})
})

it('uploading and downloading single file using bzz with content path', async () => {
const manifestHash = await bzz.upload(uploadContent, {
contentType: 'text/plain',
Expand All @@ -84,6 +110,30 @@ describe('api-bzz-node', () => {
expect(await response.text()).toBe(uploadContent)
})

it('uploading and downloading single file using bzz-raw and streams', async () => {
const value = crypto.randomBytes(60).toString('hex')
const s = new Readable()
s.push(value)
s.push(null)

const manifestHash = await bzz.upload(s, { size: value.length })

const data: Array<Uint8Array> = []
const responseStream = await bzz.downloadStream(manifestHash, {
mode: 'raw',
})
responseStream.on('data', (d: Uint8Array) => {
data.push(d)
})

return new Promise(resolve => {
responseStream.on('end', () => {
expect(Buffer.concat(data).toString()).toBe(value)
resolve()
})
})
})

it('downloading the manifest', async () => {
const manifestHash = await bzz.upload(uploadContent, {
contentType: 'text/plain',
Expand Down
78 changes: 77 additions & 1 deletion __tests__/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,34 @@ describe('browser', () => {
page.on('console', msg => {
for (let i = 0; i < msg.args().length; ++i) {
/* eslint-disable-next-line no-console */
console.log(`${i}: ${msg.args()[i]}`)
console.log(msg.args()[i])
}
})

page.on('pageerror', function(err) {
/* eslint-disable-next-line no-console */
console.log('Page error: ' + err.toString())
AuHau marked this conversation as resolved.
Show resolved Hide resolved
})

page.on('error', function(err) {
/* eslint-disable-next-line no-console */
console.log('Error: ' + err.toString())
AuHau marked this conversation as resolved.
Show resolved Hide resolved
})

await page.addScriptTag({
path: resolve(
__dirname,
'../packages/swarm-browser/dist/erebos.swarm.development.js',
),
})

await page.addScriptTag({
path: resolve(
__dirname,
'../packages/swarm-browser/dist/readable-stream.js',
AuHau marked this conversation as resolved.
Show resolved Hide resolved
),
})

await page.addScriptTag({
path: resolve(
__dirname,
Expand Down Expand Up @@ -134,6 +152,39 @@ describe('browser', () => {
expect(evalResponse).toBe(uploadContent)
})

it('uploads/downloads the file with bzz using streams', async () => {
const manifestHash = await evalClient(async (client, uploadContent) => {
const s = new NodeStream.Readable()
s.push(uploadContent)
s.push(null)

return await client.bzz.upload(s, {
contentType: 'text/plain',
})
}, uploadContent)

const evalResponse = await evalClient(async (client, manifestHash) => {
const response = await client.bzz.downloadStream(manifestHash)
return new Promise(resolve => {
const data: Array<Uint8Array> = []

response.on('data', (d: Uint8Array) => {
data.push(d)
})

response.on('end', () => {
resolve(data)
})
})
}, manifestHash)

// Reconstruct Buffer
const decodedResponse = evalResponse.map(b =>
Buffer.from(Object.values(b)),
)
expect(Buffer.concat(decodedResponse).toString()).toBe(uploadContent)
})

it('lists common prefixes for nested directories', async () => {
const expectedCommonPrefixes = ['dir1/', 'dir2/']
const dirs = {
Expand Down Expand Up @@ -227,6 +278,31 @@ describe('browser', () => {
expect(directoryList).toEqual({ ...dir, '/': dir[defaultPath] })
})

it('downloadDirectoryData() streams the same data provided to uploadDirectory()', async () => {
const dir = {
[`foo-${uploadContent}.txt`]: {
data: `this is foo-${uploadContent}.txt`,
},
[`bar-${uploadContent}.txt`]: {
data: `this is bar-${uploadContent}.txt`,
},
}

const downloadedDir = await evalClient(async (client, dir) => {
const dirHash = await client.bzz.uploadDirectory(dir)
const response = await client.bzz.downloadDirectoryData(dirHash)
return Object.keys(response).reduce(
(prev, current) => ({
...prev,
[current]: { data: response[current].data.toString('utf8') },
}),
{},
)
}, dir)

expect(downloadedDir).toEqual(dir)
})

AuHau marked this conversation as resolved.
Show resolved Hide resolved
it('supports feeds posting and getting', async () => {
jest.setTimeout(20000)
const data = { test: uploadContent }
Expand Down
40 changes: 21 additions & 19 deletions docs/api-bzz.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,13 +407,33 @@ The `download()` method returns a [`Response` instance](https://developer.mozill

**Returns** `Promise<Response>`

### .downloadStream()

The `downloadStream()` method returns a NodeJs's compatible [`Readable` instance](https://nodejs.org/api/stream.html#stream_class_stream_readable) if the request succeeds, or throws a `HTTPError`.

**Arguments**

1. `hashOrDomain: string`: ENS name or Swarm hash
1. [`options?: DownloadOptions = {}`](#downloadoptions) optional object providing the `path`, `mode` and `contentType`.

**Returns** `Promise<Readable>`

### .downloadDirectoryData()

**Arguments**

1. `hashOrDomain: string`: ENS name or Swarm hash
1. [`options?: DownloadOptions = {}`](#downloadoptions)

**Returns** `Promise<DirectoryData>`

### .uploadFile()

Uploads a single file and returns the hash. If the `contentType` option is provided, it will return the manifest hash, otherwise the file will be uploaded as raw data and will return the hash of the data itself.

**Arguments**

1. `data: string | Buffer`
1. `data: string | Buffer | Readable`
1. [`options: UploadOptions = {}`](#uploadoptions)

**Returns** `Promise<string>`
Expand Down Expand Up @@ -667,15 +687,6 @@ Returns a [RxJS `Observable`](https://rxjs.dev/api/index/class/Observable) emitt

**Returns** `Observable<FileEntry>`

### .downloadDirectoryData()

**Arguments**

1. `hashOrDomain: string`: ENS name or Swarm hash
1. [`options?: DownloadOptions = {}`](#downloadoptions)

**Returns** `Promise<DirectoryData>`

### .downloadTarTo()

**Arguments**
Expand Down Expand Up @@ -718,15 +729,6 @@ Call `downloadFileTo()` or `downloadDirectoryTo()` depending on the provided `pa

**Returns** `Promise<void>`

### .uploadFileStream()

**Arguments**

1. `stream: Readable`: Node.js [`Readable stream`](https://nodejs.org/dist/latest-v10.x/docs/api/stream.html#stream_class_stream_readable) instance
1. [`options?: UploadOptions = {}`](#uploadoptions)

**Returns** `Promise<string>`

### .uploadTar()

**Arguments**
Expand Down
7 changes: 6 additions & 1 deletion packages/api-bzz-base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
"@babel/runtime": "^7.6.2",
"@erebos/hex": "^0.10.0",
"@erebos/keccak256": "^0.10.0",
"rxjs": "^6.5.3"
"readable-stream": "^3.1.1",
"rxjs": "^6.5.3",
"tar-stream": "^2.1.0"
},
"devDependencies": {
"@types/readable-stream": "^2.3.5"
}
}