Skip to content

Commit

Permalink
feat: create storage package
Browse files Browse the repository at this point in the history
  • Loading branch information
yknl committed Aug 11, 2020
1 parent 1867afe commit c99680b
Show file tree
Hide file tree
Showing 8 changed files with 1,426 additions and 0 deletions.
11 changes: 11 additions & 0 deletions packages/storage/README.md
@@ -0,0 +1,11 @@
# `storage`

> TODO: description
## Usage

```
const storage = require('storage');
// TODO: DEMONSTRATE API
```
7 changes: 7 additions & 0 deletions packages/storage/__tests__/storage.test.js
@@ -0,0 +1,7 @@
'use strict';

const storage = require('..');

describe('storage', () => {
it('needs tests');
});
32 changes: 32 additions & 0 deletions packages/storage/package.json
@@ -0,0 +1,32 @@
{
"name": "@stacks/storage",
"version": "1.0.0",
"description": "Blockstack storage library",
"author": "yknl <yukanliao@gmail.com>",
"homepage": "https://blockstack.org",
"license": "GPL-3.0-or-later",
"main": "lib/index.js",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/blockstack/blockstack.js.git"
},
"scripts": {
"build": "rimraf lib && tsc -b tsconfig.build.json",
"test": "echo \"Error: run tests from root\" && exit 1"
},
"bugs": {
"url": "https://github.com/blockstack/blockstack.js/issues"
},
"dependencies": {
"@stacks/auth": "^1.0.0",
"@stacks/common": "^1.0.0",
"@stacks/encryption": "^1.0.0"
}
}
135 changes: 135 additions & 0 deletions packages/storage/src/fileContentLoader.ts
@@ -0,0 +1,135 @@
/**
* Retrieves the specified file from the app's data store.
* @param {String} path - the path to the file to read
* @returns {Promise} that resolves to the raw data in the file
* or rejects with an error
*/
export

/** @ignore */
type PutFileContent = string | Buffer | ArrayBufferView | ArrayBufferLike | Blob

/** @ignore */
export class FileContentLoader {
readonly content: Buffer | Blob

readonly wasString: boolean

readonly contentType: string

readonly contentByteLength: number

private loadedData?: Promise<Buffer>

static readonly supportedTypesMsg = 'Supported types are: `string` (to be UTF8 encoded), '
+ '`Buffer`, `Blob`, `File`, `ArrayBuffer`, `UInt8Array` or any other typed array buffer. '

constructor(content: PutFileContent, contentType: string) {
this.wasString = typeof content === 'string'
this.content = FileContentLoader.normalizeContentDataType(content, contentType)
this.contentType = contentType || this.detectContentType()
this.contentByteLength = this.detectContentLength()
}

private static normalizeContentDataType(content: PutFileContent,
contentType: string): Buffer | Blob {
try {
if (typeof content === 'string') {
// If a charset is specified it must be either utf8 or ascii, otherwise the encoded content
// length cannot be reliably detected. If no charset specified it will be treated as utf8.
const charset = (contentType || '').toLowerCase().replace('-', '')
if (charset.includes('charset') && !charset.includes('charset=utf8') && !charset.includes('charset=ascii')) {
throw new Error(`Unable to determine byte length with charset: ${contentType}`)
}
if (typeof TextEncoder !== 'undefined') {
const encodedString = new TextEncoder().encode(content)
return Buffer.from(encodedString.buffer)
}
return Buffer.from(content)
} else if (Buffer.isBuffer(content)) {
return content
} else if (ArrayBuffer.isView(content)) {
return Buffer.from(content.buffer, content.byteOffset, content.byteLength)
} else if (typeof Blob !== 'undefined' && content instanceof Blob) {
return content
} else if (typeof ArrayBuffer !== 'undefined' && content instanceof ArrayBuffer) {
return Buffer.from(content)
} else if (Array.isArray(content)) {
// Provided with a regular number `Array` -- this is either an (old) method
// of representing an octet array, or a dev error. Perform basic check for octet array.
if (content.length > 0
&& (!Number.isInteger(content[0]) || content[0] < 0 || content[0] > 255)) {
throw new Error(`Unexpected array values provided as file data: value "${content[0]}" at index 0 is not an octet number. ${this.supportedTypesMsg}`)
}
return Buffer.from(content)
} else {
const typeName = Object.prototype.toString.call(content)
throw new Error(`Unexpected type provided as file data: ${typeName}. ${this.supportedTypesMsg}`)
}
} catch (error) {
console.error(error)
throw new Error(`Error processing data: ${error}`)
}
}

private detectContentType(): string {
if (this.wasString) {
return 'text/plain; charset=utf-8'
} else if (typeof Blob !== 'undefined' && this.content instanceof Blob && this.content.type) {
return this.content.type
} else {
return 'application/octet-stream'
}
}

private detectContentLength(): number {
if (ArrayBuffer.isView(this.content) || Buffer.isBuffer(this.content)) {
return this.content.byteLength
} else if (typeof Blob !== 'undefined' && this.content instanceof Blob) {
return this.content.size
}
const typeName = Object.prototype.toString.call(this.content)
const error = new Error(`Unexpected type "${typeName}" while detecting content length`)
console.error(error)
throw error
}

private async loadContent(): Promise<Buffer> {
try {
if (Buffer.isBuffer(this.content)) {
return this.content
} else if (ArrayBuffer.isView(this.content)) {
return Buffer.from(this.content.buffer, this.content.byteOffset, this.content.byteLength)
} else if (typeof Blob !== 'undefined' && this.content instanceof Blob) {
const reader = new FileReader()
const readPromise = new Promise<Buffer>((resolve, reject) => {
reader.onerror = (err) => {
reject(err)
}
reader.onload = () => {
const arrayBuffer = reader.result as ArrayBuffer
resolve(Buffer.from(arrayBuffer))
}
reader.readAsArrayBuffer(this.content as Blob)
})
const result = await readPromise
return result
} else {
const typeName = Object.prototype.toString.call(this.content)
throw new Error(`Unexpected type ${typeName}`)
}
} catch (error) {
console.error(error)
const loadContentError = new Error(`Error loading content: ${error}`)
console.error(loadContentError)
throw loadContentError
}
}

load(): Promise<Buffer | string> {
if (this.loadedData === undefined) {
this.loadedData = this.loadContent()
}
return this.loadedData
}
}

0 comments on commit c99680b

Please sign in to comment.