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: ens support for download methods #659

Merged
merged 4 commits into from
May 11, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 50 additions & 26 deletions src/bee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,25 @@ import {
assertPssMessageHandler,
assertPublicKey,
assertReference,
assertReferenceOrEns,
assertRequestOptions,
assertUploadOptions,
makeTagUid,
} from './utils/type'
import { setJsonData, getJsonData } from './feed/json'
import { makeCollectionFromFileList, assertCollection } from './utils/collection'
import { makeCollectionFromFS } from './utils/collection.node'
import { AllTagsOptions, CHUNK_SIZE, Collection, Ky, Readable, RequestOptions, SPAN_SIZE, UploadResult } from './types'
import {
AllTagsOptions,
CHUNK_SIZE,
Collection,
Ky,
Readable,
ReferenceOrEns,
RequestOptions,
SPAN_SIZE,
UploadResult,
} from './types'

import type { Options as KyOptions } from 'ky-universal'

Expand Down Expand Up @@ -161,32 +172,36 @@ export class Bee {
/**
* Download data as a byte array
*
* @param reference Bee data reference
* @param reference Bee data reference in hex string (either 64 or 128 chars long) or ENS domain.
* @param options Options that affects the request behavior
* @throws TypeError if some of the input parameters is not expected type
* @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters
* @see [Bee docs - Upload and download](https://docs.ethswarm.org/docs/access-the-swarm/upload-and-download)
* @see [Bee API reference - `GET /bytes`](https://docs.ethswarm.org/api/#tag/Bytes/paths/~1bytes~1{reference}/get)
*/
async downloadData(reference: Reference | string, options?: RequestOptions): Promise<Data> {
async downloadData(reference: ReferenceOrEns | string, options?: RequestOptions): Promise<Data> {
assertRequestOptions(options)
assertReference(reference)
assertReferenceOrEns(reference)

return bytes.download(this.getKy(options), reference)
}

/**
* Download data as a Readable stream
*
* @param reference Bee data reference
* @param reference Bee data reference in hex string (either 64 or 128 chars long) or ENS domain.
* @param options Options that affects the request behavior
* @throws TypeError if some of the input parameters is not expected type
* @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters
* @see [Bee docs - Upload and download](https://docs.ethswarm.org/docs/access-the-swarm/upload-and-download)
* @see [Bee API reference - `GET /bytes`](https://docs.ethswarm.org/api/#tag/Bytes/paths/~1bytes~1{reference}/get)
*/
async downloadReadableData(
reference: Reference | string,
reference: ReferenceOrEns | string,
options?: RequestOptions,
): Promise<ReadableStream<Uint8Array>> {
assertRequestOptions(options)
assertReference(reference)
assertReferenceOrEns(reference)

return bytes.downloadReadable(this.getKy(options), reference)
}
Expand Down Expand Up @@ -225,14 +240,16 @@ export class Bee {
/**
* Download chunk as a byte array
*
* @param reference Bee chunk reference
* @param reference Bee chunk reference in hex string (either 64 or 128 chars long) or ENS domain.
* @param options Options that affects the request behavior
* @throws TypeError if some of the input parameters is not expected type
* @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters
* @see [Bee docs - Upload and download](https://docs.ethswarm.org/docs/access-the-swarm/upload-and-download)
* @see [Bee API reference - `GET /chunks`](https://docs.ethswarm.org/api/#tag/Chunk/paths/~1chunks~1{reference}/get)
*/
async downloadChunk(reference: Reference | string, options?: RequestOptions): Promise<Data> {
async downloadChunk(reference: ReferenceOrEns | string, options?: RequestOptions): Promise<Data> {
assertRequestOptions(options)
assertReference(reference)
assertReferenceOrEns(reference)

return chunk.download(this.getKy(options), reference)
}
Expand Down Expand Up @@ -289,38 +306,41 @@ export class Bee {
/**
* Download single file.
*
* @param reference Bee file reference
* @param reference Bee file reference in hex string (either 64 or 128 chars long) or ENS domain.
* @param path If reference points to manifest, then this parameter defines path to the file
* @param options Options that affects the request behavior
*
* @throws TypeError if some of the input parameters is not expected type
* @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters
* @see Data
* @see [Bee docs - Upload and download](https://docs.ethswarm.org/docs/access-the-swarm/upload-and-download)
* @see [Bee API reference - `GET /bzz`](https://docs.ethswarm.org/api/#tag/Collection/paths/~1bzz~1{reference}~1{path}/get)
*/
async downloadFile(reference: Reference | string, path = '', options?: RequestOptions): Promise<FileData<Data>> {
async downloadFile(reference: ReferenceOrEns | string, path = '', options?: RequestOptions): Promise<FileData<Data>> {
assertRequestOptions(options)
assertReference(reference)
assertReferenceOrEns(reference)

return bzz.downloadFile(this.getKy(options), reference, path)
}

/**
* Download single file as a readable stream
*
* @param reference Hash reference to file
* @param reference Bee file reference in hex string (either 64 or 128 chars long) or ENS domain.
* @param path If reference points to manifest / collections, then this parameter defines path to the file
* @param options Options that affects the request behavior
* @throws TypeError if some of the input parameters is not expected type
* @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters
*
* @see [Bee docs - Upload and download](https://docs.ethswarm.org/docs/access-the-swarm/upload-and-download)
* @see [Bee API reference - `GET /bzz`](https://docs.ethswarm.org/api/#tag/Collection/paths/~1bzz~1{reference}~1{path}/get)
*/
async downloadReadableFile(
reference: Reference | string,
reference: ReferenceOrEns | string,
path = '',
options?: RequestOptions,
): Promise<FileData<ReadableStream<Uint8Array>>> {
assertRequestOptions(options)
assertReference(reference)
assertReferenceOrEns(reference)

return bzz.downloadFileReadable(this.getKy(options), reference, path)
}
Expand Down Expand Up @@ -566,9 +586,10 @@ export class Bee {
*
* **Warning! Not allowed when node is in Gateway mode!**
*
* @param reference Bee data reference
* @param reference Bee data reference in hex string (either 64 or 128 chars long) or ENS domain.
* @param options Options that affects the request behavior
* @throws TypeError if reference is in not correct format
* @throws TypeError if some of the input parameters is not expected type
* @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters
*
* @see [Bee docs - Pinning](https://docs.ethswarm.org/docs/access-the-swarm/pinning)
*/
Expand All @@ -582,31 +603,34 @@ export class Bee {
/**
* Instructs the Bee node to reupload a locally pinned data into the network.
*
* @param reference
* @param reference Bee data reference to be re-uploaded in hex string (either 64 or 128 chars long) or ENS domain.
* @param options Options that affects the request behavior
* @throws BeeArgumentError if the reference is not locally pinned
* @throws TypeError if reference is in not correct format
* @throws TypeError if some of the input parameters is not expected type
* @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters
*
* @see [Bee API reference - `PUT /stewardship`](https://docs.ethswarm.org/api/#tag/Stewardship/paths/~1stewardship~1{reference}/put)
*/
async reuploadPinnedData(reference: Reference | string, options?: RequestOptions): Promise<void> {
async reuploadPinnedData(reference: ReferenceOrEns | string, options?: RequestOptions): Promise<void> {
assertRequestOptions(options)
assertReference(reference)
assertReferenceOrEns(reference)

await stewardship.reupload(this.getKy(options), reference)
}

/**
* Checks if content specified by reference is retrievable from the network.
*
* @param reference The checked content
* @param reference Bee data reference to be checked in hex string (either 64 or 128 chars long) or ENS domain.
* @param options Options that affects the request behavior
* @throws TypeError if some of the input parameters is not expected type
* @throws BeeArgumentError if there is passed ENS domain with invalid unicode characters
*
* @see [Bee API reference - `GET /stewardship`](https://docs.ethswarm.org/api/#tag/Stewardship/paths/~1stewardship~1{reference}/get)
*/
async isReferenceRetrievable(reference: Reference | string, options?: RequestOptions): Promise<boolean> {
async isReferenceRetrievable(reference: ReferenceOrEns | string, options?: RequestOptions): Promise<boolean> {
assertRequestOptions(options)
assertReference(reference)
assertReferenceOrEns(reference)

return stewardship.isRetrievable(this.getKy(options), reference)
}
Expand Down
6 changes: 3 additions & 3 deletions src/modules/bytes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { BatchId, Data, Ky, Reference, UploadOptions } from '../types'
import type { BatchId, Data, Ky, Reference, ReferenceOrEns, UploadOptions } from '../types'
import { prepareData } from '../utils/data'
import { extractUploadHeaders } from '../utils/headers'
import { http } from '../utils/http'
Expand Down Expand Up @@ -45,7 +45,7 @@ export async function upload(
* @param ky
* @param hash Bee content reference
*/
export async function download(ky: Ky, hash: Reference): Promise<Data> {
export async function download(ky: Ky, hash: ReferenceOrEns): Promise<Data> {
const response = await http<ArrayBuffer>(ky, {
responseType: 'arraybuffer',
path: `${endpoint}/${hash}`,
Expand All @@ -60,7 +60,7 @@ export async function download(ky: Ky, hash: Reference): Promise<Data> {
* @param ky
* @param hash Bee content reference
*/
export async function downloadReadable(ky: Ky, hash: Reference): Promise<ReadableStream<Uint8Array>> {
export async function downloadReadable(ky: Ky, hash: ReferenceOrEns): Promise<ReadableStream<Uint8Array>> {
const response = await http<ReadableStream<Uint8Array>>(ky, {
responseType: 'stream',
path: `${endpoint}/${hash}`,
Expand Down
5 changes: 3 additions & 2 deletions src/modules/bzz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Ky,
Readable,
Reference,
ReferenceOrEns,
UploadHeaders,
UploadResult,
} from '../types'
Expand Down Expand Up @@ -83,7 +84,7 @@ export async function uploadFile(
* @param hash Bee file or collection hash
* @param path If hash is collection then this defines path to a single file in the collection
*/
export async function downloadFile(ky: Ky, hash: string, path = ''): Promise<FileData<Data>> {
export async function downloadFile(ky: Ky, hash: ReferenceOrEns, path = ''): Promise<FileData<Data>> {
const response = await http<ArrayBuffer>(ky, {
method: 'GET',
responseType: 'arraybuffer',
Expand All @@ -106,7 +107,7 @@ export async function downloadFile(ky: Ky, hash: string, path = ''): Promise<Fil
*/
export async function downloadFileReadable(
ky: Ky,
hash: string,
hash: ReferenceOrEns,
path = '',
): Promise<FileData<ReadableStream<Uint8Array>>> {
const response = await http<ReadableStream<Uint8Array>>(ky, {
Expand Down
4 changes: 2 additions & 2 deletions src/modules/chunk.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { BatchId, Data, Ky, Reference, ReferenceResponse, UploadOptions } from '../types'
import type { BatchId, Data, Ky, Reference, ReferenceOrEns, ReferenceResponse, UploadOptions } from '../types'
import { extractUploadHeaders } from '../utils/headers'
import { http } from '../utils/http'
import { wrapBytesWithHelpers } from '../utils/bytes'
Expand Down Expand Up @@ -44,7 +44,7 @@ export async function upload(
* @param hash Bee content reference
*
*/
export async function download(ky: Ky, hash: string): Promise<Data> {
export async function download(ky: Ky, hash: ReferenceOrEns): Promise<Data> {
const response = await http<ArrayBuffer>(ky, {
responseType: 'arraybuffer',
path: `${endpoint}/${hash}`,
Expand Down
6 changes: 3 additions & 3 deletions src/modules/stewardship.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Ky, Reference } from '../types'
import type { Ky, ReferenceOrEns } from '../types'
import { http } from '../utils/http'

const stewardshipEndpoint = 'stewardship'
Expand All @@ -10,7 +10,7 @@ const stewardshipEndpoint = 'stewardship'
* @param options
* @throws BeeResponseError if not locally pinned or invalid data
*/
export async function reupload(ky: Ky, reference: Reference): Promise<void> {
export async function reupload(ky: Ky, reference: ReferenceOrEns): Promise<void> {
await http(ky, {
method: 'put',
path: `${stewardshipEndpoint}/${reference}`,
Expand All @@ -21,7 +21,7 @@ interface IsRetrievableResponse {
isRetrievable: boolean
}

export async function isRetrievable(ky: Ky, reference: Reference): Promise<boolean> {
export async function isRetrievable(ky: Ky, reference: ReferenceOrEns): Promise<boolean> {
const response = await http<IsRetrievableResponse>(ky, {
method: 'get',
responseType: 'json',
Expand Down
6 changes: 6 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ export const FEED_INDEX_HEX_LENGTH = 16
*/
export type Reference = HexString<typeof REFERENCE_HEX_LENGTH> | HexString<typeof ENCRYPTED_REFERENCE_HEX_LENGTH>

/**
* Type that represents either Swarm's reference in hex string or ESN domain (something.eth).
* This is the type used on all the download functions.
*/
export type ReferenceOrEns = Reference | string

export type PlainBytesReference = Bytes<typeof REFERENCE_BYTES_LENGTH>
export type EncryptedBytesReference = Bytes<typeof ENCRYPTED_REFERENCE_BYTES_LENGTH>
export type BytesReference = PlainBytesReference | EncryptedBytesReference
Expand Down
42 changes: 41 additions & 1 deletion src/utils/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ import {
RequestOptions,
PostageBatchOptions,
CashoutOptions,
ReferenceOrEns,
} from '../types'
import { BeeArgumentError } from './error'
import { isFile } from './file'
import { assertHexString, assertPrefixedHexString } from './hex'
import { assertHexString, assertPrefixedHexString, isHexString } from './hex'
import { isReadable } from './stream'

export function isUint8Array(obj: unknown): obj is Uint8Array {
Expand Down Expand Up @@ -100,6 +101,45 @@ export function assertReference(value: unknown): asserts value is Reference {
}
}

export function assertReferenceOrEns(value: unknown): asserts value is ReferenceOrEns {
if (typeof value !== 'string') {
throw new TypeError('ReferenceOrEns has to be a string!')
}

if (isHexString(value)) {
assertReference(value)

return
}

/**
* a.asdf - VALID
* test.eth - VALID
* ADAM.ETH - VALID
* ADAM UHLIR.ETH - INVALID
* test.whatever.eth - VALID
* -adg.ets - INVALID
* adg-.ets - INVALID
* as-a.com - VALID
* ethswarm.org - VALID
* http://asdf.asf - INVALID
* řš+ýí.šě+ř.čě - VALID
* tsg.asg?asg - INVALID
* tsg.asg:1599 - INVALID
* ethswarm.something- - INVALID
* ethswarm.-something - INVALID
* ethswarm.some-thing - VALID
*/
const DOMAIN_REGEX = /^(?:(?!-)[^.\/?:\s]{1,63}(?<!-)\.)+(?!-)[^.\/?:\s]{2,63}(?<!-)$/

// We are doing best-effort validation of domain here. The proper way would be to do validation using IDNA UTS64 standard
// but that would give us high penalty to our dependencies as the library (idna-uts46-hx) that does this validation and translation
// adds 160kB minified size which is significant. We expects that full validation will be done on Bee side.
if (!DOMAIN_REGEX.test(value)) {
throw new TypeError('ReferenceOrEns is not valid Reference, but also not valid ENS domain.')
}
}

export function assertAddress(value: unknown): asserts value is Address {
assertHexString(value, ADDRESS_HEX_LENGTH, 'Address')
}
Expand Down
32 changes: 32 additions & 0 deletions test/unit/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,38 @@ export function testReferenceAssertions(executor: (input: unknown) => void): voi
})
}

export function testReferenceOrEnsAssertions(executor: (input: unknown) => void): void {
it('should throw exception for bad ReferenceOrEns', async () => {
await expect(() => executor(1)).rejects.toThrow(TypeError)
await expect(() => executor(true)).rejects.toThrow(TypeError)
await expect(() => executor({})).rejects.toThrow(TypeError)
await expect(() => executor(null)).rejects.toThrow(TypeError)
await expect(() => executor(undefined)).rejects.toThrow(TypeError)
await expect(() => executor([])).rejects.toThrow(TypeError)

// Not an valid hexstring (ZZZ)
await expect(() => executor('ZZZfb5a872396d9693e5c9f9d7233cfa93f395c093371017ff44aa9ae6564cdd')).rejects.toThrow(
TypeError,
)

// Prefixed hexstring is not accepted
await expect(() => executor('0x634fb5a872396d9693e5c9f9d7233cfa93f395c093371017ff44aa9ae6564cdd')).rejects.toThrow(
TypeError,
)

// Length mismatch
await expect(() => executor('4fb5a872396d9693e5c9f9d7233cfa93f395c093371017ff44aa9ae6564cdd')).rejects.toThrow(
TypeError,
)

// ENS with invalid characters
await expect(() => executor('')).rejects.toThrow(TypeError)
await expect(() => executor('some space.eth')).rejects.toThrow(TypeError)
await expect(() => executor('-example.eth')).rejects.toThrow(TypeError)
await expect(() => executor('http://example.eth')).rejects.toThrow(TypeError)
AuHau marked this conversation as resolved.
Show resolved Hide resolved
})
}

export function testAddressPrefixAssertions(executor: (input: unknown) => void): void {
it('should throw exception for bad AddressPrefix', async () => {
await expect(() => executor(1)).rejects.toThrow(TypeError)
Expand Down
Loading