@holochain-open-dev/file-storage 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # @holochain-open-dev/file-storage
2
+
3
+ Frontend module for the Holochain zome `hc_zome_file_storage`.
4
+
5
+ This package includes types, a service and a collection of Custom Elements to build Holochain applications that automatically connect and interact with the `hc_zome_file_storage` zome.
6
+
7
+ By using [Custom Elements](https://developers.google.com/web/fundamentals/web-components/customelements), this package exports frontend blocks reusable in any framework, that make it really easy for consuming web applications to include functionality to create and update profiles, or search for an agent in the DHT.
8
+
9
+ Read about how to include both the zome and this frontend module in your application here:
10
+
11
+ - https://holochain-open-dev.github.io/file-storage
@@ -0,0 +1,3 @@
1
+ import { Context } from '@holochain-open-dev/context';
2
+ import { FileStorageService } from './services/file-storage.service';
3
+ export declare const fileStorageServiceContext: Context<FileStorageService>;
@@ -0,0 +1,3 @@
1
+ import { createContext } from '@holochain-open-dev/context';
2
+ export const fileStorageServiceContext = createContext('hc_zome_file_storage/file-storage-servce');
3
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAW,MAAM,6BAA6B,CAAC;AAGrE,MAAM,CAAC,MAAM,yBAAyB,GACpC,aAAa,CAAC,0CAA0C,CAAC,CAAC","sourcesContent":["import { createContext, Context } from '@holochain-open-dev/context';\nimport { FileStorageService } from './services/file-storage.service';\n\nexport const fileStorageServiceContext: Context<FileStorageService> =\n createContext('hc_zome_file_storage/file-storage-servce');\n"]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { __decorate } from "tslib";
2
+ import { UploadFiles } from '../elements/upload-files';
3
+ import { customElement } from 'lit/decorators.js';
4
+ let UF = class UF extends UploadFiles {
5
+ };
6
+ UF = __decorate([
7
+ customElement('upload-files')
8
+ ], UF);
9
+ //# sourceMappingURL=upload-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-files.js","sourceRoot":"","sources":["../../src/definitions/upload-files.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGlD,IAAM,EAAE,GAAR,MAAM,EAAG,SAAQ,WAAW;CAAG,CAAA;AAAzB,EAAE;IADP,aAAa,CAAC,cAAc,CAAC;GACxB,EAAE,CAAuB","sourcesContent":["import { UploadFiles } from '../elements/upload-files';\nimport { customElement } from 'lit/decorators.js';\n\n@customElement('upload-files')\nclass UF extends UploadFiles {}"]}
@@ -0,0 +1,19 @@
1
+ import { LitElement } from 'lit';
2
+ import { FileStorageService } from '../services/file-storage.service';
3
+ declare const UploadFiles_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/types/src/types").ScopedElementsHost>;
4
+ /**
5
+ * @fires file-uploaded - Fired after having uploaded the file
6
+ * @csspart dropzone - Style the dropzone itself
7
+ */
8
+ export declare class UploadFiles extends UploadFiles_base {
9
+ /** Public attributes */
10
+ oneFile: boolean;
11
+ acceptedFiles: string | undefined;
12
+ /** Dependencies */
13
+ _service: FileStorageService;
14
+ /** Private properties */
15
+ firstUpdated(): void;
16
+ render(): import("lit-html").TemplateResult<1>;
17
+ static get styles(): import("lit").CSSResult[];
18
+ }
19
+ export {};
@@ -0,0 +1,59 @@
1
+ import { __decorate } from "tslib";
2
+ import { css, html, LitElement } from 'lit';
3
+ import { property } from 'lit/decorators.js';
4
+ import { ScopedElementsMixin } from '@open-wc/scoped-elements';
5
+ import { DropzoneElement } from '@scoped-elements/dropzone';
6
+ import { contextProvided } from '@holochain-open-dev/context';
7
+ import { sharedStyles } from '../sharedStyles';
8
+ import { HolochainDropzone } from '../holochain-dropzone';
9
+ import { fileStorageServiceContext } from '../context';
10
+ /**
11
+ * @fires file-uploaded - Fired after having uploaded the file
12
+ * @csspart dropzone - Style the dropzone itself
13
+ */
14
+ export class UploadFiles extends ScopedElementsMixin(LitElement) {
15
+ constructor() {
16
+ /** Public attributes */
17
+ super(...arguments);
18
+ this.oneFile = false;
19
+ this.acceptedFiles = undefined;
20
+ }
21
+ /** Private properties */
22
+ firstUpdated() {
23
+ const service = this._service;
24
+ this.defineScopedElement('drop-zone', class extends DropzoneElement {
25
+ buildDropzone(dropzoneElement, options) {
26
+ return new HolochainDropzone(dropzoneElement, service, options);
27
+ }
28
+ });
29
+ }
30
+ render() {
31
+ return html `
32
+ <drop-zone
33
+ .oneFile=${this.oneFile}
34
+ .acceptedFiles=${this.acceptedFiles}
35
+ @file-uploaded=${(e) => (e.detail.hash = e.detail.file.hash)}
36
+ ></drop-zone>
37
+ `;
38
+ }
39
+ static get styles() {
40
+ return [
41
+ sharedStyles,
42
+ css `
43
+ :host {
44
+ display: contents;
45
+ }
46
+ `,
47
+ ];
48
+ }
49
+ }
50
+ __decorate([
51
+ property({ type: Boolean, attribute: 'one-file' })
52
+ ], UploadFiles.prototype, "oneFile", void 0);
53
+ __decorate([
54
+ property({ type: String, attribute: 'accepted-files' })
55
+ ], UploadFiles.prototype, "acceptedFiles", void 0);
56
+ __decorate([
57
+ contextProvided({ context: fileStorageServiceContext })
58
+ ], UploadFiles.prototype, "_service", void 0);
59
+ //# sourceMappingURL=upload-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-files.js","sourceRoot":"","sources":["../../src/elements/upload-files.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAI9D,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAEvD;;;GAGG;AACH,MAAM,OAAO,WAAY,SAAQ,mBAAmB,CAAC,UAAU,CAAC;IAAhE;QACE,wBAAwB;;QAE4B,YAAO,GAAG,KAAK,CAAC;QACX,kBAAa,GAEtD,SAAS,CAAC;IA4C5B,CAAC;IArCC,yBAAyB;IAEzB,YAAY;QACV,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;QAE9B,IAAI,CAAC,mBAAmB,CACtB,WAAW,EACX,KAAM,SAAQ,eAAe;YAC3B,aAAa,CAAC,eAA4B,EAAE,OAAwB;gBAClE,OAAO,IAAI,iBAAiB,CAAC,eAAe,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YAClE,CAAC;SACF,CACF,CAAC;IAEJ,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;;mBAEI,IAAI,CAAC,OAAO;yBACN,IAAI,CAAC,aAAa;yBAClB,CAAC,CAAc,EAAE,EAAE,CAClC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;;KAEzC,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,MAAM;QACf,OAAO;YACL,YAAY;YACZ,GAAG,CAAA;;;;OAIF;SACF,CAAC;IACJ,CAAC;CACF;AA/CqD;IAAnD,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;4CAAiB;AACX;IAAxD,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;kDAE9B;AAK1B;IADC,eAAe,CAAC,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC;6CAC1B","sourcesContent":["import { css, html, LitElement } from 'lit';\nimport { property } from 'lit/decorators.js';\n\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements';\nimport { DropzoneElement } from '@scoped-elements/dropzone';\nimport { contextProvided } from '@holochain-open-dev/context';\nimport { DropzoneOptions } from 'dropzone';\n\nimport { FileStorageService } from '../services/file-storage.service';\nimport { sharedStyles } from '../sharedStyles';\nimport { HolochainDropzone } from '../holochain-dropzone';\nimport { fileStorageServiceContext } from '../context';\n\n/**\n * @fires file-uploaded - Fired after having uploaded the file\n * @csspart dropzone - Style the dropzone itself\n */\nexport class UploadFiles extends ScopedElementsMixin(LitElement) {\n /** Public attributes */\n\n @property({ type: Boolean, attribute: 'one-file' }) oneFile = false;\n @property({ type: String, attribute: 'accepted-files' }) acceptedFiles:\n | string\n | undefined = undefined;\n\n /** Dependencies */\n\n @contextProvided({ context: fileStorageServiceContext })\n _service!: FileStorageService;\n\n /** Private properties */\n\n firstUpdated() {\n const service = this._service;\n\n this.defineScopedElement(\n 'drop-zone',\n class extends DropzoneElement {\n buildDropzone(dropzoneElement: HTMLElement, options: DropzoneOptions) {\n return new HolochainDropzone(dropzoneElement, service, options);\n }\n }\n );\n\n }\n\n render() {\n return html`\n <drop-zone\n .oneFile=${this.oneFile}\n .acceptedFiles=${this.acceptedFiles}\n @file-uploaded=${(e: CustomEvent) =>\n (e.detail.hash = e.detail.file.hash)}\n ></drop-zone>\n `;\n }\n\n static get styles() {\n return [\n sharedStyles,\n css`\n :host {\n display: contents;\n }\n `,\n ];\n }\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import Dropzone, { DropzoneOptions } from 'dropzone';
2
+ import { FileStorageService } from './services/file-storage.service';
3
+ export declare class HolochainDropzone extends Dropzone {
4
+ fileStorageService: FileStorageService;
5
+ constructor(el: HTMLElement, fileStorageService: FileStorageService, options: DropzoneOptions);
6
+ uploadFiles(files: Dropzone.DropzoneFile[]): void;
7
+ _uploadFilesToHolochain(dropzoneFiles: Dropzone.DropzoneFile[]): Promise<void>;
8
+ }
@@ -0,0 +1,30 @@
1
+ import Dropzone from 'dropzone';
2
+ export class HolochainDropzone extends Dropzone {
3
+ constructor(el, fileStorageService, options) {
4
+ options.url = 'https://holochain.org/'; // just to bypass the check.
5
+ super(el, options);
6
+ this.fileStorageService = fileStorageService;
7
+ }
8
+ uploadFiles(files) {
9
+ this._uploadFilesToHolochain(files);
10
+ }
11
+ async _uploadFilesToHolochain(dropzoneFiles) {
12
+ for (const file of dropzoneFiles) {
13
+ try {
14
+ this.emit('sending', file, undefined, undefined);
15
+ const hash = await this.fileStorageService.uploadFile(file, (percentatge, bytesSent) => {
16
+ this.emit('uploadprogress', file, percentatge * 100, bytesSent);
17
+ });
18
+ this.emit('success', file, undefined);
19
+ // @ts-ignore
20
+ file.hash = hash;
21
+ this.emit('complete', file);
22
+ }
23
+ catch (e) {
24
+ console.error(e);
25
+ this.emit('error', file, e.data.data);
26
+ }
27
+ }
28
+ }
29
+ }
30
+ //# sourceMappingURL=holochain-dropzone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"holochain-dropzone.js","sourceRoot":"","sources":["../src/holochain-dropzone.ts"],"names":[],"mappings":"AAAA,OAAO,QAA6B,MAAM,UAAU,CAAC;AAGrD,MAAM,OAAO,iBAAkB,SAAQ,QAAQ;IAE7C,YACE,EAAe,EACf,kBAAsC,EACtC,OAAwB;QAExB,OAAO,CAAC,GAAG,GAAG,wBAAwB,CAAC,CAAC,4BAA4B;QACpE,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACnB,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;IAC/C,CAAC;IAED,WAAW,CAAC,KAA8B;QACxC,IAAI,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,uBAAuB,CAC3B,aAAsC;QAEtC,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE;YAChC,IAAI;gBACF,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;gBACjD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,UAAU,CACnD,IAAI,EACJ,CAAC,WAAW,EAAE,SAAS,EAAE,EAAE;oBACzB,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,EAAE,WAAW,GAAG,GAAG,EAAE,SAAS,CAAC,CAAC;gBAClE,CAAC,CACF,CAAC;gBACF,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;gBACtC,aAAa;gBACb,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;gBACjB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;aAC7B;YAAC,OAAO,CAAC,EAAE;gBACV,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACjB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAG,CAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aAChD;SACF;IACH,CAAC;CACF","sourcesContent":["import Dropzone, { DropzoneOptions } from 'dropzone';\nimport { FileStorageService } from './services/file-storage.service';\n\nexport class HolochainDropzone extends Dropzone {\n fileStorageService: FileStorageService;\n constructor(\n el: HTMLElement,\n fileStorageService: FileStorageService,\n options: DropzoneOptions\n ) {\n options.url = 'https://holochain.org/'; // just to bypass the check.\n super(el, options);\n this.fileStorageService = fileStorageService;\n }\n\n uploadFiles(files: Dropzone.DropzoneFile[]) {\n this._uploadFilesToHolochain(files);\n }\n\n async _uploadFilesToHolochain(\n dropzoneFiles: Dropzone.DropzoneFile[]\n ): Promise<void> {\n for (const file of dropzoneFiles) {\n try {\n this.emit('sending', file, undefined, undefined);\n const hash = await this.fileStorageService.uploadFile(\n file,\n (percentatge, bytesSent) => {\n this.emit('uploadprogress', file, percentatge * 100, bytesSent);\n }\n );\n this.emit('success', file, undefined);\n // @ts-ignore\n file.hash = hash;\n this.emit('complete', file);\n } catch (e) {\n console.error(e);\n this.emit('error', file, (e as any).data.data);\n }\n }\n }\n}\n"]}
@@ -0,0 +1,5 @@
1
+ export * from './elements/upload-files';
2
+ export * from './services/file-storage.service';
3
+ export * from './holochain-dropzone';
4
+ export * from './types';
5
+ export * from './context';
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from './elements/upload-files';
2
+ export * from './services/file-storage.service';
3
+ export * from './holochain-dropzone';
4
+ export * from './types';
5
+ export * from './context';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,iCAAiC,CAAC;AAChD,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC","sourcesContent":["export * from './elements/upload-files';\nexport * from './services/file-storage.service';\nexport * from './holochain-dropzone';\nexport * from './types';\nexport * from './context';\n"]}
@@ -0,0 +1,40 @@
1
+ import type { CellClient } from '@holochain-open-dev/cell-client';
2
+ import { FileMetadata } from '../types';
3
+ export declare class FileStorageService {
4
+ protected cellClient: CellClient;
5
+ protected zomeName: string;
6
+ /**
7
+ * @param appWebsocket connection to the holochain backend
8
+ * @param cellId the cell to which to upload the file
9
+ * @param zomeName the zome name of the file_storage zome in the given cell
10
+ */
11
+ constructor(cellClient: CellClient, zomeName?: string);
12
+ /**
13
+ * Upload a file to the file_storage zome, splitting it into chunks
14
+ *
15
+ * @param file file to split and upload
16
+ * @param chunkSize chunk size to split the file, default 256 KB
17
+ */
18
+ uploadFile(file: File, onProgress?: undefined | ((percentatgeProgress: number, bytesSent: number) => void), chunkSize?: number): Promise<string>;
19
+ /**
20
+ * Downloads the whole file with the given hash
21
+ * @param fileHash
22
+ */
23
+ downloadFile(fileHash: string): Promise<File>;
24
+ /**
25
+ * Gets only the metadata of the file with the given hash
26
+ * This is specially useful if you want to fetch the chunks one by one
27
+ * @param fileHash the hash of the file
28
+ */
29
+ getFileMetadata(fileHash: string): Promise<FileMetadata>;
30
+ /**
31
+ * Fetch the chunk identified with the given hash
32
+ * This is useful if used with the chunk hashes received with `getFileMetadata`
33
+ * @param fileChunkHash
34
+ */
35
+ fetchChunk(fileChunkHash: string): Promise<Blob>;
36
+ /** Private helpers */
37
+ private _splitFile;
38
+ private _createChunk;
39
+ private _callZome;
40
+ }
@@ -0,0 +1,89 @@
1
+ export class FileStorageService {
2
+ /**
3
+ * @param appWebsocket connection to the holochain backend
4
+ * @param cellId the cell to which to upload the file
5
+ * @param zomeName the zome name of the file_storage zome in the given cell
6
+ */
7
+ constructor(cellClient, zomeName = 'file_storage') {
8
+ this.cellClient = cellClient;
9
+ this.zomeName = zomeName;
10
+ }
11
+ /**
12
+ * Upload a file to the file_storage zome, splitting it into chunks
13
+ *
14
+ * @param file file to split and upload
15
+ * @param chunkSize chunk size to split the file, default 256 KB
16
+ */
17
+ async uploadFile(file, onProgress = undefined, chunkSize = 256 * 1024) {
18
+ const blobs = this._splitFile(file, chunkSize);
19
+ const numberOfChunks = blobs.length;
20
+ const bytesPerChunk = blobs[0].size;
21
+ const chunksHashes = [];
22
+ for (let i = 0; i < blobs.length; i++) {
23
+ const chunkHash = await this._createChunk(blobs[i]);
24
+ chunksHashes.push(chunkHash);
25
+ if (onProgress) {
26
+ onProgress(((i + 1) * 1.0) / numberOfChunks, bytesPerChunk * (i + 1));
27
+ }
28
+ }
29
+ const fileToCreate = {
30
+ name: file.name,
31
+ size: file.size,
32
+ fileType: file.type,
33
+ lastModified: file.lastModified,
34
+ chunksHashes,
35
+ };
36
+ const hash = await this._callZome('create_file_metadata', fileToCreate);
37
+ return hash;
38
+ }
39
+ /**
40
+ * Downloads the whole file with the given hash
41
+ * @param fileHash
42
+ */
43
+ async downloadFile(fileHash) {
44
+ const metadata = await this.getFileMetadata(fileHash);
45
+ const fetchChunksPromises = metadata.chunksHashes.map(hash => this.fetchChunk(hash));
46
+ const chunks = await Promise.all(fetchChunksPromises);
47
+ const file = new File(chunks, metadata.name, {
48
+ lastModified: metadata.lastModifed,
49
+ type: metadata.fileType,
50
+ });
51
+ return file;
52
+ }
53
+ /**
54
+ * Gets only the metadata of the file with the given hash
55
+ * This is specially useful if you want to fetch the chunks one by one
56
+ * @param fileHash the hash of the file
57
+ */
58
+ async getFileMetadata(fileHash) {
59
+ return await this._callZome('get_file_metadata', fileHash);
60
+ }
61
+ /**
62
+ * Fetch the chunk identified with the given hash
63
+ * This is useful if used with the chunk hashes received with `getFileMetadata`
64
+ * @param fileChunkHash
65
+ */
66
+ async fetchChunk(fileChunkHash) {
67
+ const bytes = await this._callZome('get_file_chunk', fileChunkHash);
68
+ return new Blob([new Uint8Array(bytes)]);
69
+ }
70
+ /** Private helpers */
71
+ _splitFile(file, chunkSize) {
72
+ let offset = 0;
73
+ const chunks = [];
74
+ while (file.size > offset) {
75
+ const chunk = file.slice(offset, offset + chunkSize);
76
+ offset += chunkSize;
77
+ chunks.push(chunk);
78
+ }
79
+ return chunks;
80
+ }
81
+ async _createChunk(chunk) {
82
+ const bytes = await chunk.arrayBuffer();
83
+ return this._callZome('create_file_chunk', new Uint8Array(bytes));
84
+ }
85
+ _callZome(fnName, payload) {
86
+ return this.cellClient.callZome(this.zomeName, fnName, payload);
87
+ }
88
+ }
89
+ //# sourceMappingURL=file-storage.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-storage.service.js","sourceRoot":"","sources":["../../src/services/file-storage.service.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CellClient } from '@holochain-open-dev/cell-client';\nimport { FileMetadata } from '../types';\n\nexport class FileStorageService {\n /**\n * @param appWebsocket connection to the holochain backend\n * @param cellId the cell to which to upload the file\n * @param zomeName the zome name of the file_storage zome in the given cell\n */\n constructor(\n protected cellClient: CellClient,\n protected zomeName: string = 'file_storage'\n ) {}\n\n /**\n * Upload a file to the file_storage zome, splitting it into chunks\n *\n * @param file file to split and upload\n * @param chunkSize chunk size to split the file, default 256 KB\n */\n async uploadFile(\n file: File,\n onProgress:\n | undefined\n | ((percentatgeProgress: number, bytesSent: number) => void) = undefined,\n chunkSize: number = 256 * 1024\n ): Promise<string> {\n const blobs = this._splitFile(file, chunkSize);\n const numberOfChunks = blobs.length;\n const bytesPerChunk = blobs[0].size;\n\n const chunksHashes: Array<string> = [];\n for (let i = 0; i < blobs.length; i++) {\n const chunkHash = await this._createChunk(blobs[i]);\n chunksHashes.push(chunkHash);\n if (onProgress) {\n onProgress(((i + 1) * 1.0) / numberOfChunks, bytesPerChunk * (i + 1));\n }\n }\n\n const fileToCreate = {\n name: file.name,\n size: file.size,\n fileType: file.type,\n lastModified: file.lastModified,\n chunksHashes,\n };\n const hash = await this._callZome('create_file_metadata', fileToCreate);\n\n return hash;\n }\n\n /**\n * Downloads the whole file with the given hash\n * @param fileHash\n */\n async downloadFile(fileHash: string): Promise<File> {\n const metadata = await this.getFileMetadata(fileHash);\n\n const fetchChunksPromises = metadata.chunksHashes.map(hash =>\n this.fetchChunk(hash)\n );\n\n const chunks = await Promise.all(fetchChunksPromises);\n\n const file = new File(chunks, metadata.name, {\n lastModified: metadata.lastModifed,\n type: metadata.fileType,\n });\n\n return file;\n }\n\n /**\n * Gets only the metadata of the file with the given hash\n * This is specially useful if you want to fetch the chunks one by one\n * @param fileHash the hash of the file\n */\n async getFileMetadata(fileHash: string): Promise<FileMetadata> {\n return await this._callZome('get_file_metadata', fileHash);\n }\n\n /**\n * Fetch the chunk identified with the given hash\n * This is useful if used with the chunk hashes received with `getFileMetadata`\n * @param fileChunkHash\n */\n async fetchChunk(fileChunkHash: string): Promise<Blob> {\n const bytes = await this._callZome('get_file_chunk', fileChunkHash);\n\n return new Blob([new Uint8Array(bytes)]);\n }\n\n /** Private helpers */\n\n private _splitFile(file: File, chunkSize: number): Blob[] {\n let offset = 0;\n const chunks: Blob[] = [];\n\n while (file.size > offset) {\n const chunk = file.slice(offset, offset + chunkSize);\n offset += chunkSize;\n chunks.push(chunk);\n }\n\n return chunks;\n }\n\n private async _createChunk(chunk: Blob): Promise<string> {\n const bytes = await chunk.arrayBuffer();\n\n return this._callZome('create_file_chunk', new Uint8Array(bytes));\n }\n\n private _callZome(fnName: string, payload: any): Promise<any> {\n return this.cellClient.callZome(this.zomeName, fnName, payload);\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare const sharedStyles: import("lit").CSSResult;
@@ -0,0 +1,16 @@
1
+ import { css } from 'lit';
2
+ export const sharedStyles = css `
3
+ .column {
4
+ display: flex;
5
+ flex-direction: column;
6
+ }
7
+ .row {
8
+ display: flex;
9
+ flex-direction: row;
10
+ }
11
+ .center-content {
12
+ align-items: center;
13
+ justify-content: center;
14
+ }
15
+ `;
16
+ //# sourceMappingURL=sharedStyles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharedStyles.js","sourceRoot":"","sources":["../src/sharedStyles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;;;;;;;;;;;;;CAa9B,CAAC","sourcesContent":["import { css } from 'lit';\n\nexport const sharedStyles = css`\n .column {\n display: flex;\n flex-direction: column;\n }\n .row {\n display: flex;\n flex-direction: row;\n }\n .center-content {\n align-items: center;\n justify-content: center;\n }\n`;\n"]}
@@ -0,0 +1,8 @@
1
+ export interface FileMetadata {
2
+ name: string;
3
+ lastModifed: number;
4
+ size: number;
5
+ fileType: string;
6
+ creatorPubKey: string;
7
+ chunksHashes: Array<string>;
8
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export interface FileMetadata {\n name: string;\n lastModifed: number;\n size: number;\n fileType: string;\n creatorPubKey: string;\n chunksHashes: Array<string>;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@holochain-open-dev/file-storage",
3
+ "version": "0.0.1",
4
+ "description": "File storage utilities to store files in holochain DHT",
5
+ "author": "guillem.cordoba@gmail.com",
6
+ "license": "MIT",
7
+ "main": "dist/index.js",
8
+ "module": "dist/index.js",
9
+ "exports": {
10
+ ".": "./dist/index.js",
11
+ "./upload-files": "./dist/definitions/upload-files.js"
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+
17
+ "scripts": {
18
+ "start": "npm run build && concurrently -k --names tsc,dev-server \"npm run build-watch\" \"web-dev-server --config demo/web-dev-server.config.mjs\"",
19
+ "build": "tsc",
20
+ "build-watch": "tsc -w --preserveWatchOutput",
21
+ "test": "npm run build && web-test-runner --coverage --puppeteer",
22
+ "test-debug": "npm run build && DEBUG=true web-test-runner --coverage --puppeteer",
23
+ "test-watch": "web-test-runner --watch --puppeteer",
24
+ "e2e": "CONDUCTOR_URL=ws://localhost:8888 concurrently -k -s first \"npm:test\" \"npm:start-holochain\"",
25
+ "analyze": "wca analyze src --format json --outFile custom-elements.json",
26
+ "lint": "eslint --ext .ts,.html . --ignore-path .gitignore",
27
+ "format": "eslint --ext .ts,.html . --fix --ignore-path .gitignore"
28
+ },
29
+ "dependencies": {
30
+ "@holochain/client": "^0.3.2",
31
+ "@holochain-open-dev/cell-client": "^0.3.2",
32
+ "@holochain-open-dev/context": "^0.0.3",
33
+ "@open-wc/scoped-elements": "^2.0.1",
34
+ "@scoped-elements/dropzone": "^0.0.3",
35
+ "@scoped-elements/material-web": "^0.0.19",
36
+ "lit": "^2.2.0"
37
+ },
38
+ "devDependencies": {
39
+ "@open-wc/eslint-config": "^2.1.0",
40
+ "@open-wc/testing": "^3.0.0-next.5",
41
+ "@open-wc/testing-karma": "^4.0.9",
42
+ "@rollup/plugin-commonjs": "^15.1.0",
43
+ "@rollup/plugin-node-resolve": "^9.0.0",
44
+ "@rollup/plugin-replace": "^2.4.2",
45
+ "@rollup/plugin-typescript": "^8.2.1",
46
+ "@types/dropzone": "^5.7.0",
47
+ "@types/node": "13.11.1",
48
+ "@typescript-eslint/eslint-plugin": "^2.34.0",
49
+ "@typescript-eslint/parser": "^2.34.0",
50
+ "@web/dev-server": "0.0.13",
51
+ "@web/dev-server-rollup": "^0.2.9",
52
+ "@web/test-runner": "^0.7.41",
53
+ "@web/test-runner-puppeteer": "^0.6.4",
54
+ "buffer": "^5.6.0",
55
+ "concurrently": "^5.1.0",
56
+ "deepmerge": "^3.2.0",
57
+ "es-dev-server": "^1.23.0",
58
+ "eslint": "^6.8.0",
59
+ "eslint-config-prettier": "^6.15.0",
60
+ "gh-pages": "^3.1.0",
61
+ "husky": "^1.0.0",
62
+ "lint-staged": "^10.0.0",
63
+ "prettier": "^2.0.4",
64
+ "rimraf": "^3.0.2",
65
+ "rollup": "^2.32.0",
66
+ "rollup-plugin-node-builtins": "^2.1.2",
67
+ "rollup-plugin-node-globals": "^1.4.0",
68
+ "rollup-plugin-postcss": "^3.1.8",
69
+ "rollup-plugin-postcss-lit": "^1.0.1",
70
+ "tslib": "^1.11.0",
71
+ "typescript": "~4.2.4",
72
+ "web-component-analyzer": "^1.1.6"
73
+ },
74
+ "eslintConfig": {
75
+ "extends": [
76
+ "@open-wc/eslint-config",
77
+ "eslint-config-prettier"
78
+ ]
79
+ },
80
+ "prettier": {
81
+ "singleQuote": true,
82
+ "arrowParens": "avoid"
83
+ },
84
+ "publishConfig": {
85
+ "access": "public"
86
+ },
87
+ "customElements": "custom-elements.json",
88
+ "type": "module"
89
+ }