├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── .nojekyll ├── README.md ├── classes │ ├── BlockHasher.md │ ├── ChunkSplitter.md │ ├── MerkleRootCalculator.md │ ├── MerkleTreeBalancer.md │ ├── PieceHasher.md │ └── Tokenizer.md ├── enums │ ├── CommonPieceLength.md │ ├── TokenType.md │ └── TorrentType.md ├── interfaces │ ├── BObjectLoose.md │ ├── BObjectStrict.md │ ├── ByteStringToken.md │ ├── DictionaryEndToken.md │ ├── DictionaryStartToken.md │ ├── FilePropsBase.md │ ├── FilePropsV1.md │ ├── FilePropsV2.md │ ├── FileTreeFileNode.md │ ├── InfoBase.md │ ├── InfoBaseV1.md │ ├── InfoMultiFileHybrid.md │ ├── InfoMultiFileV1.md │ ├── InfoSingleFileHybrid.md │ ├── InfoSingleFileV1.md │ ├── InfoV2.md │ ├── IntegerToken.md │ ├── ListEndToken.md │ ├── ListStartToken.md │ ├── MetaInfoBase.md │ ├── MetaInfoHybrid.md │ ├── MetaInfoV1.md │ ├── MetaInfoV2.md │ ├── TorrentOptionsBase.md │ ├── TorrentOptionsHybrid.md │ ├── TorrentOptionsV1.md │ └── TorrentOptionsV2.md └── modules.md ├── package.json ├── src ├── create.ts ├── decode.ts ├── encode.ts ├── index.ts ├── transformers │ ├── blockHasher.ts │ ├── chunkSplitter.ts │ ├── index.ts │ ├── merkleRootCalculator.ts │ ├── merkleTreeBalancer.ts │ ├── pieceHasher.ts │ └── tokenizer.ts └── utils │ ├── codec.ts │ ├── fileDirLike.ts │ ├── fileTree.ts │ ├── index.ts │ └── misc.ts ├── tsconfig.json └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "project": ["./tsconfig.json"] 5 | }, 6 | "plugins": ["@typescript-eslint"], 7 | "root": true, 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:@typescript-eslint/recommended-requiring-type-checking" 12 | ], 13 | "ignorePatterns": ["dist/**/*", "node_modules/**/*", "webpack.config.js"] 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | dist 4 | package-lock.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ze-Zheng Wu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

torrefy

4 |

5 | GitHub top language npm version npm downloads GitHub search hit counter Rate this package 6 |

7 |

8 | An ESM package that uses Web Streams API to create v1, v2 or hybrid torrents in your web browser. 9 |

10 |

11 | 🏗This package is under active development.🏗 12 |

13 |
14 | 15 | ## Install 16 | 17 | ```bash 18 | npm i torrefy # or yarn add torrefy 19 | ``` 20 | 21 | ## Basic usage 22 | 23 | ```ts 24 | import { create, encode, decode } from "torrefy"; 25 | 26 | // create a test file 27 | const testFile = new File( 28 | ["Hello world. This is the test file content."], 29 | "testfile.txt" 30 | ); 31 | 32 | // calculate (hash) the meta info of the test file 33 | const metaInfo = await create([testFile]); 34 | 35 | // bencode meta info into a readable stream 36 | const torrentStream = encode(metaInfo); 37 | 38 | // tee the readable stream into two readable streams 39 | const [torrentStream1, torrentStream2] = torrentStream.tee(); 40 | 41 | // consume the first readable stream as an array buffer 42 | const torrentBinary = await new Response(torrentStream1).arrayBuffer(); 43 | 44 | // decode the second readable stream into meta info 45 | const decodedMetaInfo = await decode(torrentStream2); 46 | ``` 47 | 48 | ## Features 49 | 50 | ### Supports Creating V1, V2 or Hybrid Torrents 51 | 52 | This package supports creating [v1](http://bittorrent.org/beps/bep_0003.html), [v2](https://www.bittorrent.org/beps/bep_0052.html) ([introduction blog](https://blog.libtorrent.org/2020/09/bittorrent-v2/)) or [hybrid](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path) ([introduction blog](https://blog.libtorrent.org/2020/09/bittorrent-v2/#:~:text=for%20backwards%20compatibility.-,backwards%20compatibility,-All%20new%20features)) torrents. 53 | 54 | ### Covers Various Web File APIs 55 | 56 | This package can handle input files or directories acquired from [File API](https://developer.mozilla.org/docs/Web/API/File), [File and Directory Entries API](https://developer.mozilla.org/docs/Web/API/File_and_Directory_Entries_API) or [File System Access API](https://developer.mozilla.org/docs/Web/API/File_System_Access_API). 57 | 58 | ### Supports Comprehensive Options 59 | 60 | TBD 61 | 62 | ### Supports Handling Progress 63 | 64 | TBD 65 | 66 | ### Exposes Stream-Based APIs 67 | 68 | The `create` function consumes an [iterable](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol) of input files as [`ReadableStream`](https://developer.mozilla.org/docs/Web/API/ReadableStream)s with options and populates a `MetaInfo` object. This function internally uses several [`TransformStream`](https://developer.mozilla.org/docs/Web/API/TransformStream)s to chop the files into pieces and hash them. 69 | 70 | The `encode` function consumes any bcodec friendly entity (e.g. `MetaInfo` object) and [bencode](http://bittorrent.org/beps/bep_0003.html#bencoding)s it into a `ReadableStream`. 71 | 72 | The `decode` function consumes any bcodec friendly `ReadableStream` (e.g. torrent `ReadableStream`) and bdecodes it into the corresponding entity. This function internally uses a `TransformStream` called `Tokenizer` to tokenize the input `ReadableStream` and then calls `parse` function to parse the `Tokens`. 73 | 74 | All `TransformStream`s used in this package are also exported. 75 | 76 | ### Supports a Comprehensive Set of Bcodec Friendly Javascript Types 77 | 78 | Bcodec friendly Javascript types includes (for the time being): 79 | 80 | | Bcodec Type \ Javascript Type | `Strict` | `Loose` | 81 | | :---------------------------: | :---------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------: | 82 | | `ByteString` | [`string`](https://developer.mozilla.org/docs/Glossary/String) | `string` [`ArrayBuffer`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) | 83 | | `Integer` | [`number`](https://developer.mozilla.org/docs/Glossary/Number) [`bigint`](https://developer.mozilla.org/docs/Glossary/BigInt) | `number` `bigint` [`boolean`](https://developer.mozilla.org/docs/Glossary/Boolean) | 84 | | `List` | [`Strict[]`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array) | `Loose[]` | 85 | | `Dictionary` | [`{[key: string]: Strict}`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object) | ` {[key: string]: Loose}`
[`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map) | 86 | | ignored | - | `undefined` `null` | 87 | 88 | `encode` function supports all `Loose` type inputs and `decode` function always returns `Strict` type results. 89 | 90 | ### Supports Hooks in Bencoding 91 | 92 | You can register encoder hooks when using the `encode` function. A common use case is extracting the bencoded `info` dictionary and calculating the [`infohash`](http://bittorrent.org/beps/bep_0052.html#infohash). (This package doesn't provide an out-of-box function to calculate `infohash` for now) 93 | 94 | To use encoder hooks, you will have to install the peer dependency [`@sec-ant/trie-map`](https://www.npmjs.com/package/@sec-ant/trie-map), which acts as an encoder hook system and allows you to register encoder hooks with iterable paths as keys in. Refer to its [README](https://github.com/Sec-ant/trie-map) to learn more about the package. 95 | 96 | This package provides several helper functions to help you register hooks in a hook system and consume their results as you please: `useUint8ArrayStreamHook`, `useArrayBufferPromiseHook`, `useTextPromiseHook`. You can also define your own functions to register and use hooks. 97 | 98 | Here is probably how you should use this feature: 99 | 100 | ```ts 101 | import { encode, EncoderHookSystem, useArrayBufferPromiseHook } from "torrefy"; 102 | import { TrieMap } from "@sec-ant/trie-map"; 103 | 104 | // create a dummy object to encode 105 | const dummyObject = { 106 | a: "b", 107 | c: 1, 108 | info: { 109 | foo: "bar", 110 | }, 111 | s: ["t"], 112 | }; 113 | 114 | // initialize an encoder hook system 115 | const hookSystem: EncoderHookSystem = new TrieMap(); 116 | 117 | // register an encoder hook under dummyObject.info path in the hook system 118 | // and consume the result as an array buffer promise 119 | const infoArrayBufferPromise = useArrayBufferPromiseHook(["info"], hookSystem); 120 | 121 | // pass the hook system as an input argument to the encode function 122 | const bencodedReadableStream = encode(dummyObject, hookSystem); 123 | 124 | // consume the result of the hook 125 | const infoArrayBuffer = await infoArrayBufferPromise; // => ArrayBuffer(12) 126 | ``` 127 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | torrefy / [Exports](modules.md) 2 | 3 |
4 | 5 |

torrefy

6 |

7 | GitHub top language npm version npm downloads GitHub search hit counter Rate this package 8 |

9 |

10 | An ESM package that uses Web Streams API to create v1, v2 or hybrid torrents in your web browser. 11 |

12 |

13 | 🏗This package is under active development.🏗 14 |

15 |
16 | 17 | ## Install 18 | 19 | ```bash 20 | npm i torrefy # or yarn add torrefy 21 | ``` 22 | 23 | ## Basic usage 24 | 25 | ```ts 26 | import { create, encode, decode } from "torrefy"; 27 | 28 | // create a test file 29 | const testFile = new File( 30 | ["Hello world. This is the test file content."], 31 | "testfile.txt" 32 | ); 33 | 34 | // calculate (hash) the meta info of the test file 35 | const metaInfo = await create([testFile]); 36 | 37 | // bencode meta info into a readable stream 38 | const torrentStream = encode(metaInfo); 39 | 40 | // tee the readable stream into two readable streams 41 | const [torrentStream1, torrentStream2] = torrentStream.tee(); 42 | 43 | // consume the first readable stream as an array buffer 44 | const torrentBinary = await new Response(torrentStream1).arrayBuffer(); 45 | 46 | // decode the second readable stream into meta info 47 | const decodedMetaInfo = await decode(torrentStream2); 48 | ``` 49 | 50 | ## Features 51 | 52 | ### Supports Creating V1, V2 or Hybrid Torrents 53 | 54 | This package supports creating [v1](http://bittorrent.org/beps/bep_0003.html), [v2](https://www.bittorrent.org/beps/bep_0052.html) ([introduction blog](https://blog.libtorrent.org/2020/09/bittorrent-v2/)) or [hybrid](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path) ([introduction blog](https://blog.libtorrent.org/2020/09/bittorrent-v2/#:~:text=for%20backwards%20compatibility.-,backwards%20compatibility,-All%20new%20features)) torrents. 55 | 56 | ### Covers Various Web File APIs 57 | 58 | This package can handle input files or directories acquired from [File API](https://developer.mozilla.org/docs/Web/API/File), [File and Directory Entries API](https://developer.mozilla.org/docs/Web/API/File_and_Directory_Entries_API) or [File System Access API](https://developer.mozilla.org/docs/Web/API/File_System_Access_API). 59 | 60 | ### Supports Comprehensive Options 61 | 62 | TBD 63 | 64 | ### Supports Handling Progress 65 | 66 | TBD 67 | 68 | ### Exposes Stream-Based APIs 69 | 70 | The `create` function consumes an [iterable](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol) of input files as [`ReadableStream`](https://developer.mozilla.org/docs/Web/API/ReadableStream)s with options and populates a `MetaInfo` object. This function internally uses several [`TransformStream`](https://developer.mozilla.org/docs/Web/API/TransformStream)s to chop the files into pieces and hash them. 71 | 72 | The `encode` function consumes any bcodec friendly entity (e.g. `MetaInfo` object) and [bencode](http://bittorrent.org/beps/bep_0003.html#bencoding)s it into a `ReadableStream`. 73 | 74 | The `decode` function consumes any bcodec friendly `ReadableStream` (e.g. torrent `ReadableStream`) and bdecodes it into the corresponding entity. This function internally uses a `TransformStream` called `Tokenizer` to tokenize the input `ReadableStream` and then calls `parse` function to parse the `Tokens`. 75 | 76 | All `TransformStream`s used in this package are also exported. 77 | 78 | ### Supports a Comprehensive Set of Bcodec Friendly Javascript Types 79 | 80 | Bcodec friendly Javascript types includes (for the time being): 81 | 82 | | Bcodec Type \ Javascript Type | `Strict` | `Loose` | 83 | | :---------------------------: | :---------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------: | 84 | | `ByteString` | [`string`](https://developer.mozilla.org/docs/Glossary/String) | `string` [`ArrayBuffer`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) | 85 | | `Integer` | [`number`](https://developer.mozilla.org/docs/Glossary/Number) [`bigint`](https://developer.mozilla.org/docs/Glossary/BigInt) | `number` `bigint` [`boolean`](https://developer.mozilla.org/docs/Glossary/Boolean) | 86 | | `List` | [`Strict[]`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array) | `Loose[]` | 87 | | `Dictionary` | [`{[key: string]: Strict}`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object) | ` {[key: string]: Loose}`
[`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map) | 88 | | ignored | - | `undefined` `null` | 89 | 90 | `encode` function supports all `Loose` type inputs and `decode` function always returns `Strict` type results. 91 | 92 | ### Supports Hooks in Bencoding 93 | 94 | You can register encoder hooks when using the `encode` function. A common use case is extracting the bencoded `info` dictionary and calculating the [`infohash`](http://bittorrent.org/beps/bep_0052.html#infohash). (This package doesn't provide an out-of-box function to calculate `infohash` for now) 95 | 96 | To use encoder hooks, you will have to install the peer dependency [`@sec-ant/trie-map`](https://www.npmjs.com/package/@sec-ant/trie-map), which acts as an encoder hook system and allows you to register encoder hooks with iterable paths as keys in. Refer to its [README](https://github.com/Sec-ant/trie-map) to learn more about the package. 97 | 98 | This package provides several helper functions to help you register hooks in a hook system and consume their results as you please: `useUint8ArrayStreamHook`, `useArrayBufferPromiseHook`, `useTextPromiseHook`. You can also define your own functions to register and use hooks. 99 | 100 | Here is probably how you should use this feature: 101 | 102 | ```ts 103 | import { encode, EncoderHookSystem, useArrayBufferPromiseHook } from "torrefy"; 104 | import { TrieMap } from "@sec-ant/trie-map"; 105 | 106 | // create a dummy object to encode 107 | const dummyObject = { 108 | a: "b", 109 | c: 1, 110 | info: { 111 | foo: "bar", 112 | }, 113 | s: ["t"], 114 | }; 115 | 116 | // initialize an encoder hook system 117 | const hookSystem: EncoderHookSystem = new TrieMap(); 118 | 119 | // register an encoder hook under dummyObject.info path in the hook system 120 | // and consume the result as an array buffer promise 121 | const infoArrayBufferPromise = useArrayBufferPromiseHook(["info"], hookSystem); 122 | 123 | // pass the hook system as an input argument to the encode function 124 | const bencodedReadableStream = encode(dummyObject, hookSystem); 125 | 126 | // consume the result of the hook 127 | const infoArrayBuffer = await infoArrayBufferPromise; // => ArrayBuffer(12) 128 | ``` 129 | -------------------------------------------------------------------------------- /docs/classes/BlockHasher.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / BlockHasher 2 | 3 | # Class: BlockHasher 4 | 5 | ## Hierarchy 6 | 7 | - `TransformStream`<`Uint8Array`, `Uint8Array`[]\> 8 | 9 | ↳ **`BlockHasher`** 10 | 11 | ## Table of contents 12 | 13 | ### Constructors 14 | 15 | - [constructor](BlockHasher.md#constructor) 16 | 17 | ### Properties 18 | 19 | - [readable](BlockHasher.md#readable) 20 | - [writable](BlockHasher.md#writable) 21 | 22 | ## Constructors 23 | 24 | ### constructor 25 | 26 | • **new BlockHasher**(`blocksPerPiece`) 27 | 28 | #### Parameters 29 | 30 | | Name | Type | 31 | | :------ | :------ | 32 | | `blocksPerPiece` | `number` | 33 | 34 | #### Overrides 35 | 36 | TransformStream<Uint8Array, Uint8Array[]\>.constructor 37 | 38 | #### Defined in 39 | 40 | [src/transformers/blockHasher.ts:67](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/blockHasher.ts#L67) 41 | 42 | ## Properties 43 | 44 | ### readable 45 | 46 | • `Readonly` **readable**: `ReadableStream`<`Uint8Array`[]\> 47 | 48 | #### Inherited from 49 | 50 | TransformStream.readable 51 | 52 | #### Defined in 53 | 54 | node_modules/typescript/lib/lib.dom.d.ts:14430 55 | 56 | ___ 57 | 58 | ### writable 59 | 60 | • `Readonly` **writable**: `WritableStream`<`Uint8Array`\> 61 | 62 | #### Inherited from 63 | 64 | TransformStream.writable 65 | 66 | #### Defined in 67 | 68 | node_modules/typescript/lib/lib.dom.d.ts:14431 69 | -------------------------------------------------------------------------------- /docs/classes/ChunkSplitter.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / ChunkSplitter 2 | 3 | # Class: ChunkSplitter 4 | 5 | ## Hierarchy 6 | 7 | - `TransformStream`<`Uint8Array`, `Uint8Array`\> 8 | 9 | ↳ **`ChunkSplitter`** 10 | 11 | ## Table of contents 12 | 13 | ### Constructors 14 | 15 | - [constructor](ChunkSplitter.md#constructor) 16 | 17 | ### Properties 18 | 19 | - [readable](ChunkSplitter.md#readable) 20 | - [writable](ChunkSplitter.md#writable) 21 | 22 | ## Constructors 23 | 24 | ### constructor 25 | 26 | • **new ChunkSplitter**(`chunkLength`, `opts?`) 27 | 28 | #### Parameters 29 | 30 | | Name | Type | Default value | 31 | | :------ | :------ | :------ | 32 | | `chunkLength` | `number` | `undefined` | 33 | | `opts` | `Object` | `undefined` | 34 | | `opts.padding` | `boolean` | `false` | 35 | 36 | #### Overrides 37 | 38 | TransformStream<Uint8Array, Uint8Array\>.constructor 39 | 40 | #### Defined in 41 | 42 | [src/transformers/chunkSplitter.ts:45](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/chunkSplitter.ts#L45) 43 | 44 | ## Properties 45 | 46 | ### readable 47 | 48 | • `Readonly` **readable**: `ReadableStream`<`Uint8Array`\> 49 | 50 | #### Inherited from 51 | 52 | TransformStream.readable 53 | 54 | #### Defined in 55 | 56 | node_modules/typescript/lib/lib.dom.d.ts:14430 57 | 58 | ___ 59 | 60 | ### writable 61 | 62 | • `Readonly` **writable**: `WritableStream`<`Uint8Array`\> 63 | 64 | #### Inherited from 65 | 66 | TransformStream.writable 67 | 68 | #### Defined in 69 | 70 | node_modules/typescript/lib/lib.dom.d.ts:14431 71 | -------------------------------------------------------------------------------- /docs/classes/MerkleRootCalculator.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / MerkleRootCalculator 2 | 3 | # Class: MerkleRootCalculator 4 | 5 | ## Hierarchy 6 | 7 | - `TransformStream`<`Uint8Array`[], `Uint8Array`\> 8 | 9 | ↳ **`MerkleRootCalculator`** 10 | 11 | ## Table of contents 12 | 13 | ### Constructors 14 | 15 | - [constructor](MerkleRootCalculator.md#constructor) 16 | 17 | ### Properties 18 | 19 | - [readable](MerkleRootCalculator.md#readable) 20 | - [writable](MerkleRootCalculator.md#writable) 21 | 22 | ## Constructors 23 | 24 | ### constructor 25 | 26 | • **new MerkleRootCalculator**(`updateProgress?`) 27 | 28 | #### Parameters 29 | 30 | | Name | Type | 31 | | :------ | :------ | 32 | | `updateProgress?` | [`UpdateProgress`](../modules.md#updateprogress) | 33 | 34 | #### Overrides 35 | 36 | TransformStream< 37 | Uint8Array[], 38 | Uint8Array 39 | \>.constructor 40 | 41 | #### Defined in 42 | 43 | [src/transformers/merkleRootCalculator.ts:28](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/merkleRootCalculator.ts#L28) 44 | 45 | ## Properties 46 | 47 | ### readable 48 | 49 | • `Readonly` **readable**: `ReadableStream`<`Uint8Array`\> 50 | 51 | #### Inherited from 52 | 53 | TransformStream.readable 54 | 55 | #### Defined in 56 | 57 | node_modules/typescript/lib/lib.dom.d.ts:14430 58 | 59 | ___ 60 | 61 | ### writable 62 | 63 | • `Readonly` **writable**: `WritableStream`<`Uint8Array`[]\> 64 | 65 | #### Inherited from 66 | 67 | TransformStream.writable 68 | 69 | #### Defined in 70 | 71 | node_modules/typescript/lib/lib.dom.d.ts:14431 72 | -------------------------------------------------------------------------------- /docs/classes/MerkleTreeBalancer.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / MerkleTreeBalancer 2 | 3 | # Class: MerkleTreeBalancer 4 | 5 | ## Hierarchy 6 | 7 | - `TransformStream`<`Uint8Array`, `Uint8Array`[]\> 8 | 9 | ↳ **`MerkleTreeBalancer`** 10 | 11 | ## Table of contents 12 | 13 | ### Constructors 14 | 15 | - [constructor](MerkleTreeBalancer.md#constructor) 16 | 17 | ### Properties 18 | 19 | - [readable](MerkleTreeBalancer.md#readable) 20 | - [writable](MerkleTreeBalancer.md#writable) 21 | 22 | ## Constructors 23 | 24 | ### constructor 25 | 26 | • **new MerkleTreeBalancer**(`blocksPerPiece`) 27 | 28 | #### Parameters 29 | 30 | | Name | Type | 31 | | :------ | :------ | 32 | | `blocksPerPiece` | `number` | 33 | 34 | #### Overrides 35 | 36 | TransformStream< 37 | Uint8Array, 38 | Uint8Array[] 39 | \>.constructor 40 | 41 | #### Defined in 42 | 43 | [src/transformers/merkleTreeBalancer.ts:40](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/merkleTreeBalancer.ts#L40) 44 | 45 | ## Properties 46 | 47 | ### readable 48 | 49 | • `Readonly` **readable**: `ReadableStream`<`Uint8Array`[]\> 50 | 51 | #### Inherited from 52 | 53 | TransformStream.readable 54 | 55 | #### Defined in 56 | 57 | node_modules/typescript/lib/lib.dom.d.ts:14430 58 | 59 | ___ 60 | 61 | ### writable 62 | 63 | • `Readonly` **writable**: `WritableStream`<`Uint8Array`\> 64 | 65 | #### Inherited from 66 | 67 | TransformStream.writable 68 | 69 | #### Defined in 70 | 71 | node_modules/typescript/lib/lib.dom.d.ts:14431 72 | -------------------------------------------------------------------------------- /docs/classes/PieceHasher.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / PieceHasher 2 | 3 | # Class: PieceHasher 4 | 5 | ## Hierarchy 6 | 7 | - `TransformStream`<`Uint8Array`, `Uint8Array`\> 8 | 9 | ↳ **`PieceHasher`** 10 | 11 | ## Table of contents 12 | 13 | ### Constructors 14 | 15 | - [constructor](PieceHasher.md#constructor) 16 | 17 | ### Properties 18 | 19 | - [readable](PieceHasher.md#readable) 20 | - [writable](PieceHasher.md#writable) 21 | 22 | ## Constructors 23 | 24 | ### constructor 25 | 26 | • **new PieceHasher**(`updateProgress?`) 27 | 28 | #### Parameters 29 | 30 | | Name | Type | 31 | | :------ | :------ | 32 | | `updateProgress?` | [`UpdateProgress`](../modules.md#updateprogress) | 33 | 34 | #### Overrides 35 | 36 | TransformStream<Uint8Array, Uint8Array\>.constructor 37 | 38 | #### Defined in 39 | 40 | [src/transformers/pieceHasher.ts:32](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/pieceHasher.ts#L32) 41 | 42 | ## Properties 43 | 44 | ### readable 45 | 46 | • `Readonly` **readable**: `ReadableStream`<`Uint8Array`\> 47 | 48 | #### Inherited from 49 | 50 | TransformStream.readable 51 | 52 | #### Defined in 53 | 54 | node_modules/typescript/lib/lib.dom.d.ts:14430 55 | 56 | ___ 57 | 58 | ### writable 59 | 60 | • `Readonly` **writable**: `WritableStream`<`Uint8Array`\> 61 | 62 | #### Inherited from 63 | 64 | TransformStream.writable 65 | 66 | #### Defined in 67 | 68 | node_modules/typescript/lib/lib.dom.d.ts:14431 69 | -------------------------------------------------------------------------------- /docs/classes/Tokenizer.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / Tokenizer 2 | 3 | # Class: Tokenizer 4 | 5 | ## Hierarchy 6 | 7 | - `TransformStream`<`Uint8Array`, [`Token`](../modules.md#token)\> 8 | 9 | ↳ **`Tokenizer`** 10 | 11 | ## Table of contents 12 | 13 | ### Constructors 14 | 15 | - [constructor](Tokenizer.md#constructor) 16 | 17 | ### Properties 18 | 19 | - [readable](Tokenizer.md#readable) 20 | - [writable](Tokenizer.md#writable) 21 | 22 | ## Constructors 23 | 24 | ### constructor 25 | 26 | • **new Tokenizer**() 27 | 28 | #### Overrides 29 | 30 | TransformStream<Uint8Array, Token\>.constructor 31 | 32 | #### Defined in 33 | 34 | [src/transformers/tokenizer.ts:248](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L248) 35 | 36 | ## Properties 37 | 38 | ### readable 39 | 40 | • `Readonly` **readable**: `ReadableStream`<[`IntegerToken`](../interfaces/IntegerToken.md) \| [`ByteStringToken`](../interfaces/ByteStringToken.md) \| [`ListStartToken`](../interfaces/ListStartToken.md) \| [`ListEndToken`](../interfaces/ListEndToken.md) \| [`DictionaryStartToken`](../interfaces/DictionaryStartToken.md) \| [`DictionaryEndToken`](../interfaces/DictionaryEndToken.md)\> 41 | 42 | #### Inherited from 43 | 44 | TransformStream.readable 45 | 46 | #### Defined in 47 | 48 | node_modules/typescript/lib/lib.dom.d.ts:14430 49 | 50 | ___ 51 | 52 | ### writable 53 | 54 | • `Readonly` **writable**: `WritableStream`<`Uint8Array`\> 55 | 56 | #### Inherited from 57 | 58 | TransformStream.writable 59 | 60 | #### Defined in 61 | 62 | node_modules/typescript/lib/lib.dom.d.ts:14431 63 | -------------------------------------------------------------------------------- /docs/enums/CommonPieceLength.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / CommonPieceLength 2 | 3 | # Enumeration: CommonPieceLength 4 | 5 | common piece lengths 6 | 7 | ## Table of contents 8 | 9 | ### Enumeration Members 10 | 11 | - [128KB](CommonPieceLength.md#128kb) 12 | - [16KB](CommonPieceLength.md#16kb) 13 | - [16MB](CommonPieceLength.md#16mb) 14 | - [1MB](CommonPieceLength.md#1mb) 15 | - [256KB](CommonPieceLength.md#256kb) 16 | - [2MB](CommonPieceLength.md#2mb) 17 | - [32KB](CommonPieceLength.md#32kb) 18 | - [32MB](CommonPieceLength.md#32mb) 19 | - [4MB](CommonPieceLength.md#4mb) 20 | - [512KB](CommonPieceLength.md#512kb) 21 | - [64KB](CommonPieceLength.md#64kb) 22 | - [8MB](CommonPieceLength.md#8mb) 23 | 24 | ## Enumeration Members 25 | 26 | ### 128KB 27 | 28 | • **128KB** = ``131072`` 29 | 30 | #### Defined in 31 | 32 | [src/create.ts:410](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L410) 33 | 34 | ___ 35 | 36 | ### 16KB 37 | 38 | • **16KB** = ``16384`` 39 | 40 | #### Defined in 41 | 42 | [src/create.ts:407](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L407) 43 | 44 | ___ 45 | 46 | ### 16MB 47 | 48 | • **16MB** = ``16777216`` 49 | 50 | #### Defined in 51 | 52 | [src/create.ts:417](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L417) 53 | 54 | ___ 55 | 56 | ### 1MB 57 | 58 | • **1MB** = ``1048576`` 59 | 60 | #### Defined in 61 | 62 | [src/create.ts:413](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L413) 63 | 64 | ___ 65 | 66 | ### 256KB 67 | 68 | • **256KB** = ``262144`` 69 | 70 | #### Defined in 71 | 72 | [src/create.ts:411](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L411) 73 | 74 | ___ 75 | 76 | ### 2MB 77 | 78 | • **2MB** = ``2097152`` 79 | 80 | #### Defined in 81 | 82 | [src/create.ts:414](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L414) 83 | 84 | ___ 85 | 86 | ### 32KB 87 | 88 | • **32KB** = ``32768`` 89 | 90 | #### Defined in 91 | 92 | [src/create.ts:408](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L408) 93 | 94 | ___ 95 | 96 | ### 32MB 97 | 98 | • **32MB** = ``33554432`` 99 | 100 | #### Defined in 101 | 102 | [src/create.ts:418](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L418) 103 | 104 | ___ 105 | 106 | ### 4MB 107 | 108 | • **4MB** = ``4194304`` 109 | 110 | #### Defined in 111 | 112 | [src/create.ts:415](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L415) 113 | 114 | ___ 115 | 116 | ### 512KB 117 | 118 | • **512KB** = ``524288`` 119 | 120 | #### Defined in 121 | 122 | [src/create.ts:412](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L412) 123 | 124 | ___ 125 | 126 | ### 64KB 127 | 128 | • **64KB** = ``65536`` 129 | 130 | #### Defined in 131 | 132 | [src/create.ts:409](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L409) 133 | 134 | ___ 135 | 136 | ### 8MB 137 | 138 | • **8MB** = ``8388608`` 139 | 140 | #### Defined in 141 | 142 | [src/create.ts:416](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L416) 143 | -------------------------------------------------------------------------------- /docs/enums/TokenType.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / TokenType 2 | 3 | # Enumeration: TokenType 4 | 5 | ## Table of contents 6 | 7 | ### Enumeration Members 8 | 9 | - [ByteString](TokenType.md#bytestring) 10 | - [DictionaryEnd](TokenType.md#dictionaryend) 11 | - [DictionaryStart](TokenType.md#dictionarystart) 12 | - [Integer](TokenType.md#integer) 13 | - [ListEnd](TokenType.md#listend) 14 | - [ListStart](TokenType.md#liststart) 15 | 16 | ## Enumeration Members 17 | 18 | ### ByteString 19 | 20 | • **ByteString** = ``"ByteString"`` 21 | 22 | #### Defined in 23 | 24 | [src/transformers/tokenizer.ts:14](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L14) 25 | 26 | ___ 27 | 28 | ### DictionaryEnd 29 | 30 | • **DictionaryEnd** = ``"DictionaryEnd"`` 31 | 32 | #### Defined in 33 | 34 | [src/transformers/tokenizer.ts:18](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L18) 35 | 36 | ___ 37 | 38 | ### DictionaryStart 39 | 40 | • **DictionaryStart** = ``"DictionaryStart"`` 41 | 42 | #### Defined in 43 | 44 | [src/transformers/tokenizer.ts:17](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L17) 45 | 46 | ___ 47 | 48 | ### Integer 49 | 50 | • **Integer** = ``"Integer"`` 51 | 52 | #### Defined in 53 | 54 | [src/transformers/tokenizer.ts:13](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L13) 55 | 56 | ___ 57 | 58 | ### ListEnd 59 | 60 | • **ListEnd** = ``"ListEnd"`` 61 | 62 | #### Defined in 63 | 64 | [src/transformers/tokenizer.ts:16](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L16) 65 | 66 | ___ 67 | 68 | ### ListStart 69 | 70 | • **ListStart** = ``"ListStart"`` 71 | 72 | #### Defined in 73 | 74 | [src/transformers/tokenizer.ts:15](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L15) 75 | -------------------------------------------------------------------------------- /docs/enums/TorrentType.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / TorrentType 2 | 3 | # Enumeration: TorrentType 4 | 5 | torrent type: v1, v2, hybrid 6 | 7 | ## Table of contents 8 | 9 | ### Enumeration Members 10 | 11 | - [HYBRID](TorrentType.md#hybrid) 12 | - [V1](TorrentType.md#v1) 13 | - [V2](TorrentType.md#v2) 14 | 15 | ## Enumeration Members 16 | 17 | ### HYBRID 18 | 19 | • **HYBRID** = ``"hybrid"`` 20 | 21 | v1 + v2 hybrid torrent 22 | 23 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path) 24 | 25 | #### Defined in 26 | 27 | [src/create.ts:61](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L61) 28 | 29 | ___ 30 | 31 | ### V1 32 | 33 | • **V1** = ``"v1"`` 34 | 35 | v1 torrent 36 | 37 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html) 38 | 39 | #### Defined in 40 | 41 | [src/create.ts:49](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L49) 42 | 43 | ___ 44 | 45 | ### V2 46 | 47 | • **V2** = ``"v2"`` 48 | 49 | v2 torrent 50 | 51 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html) 52 | 53 | #### Defined in 54 | 55 | [src/create.ts:55](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L55) 56 | -------------------------------------------------------------------------------- /docs/interfaces/BObjectLoose.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / BObjectLoose 2 | 3 | # Interface: BObjectLoose 4 | 5 | ## Indexable 6 | 7 | ▪ [key: [`BByteString`](../modules.md#bbytestring)<``true``\>]: [`BData`](../modules.md#bdata)<``false``\> 8 | -------------------------------------------------------------------------------- /docs/interfaces/BObjectStrict.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / BObjectStrict 2 | 3 | # Interface: BObjectStrict 4 | 5 | ## Indexable 6 | 7 | ▪ [key: [`BByteString`](../modules.md#bbytestring)<``true``\>]: [`BData`](../modules.md#bdata)<``true``\> 8 | -------------------------------------------------------------------------------- /docs/interfaces/ByteStringToken.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / ByteStringToken 2 | 3 | # Interface: ByteStringToken 4 | 5 | ## Table of contents 6 | 7 | ### Properties 8 | 9 | - [type](ByteStringToken.md#type) 10 | - [value](ByteStringToken.md#value) 11 | 12 | ## Properties 13 | 14 | ### type 15 | 16 | • **type**: [`ByteString`](../enums/TokenType.md#bytestring) 17 | 18 | #### Defined in 19 | 20 | [src/transformers/tokenizer.ts:27](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L27) 21 | 22 | ___ 23 | 24 | ### value 25 | 26 | • **value**: `Uint8Array` 27 | 28 | #### Defined in 29 | 30 | [src/transformers/tokenizer.ts:28](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L28) 31 | -------------------------------------------------------------------------------- /docs/interfaces/DictionaryEndToken.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / DictionaryEndToken 2 | 3 | # Interface: DictionaryEndToken 4 | 5 | ## Table of contents 6 | 7 | ### Properties 8 | 9 | - [type](DictionaryEndToken.md#type) 10 | 11 | ## Properties 12 | 13 | ### type 14 | 15 | • **type**: [`DictionaryEnd`](../enums/TokenType.md#dictionaryend) 16 | 17 | #### Defined in 18 | 19 | [src/transformers/tokenizer.ts:44](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L44) 20 | -------------------------------------------------------------------------------- /docs/interfaces/DictionaryStartToken.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / DictionaryStartToken 2 | 3 | # Interface: DictionaryStartToken 4 | 5 | ## Table of contents 6 | 7 | ### Properties 8 | 9 | - [type](DictionaryStartToken.md#type) 10 | 11 | ## Properties 12 | 13 | ### type 14 | 15 | • **type**: [`DictionaryStart`](../enums/TokenType.md#dictionarystart) 16 | 17 | #### Defined in 18 | 19 | [src/transformers/tokenizer.ts:40](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L40) 20 | -------------------------------------------------------------------------------- /docs/interfaces/FilePropsBase.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / FilePropsBase 2 | 3 | # Interface: FilePropsBase 4 | 5 | base file props 6 | 7 | ## Hierarchy 8 | 9 | - [`BObject`](../modules.md#bobject)<``false``\> 10 | 11 | ↳ **`FilePropsBase`** 12 | 13 | ↳↳ [`FilePropsV1`](FilePropsV1.md) 14 | 15 | ↳↳ [`FilePropsV2`](FilePropsV2.md) 16 | 17 | ## Table of contents 18 | 19 | ### Properties 20 | 21 | - [attr](FilePropsBase.md#attr) 22 | - [length](FilePropsBase.md#length) 23 | 24 | ## Properties 25 | 26 | ### attr 27 | 28 | • `Optional` **attr**: [`FileAttrs`](../modules.md#fileattrs) 29 | 30 | A variable-length string. When present, 31 | the characters each represent a file attribute: 32 | ``` 33 | l = symlink 34 | x = executable 35 | h = hidden 36 | p = padding file 37 | ``` 38 | [BEP 47](https://www.bittorrent.org/beps/bep_0047.html#:~:text=20%20bytes%3E%2C%0A%20%20%20%20...%0A%20%20%7D%2C%0A%20%20...%0A%7D-,attr,-A%20variable%2Dlength) 39 | 40 | #### Defined in 41 | 42 | [src/utils/fileTree.ts:76](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/utils/fileTree.ts#L76) 43 | 44 | ___ 45 | 46 | ### length 47 | 48 | • **length**: `number` 49 | 50 | Length of the file in bytes 51 | 52 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=the%20following%20keys%3A-,length,-%2D%20The%20length%20of) 53 | | 54 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=pieces%20root32%3Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaeeeeeee-,length,-Length%20of%20the) 55 | 56 | #### Defined in 57 | 58 | [src/utils/fileTree.ts:64](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/utils/fileTree.ts#L64) 59 | -------------------------------------------------------------------------------- /docs/interfaces/FilePropsV1.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / FilePropsV1 2 | 3 | # Interface: FilePropsV1 4 | 5 | v1 file props 6 | 7 | ## Hierarchy 8 | 9 | - [`FilePropsBase`](FilePropsBase.md) 10 | 11 | ↳ **`FilePropsV1`** 12 | 13 | ## Table of contents 14 | 15 | ### Properties 16 | 17 | - [attr](FilePropsV1.md#attr) 18 | - [length](FilePropsV1.md#length) 19 | - [path](FilePropsV1.md#path) 20 | 21 | ## Properties 22 | 23 | ### attr 24 | 25 | • `Optional` **attr**: [`FileAttrs`](../modules.md#fileattrs) 26 | 27 | A variable-length string. When present, 28 | the characters each represent a file attribute: 29 | ``` 30 | l = symlink 31 | x = executable 32 | h = hidden 33 | p = padding file 34 | ``` 35 | [BEP 47](https://www.bittorrent.org/beps/bep_0047.html#:~:text=20%20bytes%3E%2C%0A%20%20%20%20...%0A%20%20%7D%2C%0A%20%20...%0A%7D-,attr,-A%20variable%2Dlength) 36 | 37 | #### Inherited from 38 | 39 | [FilePropsBase](FilePropsBase.md).[attr](FilePropsBase.md#attr) 40 | 41 | #### Defined in 42 | 43 | [src/utils/fileTree.ts:76](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/utils/fileTree.ts#L76) 44 | 45 | ___ 46 | 47 | ### length 48 | 49 | • **length**: `number` 50 | 51 | Length of the file in bytes 52 | 53 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=the%20following%20keys%3A-,length,-%2D%20The%20length%20of) 54 | | 55 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=pieces%20root32%3Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaeeeeeee-,length,-Length%20of%20the) 56 | 57 | #### Inherited from 58 | 59 | [FilePropsBase](FilePropsBase.md).[length](FilePropsBase.md#length) 60 | 61 | #### Defined in 62 | 63 | [src/utils/fileTree.ts:64](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/utils/fileTree.ts#L64) 64 | 65 | ___ 66 | 67 | ### path 68 | 69 | • **path**: `string`[] 70 | 71 | A list of UTF-8 encoded strings 72 | corresponding to **subdirectory** names 73 | 74 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=file%2C%20in%20bytes.-,path,-%2D%20A%20list%20of) 75 | 76 | #### Defined in 77 | 78 | [src/utils/fileTree.ts:89](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/utils/fileTree.ts#L89) 79 | -------------------------------------------------------------------------------- /docs/interfaces/FilePropsV2.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / FilePropsV2 2 | 3 | # Interface: FilePropsV2 4 | 5 | v2 file props 6 | 7 | ## Hierarchy 8 | 9 | - [`FilePropsBase`](FilePropsBase.md) 10 | 11 | ↳ **`FilePropsV2`** 12 | 13 | ## Table of contents 14 | 15 | ### Properties 16 | 17 | - [attr](FilePropsV2.md#attr) 18 | - [length](FilePropsV2.md#length) 19 | - [pieces root](FilePropsV2.md#pieces root) 20 | 21 | ## Properties 22 | 23 | ### attr 24 | 25 | • `Optional` **attr**: [`FileAttrs`](../modules.md#fileattrs) 26 | 27 | A variable-length string. When present, 28 | the characters each represent a file attribute: 29 | ``` 30 | l = symlink 31 | x = executable 32 | h = hidden 33 | p = padding file 34 | ``` 35 | [BEP 47](https://www.bittorrent.org/beps/bep_0047.html#:~:text=20%20bytes%3E%2C%0A%20%20%20%20...%0A%20%20%7D%2C%0A%20%20...%0A%7D-,attr,-A%20variable%2Dlength) 36 | 37 | #### Inherited from 38 | 39 | [FilePropsBase](FilePropsBase.md).[attr](FilePropsBase.md#attr) 40 | 41 | #### Defined in 42 | 43 | [src/utils/fileTree.ts:76](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/utils/fileTree.ts#L76) 44 | 45 | ___ 46 | 47 | ### length 48 | 49 | • **length**: `number` 50 | 51 | Length of the file in bytes 52 | 53 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=the%20following%20keys%3A-,length,-%2D%20The%20length%20of) 54 | | 55 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=pieces%20root32%3Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaeeeeeee-,length,-Length%20of%20the) 56 | 57 | #### Inherited from 58 | 59 | [FilePropsBase](FilePropsBase.md).[length](FilePropsBase.md#length) 60 | 61 | #### Defined in 62 | 63 | [src/utils/fileTree.ts:64](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/utils/fileTree.ts#L64) 64 | 65 | ___ 66 | 67 | ### pieces root 68 | 69 | • `Optional` **pieces root**: `ArrayBuffer` 70 | 71 | For **non-empty** files this is the the root hash 72 | of a merkle tree with a branching factor of 2, 73 | constructed from 16KiB blocks of the file 74 | 75 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=any%20sibling%20entries.-,pieces%20root,-For%20non%2Dempty) 76 | 77 | #### Defined in 78 | 79 | [src/utils/fileTree.ts:103](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/utils/fileTree.ts#L103) 80 | -------------------------------------------------------------------------------- /docs/interfaces/FileTreeFileNode.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / FileTreeFileNode 2 | 3 | # Interface: FileTreeFileNode 4 | 5 | v2 file tree file node 6 | 7 | ## Hierarchy 8 | 9 | - [`BObject`](../modules.md#bobject)<``false``\> 10 | 11 | ↳ **`FileTreeFileNode`** 12 | 13 | ## Table of contents 14 | 15 | ### Properties 16 | 17 | - [](FileTreeFileNode.md#) 18 | 19 | ## Properties 20 | 21 | • ****: [`FilePropsV2`](FilePropsV2.md) 22 | 23 | Entries with zero-length keys describe the properties 24 | of the composed path at that point 25 | 26 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=Entries%20with%20zero%2Dlength%20keys%20describe%20the%20properties%20of%20the%20composed%20path%20at%20that%20point) 27 | 28 | #### Defined in 29 | 30 | [src/utils/fileTree.ts:121](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/utils/fileTree.ts#L121) 31 | -------------------------------------------------------------------------------- /docs/interfaces/InfoBase.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / InfoBase 2 | 3 | # Interface: InfoBase 4 | 5 | info base 6 | 7 | ## Hierarchy 8 | 9 | - [`BObject`](../modules.md#bobject)<``false``\> 10 | 11 | ↳ **`InfoBase`** 12 | 13 | ↳↳ [`InfoBaseV1`](InfoBaseV1.md) 14 | 15 | ↳↳ [`InfoV2`](InfoV2.md) 16 | 17 | ## Table of contents 18 | 19 | ### Properties 20 | 21 | - [name](InfoBase.md#name) 22 | - [piece length](InfoBase.md#piece length) 23 | - [private](InfoBase.md#private) 24 | - [source](InfoBase.md#source) 25 | 26 | ## Properties 27 | 28 | ### name 29 | 30 | • **name**: `string` 31 | 32 | The suggested name to save the file (or directory) as. 33 | It is purely advisory 34 | 35 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=The-,name,-key%20maps%20to) 36 | | 37 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=info%20dictionary-,name,-A%20display%20name) 38 | 39 | #### Defined in 40 | 41 | [src/create.ts:223](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L223) 42 | 43 | ___ 44 | 45 | ### piece length 46 | 47 | • **piece length**: `number` 48 | 49 | The number of bytes that each logical piece 50 | in the peer protocol refers to 51 | 52 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=is%20purely%20advisory.-,piece%20length,-maps%20to%20the) 53 | | 54 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=is%20purely%20advisory.-,piece%20length,-The%20number%20of) 55 | 56 | #### Defined in 57 | 58 | [src/create.ts:232](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L232) 59 | 60 | ___ 61 | 62 | ### private 63 | 64 | • `Optional` **private**: `boolean` 65 | 66 | is private torrent 67 | 68 | #### Defined in 69 | 70 | [src/create.ts:236](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L236) 71 | 72 | ___ 73 | 74 | ### source 75 | 76 | • `Optional` **source**: `string` 77 | 78 | source 79 | 80 | #### Defined in 81 | 82 | [src/create.ts:240](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L240) 83 | -------------------------------------------------------------------------------- /docs/interfaces/InfoBaseV1.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / InfoBaseV1 2 | 3 | # Interface: InfoBaseV1 4 | 5 | v1 info base 6 | 7 | ## Hierarchy 8 | 9 | - [`InfoBase`](InfoBase.md) 10 | 11 | ↳ **`InfoBaseV1`** 12 | 13 | ↳↳ [`InfoSingleFileV1`](InfoSingleFileV1.md) 14 | 15 | ↳↳ [`InfoMultiFileV1`](InfoMultiFileV1.md) 16 | 17 | ## Table of contents 18 | 19 | ### Properties 20 | 21 | - [name](InfoBaseV1.md#name) 22 | - [piece length](InfoBaseV1.md#piece length) 23 | - [pieces](InfoBaseV1.md#pieces) 24 | - [private](InfoBaseV1.md#private) 25 | - [source](InfoBaseV1.md#source) 26 | 27 | ## Properties 28 | 29 | ### name 30 | 31 | • **name**: `string` 32 | 33 | The suggested name to save the file (or directory) as. 34 | It is purely advisory 35 | 36 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=The-,name,-key%20maps%20to) 37 | | 38 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=info%20dictionary-,name,-A%20display%20name) 39 | 40 | #### Inherited from 41 | 42 | [InfoBase](InfoBase.md).[name](InfoBase.md#name) 43 | 44 | #### Defined in 45 | 46 | [src/create.ts:223](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L223) 47 | 48 | ___ 49 | 50 | ### piece length 51 | 52 | • **piece length**: `number` 53 | 54 | The number of bytes that each logical piece 55 | in the peer protocol refers to 56 | 57 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=is%20purely%20advisory.-,piece%20length,-maps%20to%20the) 58 | | 59 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=is%20purely%20advisory.-,piece%20length,-The%20number%20of) 60 | 61 | #### Inherited from 62 | 63 | [InfoBase](InfoBase.md).[piece length](InfoBase.md#piece length) 64 | 65 | #### Defined in 66 | 67 | [src/create.ts:232](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L232) 68 | 69 | ___ 70 | 71 | ### pieces 72 | 73 | • **pieces**: `string` \| `ArrayBuffer` 74 | 75 | Pieces maps to a string whose length is a multiple of 20 76 | 77 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=M%20as%20default%29.-,pieces,-maps%20to%20a) 78 | 79 | #### Defined in 80 | 81 | [src/create.ts:252](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L252) 82 | 83 | ___ 84 | 85 | ### private 86 | 87 | • `Optional` **private**: `boolean` 88 | 89 | is private torrent 90 | 91 | #### Inherited from 92 | 93 | [InfoBase](InfoBase.md).[private](InfoBase.md#private) 94 | 95 | #### Defined in 96 | 97 | [src/create.ts:236](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L236) 98 | 99 | ___ 100 | 101 | ### source 102 | 103 | • `Optional` **source**: `string` 104 | 105 | source 106 | 107 | #### Inherited from 108 | 109 | [InfoBase](InfoBase.md).[source](InfoBase.md#source) 110 | 111 | #### Defined in 112 | 113 | [src/create.ts:240](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L240) 114 | -------------------------------------------------------------------------------- /docs/interfaces/InfoMultiFileHybrid.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / InfoMultiFileHybrid 2 | 3 | # Interface: InfoMultiFileHybrid 4 | 5 | hybrid multi file info 6 | 7 | ## Hierarchy 8 | 9 | - [`InfoMultiFileV1`](InfoMultiFileV1.md) 10 | 11 | - [`InfoV2`](InfoV2.md) 12 | 13 | ↳ **`InfoMultiFileHybrid`** 14 | 15 | ## Table of contents 16 | 17 | ### Properties 18 | 19 | - [file tree](InfoMultiFileHybrid.md#file tree) 20 | - [files](InfoMultiFileHybrid.md#files) 21 | - [meta version](InfoMultiFileHybrid.md#meta version) 22 | - [name](InfoMultiFileHybrid.md#name) 23 | - [piece length](InfoMultiFileHybrid.md#piece length) 24 | - [pieces](InfoMultiFileHybrid.md#pieces) 25 | - [private](InfoMultiFileHybrid.md#private) 26 | - [source](InfoMultiFileHybrid.md#source) 27 | 28 | ## Properties 29 | 30 | ### file tree 31 | 32 | • **file tree**: [`FileTreeDirNode`](../modules.md#filetreedirnode) 33 | 34 | A tree of dictionaries where dictionary keys 35 | represent UTF-8 encoded path elements 36 | 37 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=about%20invalid%20files.-,file%20tree,-A%20tree%20of) 38 | 39 | #### Inherited from 40 | 41 | [InfoV2](InfoV2.md).[file tree](InfoV2.md#file tree) 42 | 43 | #### Defined in 44 | 45 | [src/create.ts:284](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L284) 46 | 47 | ___ 48 | 49 | ### files 50 | 51 | • **files**: [`FileListV1`](../modules.md#filelistv1) 52 | 53 | #### Inherited from 54 | 55 | [InfoMultiFileV1](InfoMultiFileV1.md).[files](InfoMultiFileV1.md#files) 56 | 57 | #### Defined in 58 | 59 | [src/create.ts:266](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L266) 60 | 61 | ___ 62 | 63 | ### meta version 64 | 65 | • **meta version**: `number` 66 | 67 | An integer value, set to 2 to indicate compatibility 68 | with the current revision of this specification 69 | 70 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=an%20alignment%20gap.-,meta%20version,-An%20integer%20value) 71 | 72 | #### Inherited from 73 | 74 | [InfoV2](InfoV2.md).[meta version](InfoV2.md#meta version) 75 | 76 | #### Defined in 77 | 78 | [src/create.ts:291](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L291) 79 | 80 | ___ 81 | 82 | ### name 83 | 84 | • **name**: `string` 85 | 86 | The suggested name to save the file (or directory) as. 87 | It is purely advisory 88 | 89 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=The-,name,-key%20maps%20to) 90 | | 91 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=info%20dictionary-,name,-A%20display%20name) 92 | 93 | #### Inherited from 94 | 95 | [InfoV2](InfoV2.md).[name](InfoV2.md#name) 96 | 97 | #### Defined in 98 | 99 | [src/create.ts:223](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L223) 100 | 101 | ___ 102 | 103 | ### piece length 104 | 105 | • **piece length**: `number` 106 | 107 | The number of bytes that each logical piece 108 | in the peer protocol refers to 109 | 110 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=is%20purely%20advisory.-,piece%20length,-maps%20to%20the) 111 | | 112 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=is%20purely%20advisory.-,piece%20length,-The%20number%20of) 113 | 114 | #### Inherited from 115 | 116 | [InfoV2](InfoV2.md).[piece length](InfoV2.md#piece length) 117 | 118 | #### Defined in 119 | 120 | [src/create.ts:232](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L232) 121 | 122 | ___ 123 | 124 | ### pieces 125 | 126 | • **pieces**: `string` \| `ArrayBuffer` 127 | 128 | Pieces maps to a string whose length is a multiple of 20 129 | 130 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=M%20as%20default%29.-,pieces,-maps%20to%20a) 131 | 132 | #### Inherited from 133 | 134 | [InfoMultiFileV1](InfoMultiFileV1.md).[pieces](InfoMultiFileV1.md#pieces) 135 | 136 | #### Defined in 137 | 138 | [src/create.ts:252](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L252) 139 | 140 | ___ 141 | 142 | ### private 143 | 144 | • `Optional` **private**: `boolean` 145 | 146 | is private torrent 147 | 148 | #### Inherited from 149 | 150 | [InfoV2](InfoV2.md).[private](InfoV2.md#private) 151 | 152 | #### Defined in 153 | 154 | [src/create.ts:236](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L236) 155 | 156 | ___ 157 | 158 | ### source 159 | 160 | • `Optional` **source**: `string` 161 | 162 | source 163 | 164 | #### Inherited from 165 | 166 | [InfoV2](InfoV2.md).[source](InfoV2.md#source) 167 | 168 | #### Defined in 169 | 170 | [src/create.ts:240](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L240) 171 | -------------------------------------------------------------------------------- /docs/interfaces/InfoMultiFileV1.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / InfoMultiFileV1 2 | 3 | # Interface: InfoMultiFileV1 4 | 5 | v1 multi file info 6 | 7 | ## Hierarchy 8 | 9 | - [`InfoBaseV1`](InfoBaseV1.md) 10 | 11 | ↳ **`InfoMultiFileV1`** 12 | 13 | ↳↳ [`InfoMultiFileHybrid`](InfoMultiFileHybrid.md) 14 | 15 | ## Table of contents 16 | 17 | ### Properties 18 | 19 | - [files](InfoMultiFileV1.md#files) 20 | - [name](InfoMultiFileV1.md#name) 21 | - [piece length](InfoMultiFileV1.md#piece length) 22 | - [pieces](InfoMultiFileV1.md#pieces) 23 | - [private](InfoMultiFileV1.md#private) 24 | - [source](InfoMultiFileV1.md#source) 25 | 26 | ## Properties 27 | 28 | ### files 29 | 30 | • **files**: [`FileListV1`](../modules.md#filelistv1) 31 | 32 | #### Defined in 33 | 34 | [src/create.ts:266](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L266) 35 | 36 | ___ 37 | 38 | ### name 39 | 40 | • **name**: `string` 41 | 42 | The suggested name to save the file (or directory) as. 43 | It is purely advisory 44 | 45 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=The-,name,-key%20maps%20to) 46 | | 47 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=info%20dictionary-,name,-A%20display%20name) 48 | 49 | #### Inherited from 50 | 51 | [InfoBaseV1](InfoBaseV1.md).[name](InfoBaseV1.md#name) 52 | 53 | #### Defined in 54 | 55 | [src/create.ts:223](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L223) 56 | 57 | ___ 58 | 59 | ### piece length 60 | 61 | • **piece length**: `number` 62 | 63 | The number of bytes that each logical piece 64 | in the peer protocol refers to 65 | 66 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=is%20purely%20advisory.-,piece%20length,-maps%20to%20the) 67 | | 68 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=is%20purely%20advisory.-,piece%20length,-The%20number%20of) 69 | 70 | #### Inherited from 71 | 72 | [InfoBaseV1](InfoBaseV1.md).[piece length](InfoBaseV1.md#piece length) 73 | 74 | #### Defined in 75 | 76 | [src/create.ts:232](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L232) 77 | 78 | ___ 79 | 80 | ### pieces 81 | 82 | • **pieces**: `string` \| `ArrayBuffer` 83 | 84 | Pieces maps to a string whose length is a multiple of 20 85 | 86 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=M%20as%20default%29.-,pieces,-maps%20to%20a) 87 | 88 | #### Inherited from 89 | 90 | [InfoBaseV1](InfoBaseV1.md).[pieces](InfoBaseV1.md#pieces) 91 | 92 | #### Defined in 93 | 94 | [src/create.ts:252](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L252) 95 | 96 | ___ 97 | 98 | ### private 99 | 100 | • `Optional` **private**: `boolean` 101 | 102 | is private torrent 103 | 104 | #### Inherited from 105 | 106 | [InfoBaseV1](InfoBaseV1.md).[private](InfoBaseV1.md#private) 107 | 108 | #### Defined in 109 | 110 | [src/create.ts:236](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L236) 111 | 112 | ___ 113 | 114 | ### source 115 | 116 | • `Optional` **source**: `string` 117 | 118 | source 119 | 120 | #### Inherited from 121 | 122 | [InfoBaseV1](InfoBaseV1.md).[source](InfoBaseV1.md#source) 123 | 124 | #### Defined in 125 | 126 | [src/create.ts:240](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L240) 127 | -------------------------------------------------------------------------------- /docs/interfaces/InfoSingleFileHybrid.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / InfoSingleFileHybrid 2 | 3 | # Interface: InfoSingleFileHybrid 4 | 5 | hybrid single file info 6 | 7 | ## Hierarchy 8 | 9 | - [`InfoSingleFileV1`](InfoSingleFileV1.md) 10 | 11 | - [`InfoV2`](InfoV2.md) 12 | 13 | ↳ **`InfoSingleFileHybrid`** 14 | 15 | ## Table of contents 16 | 17 | ### Properties 18 | 19 | - [file tree](InfoSingleFileHybrid.md#file tree) 20 | - [length](InfoSingleFileHybrid.md#length) 21 | - [meta version](InfoSingleFileHybrid.md#meta version) 22 | - [name](InfoSingleFileHybrid.md#name) 23 | - [piece length](InfoSingleFileHybrid.md#piece length) 24 | - [pieces](InfoSingleFileHybrid.md#pieces) 25 | - [private](InfoSingleFileHybrid.md#private) 26 | - [source](InfoSingleFileHybrid.md#source) 27 | 28 | ## Properties 29 | 30 | ### file tree 31 | 32 | • **file tree**: [`FileTreeDirNode`](../modules.md#filetreedirnode) 33 | 34 | A tree of dictionaries where dictionary keys 35 | represent UTF-8 encoded path elements 36 | 37 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=about%20invalid%20files.-,file%20tree,-A%20tree%20of) 38 | 39 | #### Inherited from 40 | 41 | [InfoV2](InfoV2.md).[file tree](InfoV2.md#file tree) 42 | 43 | #### Defined in 44 | 45 | [src/create.ts:284](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L284) 46 | 47 | ___ 48 | 49 | ### length 50 | 51 | • **length**: `number` 52 | 53 | #### Inherited from 54 | 55 | [InfoSingleFileV1](InfoSingleFileV1.md).[length](InfoSingleFileV1.md#length) 56 | 57 | #### Defined in 58 | 59 | [src/create.ts:259](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L259) 60 | 61 | ___ 62 | 63 | ### meta version 64 | 65 | • **meta version**: `number` 66 | 67 | An integer value, set to 2 to indicate compatibility 68 | with the current revision of this specification 69 | 70 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=an%20alignment%20gap.-,meta%20version,-An%20integer%20value) 71 | 72 | #### Inherited from 73 | 74 | [InfoV2](InfoV2.md).[meta version](InfoV2.md#meta version) 75 | 76 | #### Defined in 77 | 78 | [src/create.ts:291](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L291) 79 | 80 | ___ 81 | 82 | ### name 83 | 84 | • **name**: `string` 85 | 86 | The suggested name to save the file (or directory) as. 87 | It is purely advisory 88 | 89 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=The-,name,-key%20maps%20to) 90 | | 91 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=info%20dictionary-,name,-A%20display%20name) 92 | 93 | #### Inherited from 94 | 95 | [InfoV2](InfoV2.md).[name](InfoV2.md#name) 96 | 97 | #### Defined in 98 | 99 | [src/create.ts:223](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L223) 100 | 101 | ___ 102 | 103 | ### piece length 104 | 105 | • **piece length**: `number` 106 | 107 | The number of bytes that each logical piece 108 | in the peer protocol refers to 109 | 110 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=is%20purely%20advisory.-,piece%20length,-maps%20to%20the) 111 | | 112 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=is%20purely%20advisory.-,piece%20length,-The%20number%20of) 113 | 114 | #### Inherited from 115 | 116 | [InfoV2](InfoV2.md).[piece length](InfoV2.md#piece length) 117 | 118 | #### Defined in 119 | 120 | [src/create.ts:232](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L232) 121 | 122 | ___ 123 | 124 | ### pieces 125 | 126 | • **pieces**: `string` \| `ArrayBuffer` 127 | 128 | Pieces maps to a string whose length is a multiple of 20 129 | 130 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=M%20as%20default%29.-,pieces,-maps%20to%20a) 131 | 132 | #### Inherited from 133 | 134 | [InfoSingleFileV1](InfoSingleFileV1.md).[pieces](InfoSingleFileV1.md#pieces) 135 | 136 | #### Defined in 137 | 138 | [src/create.ts:252](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L252) 139 | 140 | ___ 141 | 142 | ### private 143 | 144 | • `Optional` **private**: `boolean` 145 | 146 | is private torrent 147 | 148 | #### Inherited from 149 | 150 | [InfoV2](InfoV2.md).[private](InfoV2.md#private) 151 | 152 | #### Defined in 153 | 154 | [src/create.ts:236](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L236) 155 | 156 | ___ 157 | 158 | ### source 159 | 160 | • `Optional` **source**: `string` 161 | 162 | source 163 | 164 | #### Inherited from 165 | 166 | [InfoV2](InfoV2.md).[source](InfoV2.md#source) 167 | 168 | #### Defined in 169 | 170 | [src/create.ts:240](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L240) 171 | -------------------------------------------------------------------------------- /docs/interfaces/InfoSingleFileV1.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / InfoSingleFileV1 2 | 3 | # Interface: InfoSingleFileV1 4 | 5 | v1 single file info 6 | 7 | ## Hierarchy 8 | 9 | - [`InfoBaseV1`](InfoBaseV1.md) 10 | 11 | ↳ **`InfoSingleFileV1`** 12 | 13 | ↳↳ [`InfoSingleFileHybrid`](InfoSingleFileHybrid.md) 14 | 15 | ## Table of contents 16 | 17 | ### Properties 18 | 19 | - [length](InfoSingleFileV1.md#length) 20 | - [name](InfoSingleFileV1.md#name) 21 | - [piece length](InfoSingleFileV1.md#piece length) 22 | - [pieces](InfoSingleFileV1.md#pieces) 23 | - [private](InfoSingleFileV1.md#private) 24 | - [source](InfoSingleFileV1.md#source) 25 | 26 | ## Properties 27 | 28 | ### length 29 | 30 | • **length**: `number` 31 | 32 | #### Defined in 33 | 34 | [src/create.ts:259](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L259) 35 | 36 | ___ 37 | 38 | ### name 39 | 40 | • **name**: `string` 41 | 42 | The suggested name to save the file (or directory) as. 43 | It is purely advisory 44 | 45 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=The-,name,-key%20maps%20to) 46 | | 47 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=info%20dictionary-,name,-A%20display%20name) 48 | 49 | #### Inherited from 50 | 51 | [InfoBaseV1](InfoBaseV1.md).[name](InfoBaseV1.md#name) 52 | 53 | #### Defined in 54 | 55 | [src/create.ts:223](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L223) 56 | 57 | ___ 58 | 59 | ### piece length 60 | 61 | • **piece length**: `number` 62 | 63 | The number of bytes that each logical piece 64 | in the peer protocol refers to 65 | 66 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=is%20purely%20advisory.-,piece%20length,-maps%20to%20the) 67 | | 68 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=is%20purely%20advisory.-,piece%20length,-The%20number%20of) 69 | 70 | #### Inherited from 71 | 72 | [InfoBaseV1](InfoBaseV1.md).[piece length](InfoBaseV1.md#piece length) 73 | 74 | #### Defined in 75 | 76 | [src/create.ts:232](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L232) 77 | 78 | ___ 79 | 80 | ### pieces 81 | 82 | • **pieces**: `string` \| `ArrayBuffer` 83 | 84 | Pieces maps to a string whose length is a multiple of 20 85 | 86 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=M%20as%20default%29.-,pieces,-maps%20to%20a) 87 | 88 | #### Inherited from 89 | 90 | [InfoBaseV1](InfoBaseV1.md).[pieces](InfoBaseV1.md#pieces) 91 | 92 | #### Defined in 93 | 94 | [src/create.ts:252](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L252) 95 | 96 | ___ 97 | 98 | ### private 99 | 100 | • `Optional` **private**: `boolean` 101 | 102 | is private torrent 103 | 104 | #### Inherited from 105 | 106 | [InfoBaseV1](InfoBaseV1.md).[private](InfoBaseV1.md#private) 107 | 108 | #### Defined in 109 | 110 | [src/create.ts:236](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L236) 111 | 112 | ___ 113 | 114 | ### source 115 | 116 | • `Optional` **source**: `string` 117 | 118 | source 119 | 120 | #### Inherited from 121 | 122 | [InfoBaseV1](InfoBaseV1.md).[source](InfoBaseV1.md#source) 123 | 124 | #### Defined in 125 | 126 | [src/create.ts:240](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L240) 127 | -------------------------------------------------------------------------------- /docs/interfaces/InfoV2.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / InfoV2 2 | 3 | # Interface: InfoV2 4 | 5 | v2 info 6 | 7 | ## Hierarchy 8 | 9 | - [`InfoBase`](InfoBase.md) 10 | 11 | ↳ **`InfoV2`** 12 | 13 | ↳↳ [`InfoSingleFileHybrid`](InfoSingleFileHybrid.md) 14 | 15 | ↳↳ [`InfoMultiFileHybrid`](InfoMultiFileHybrid.md) 16 | 17 | ## Table of contents 18 | 19 | ### Properties 20 | 21 | - [file tree](InfoV2.md#file tree) 22 | - [meta version](InfoV2.md#meta version) 23 | - [name](InfoV2.md#name) 24 | - [piece length](InfoV2.md#piece length) 25 | - [private](InfoV2.md#private) 26 | - [source](InfoV2.md#source) 27 | 28 | ## Properties 29 | 30 | ### file tree 31 | 32 | • **file tree**: [`FileTreeDirNode`](../modules.md#filetreedirnode) 33 | 34 | A tree of dictionaries where dictionary keys 35 | represent UTF-8 encoded path elements 36 | 37 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=about%20invalid%20files.-,file%20tree,-A%20tree%20of) 38 | 39 | #### Defined in 40 | 41 | [src/create.ts:284](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L284) 42 | 43 | ___ 44 | 45 | ### meta version 46 | 47 | • **meta version**: `number` 48 | 49 | An integer value, set to 2 to indicate compatibility 50 | with the current revision of this specification 51 | 52 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=an%20alignment%20gap.-,meta%20version,-An%20integer%20value) 53 | 54 | #### Defined in 55 | 56 | [src/create.ts:291](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L291) 57 | 58 | ___ 59 | 60 | ### name 61 | 62 | • **name**: `string` 63 | 64 | The suggested name to save the file (or directory) as. 65 | It is purely advisory 66 | 67 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=The-,name,-key%20maps%20to) 68 | | 69 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=info%20dictionary-,name,-A%20display%20name) 70 | 71 | #### Inherited from 72 | 73 | [InfoBase](InfoBase.md).[name](InfoBase.md#name) 74 | 75 | #### Defined in 76 | 77 | [src/create.ts:223](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L223) 78 | 79 | ___ 80 | 81 | ### piece length 82 | 83 | • **piece length**: `number` 84 | 85 | The number of bytes that each logical piece 86 | in the peer protocol refers to 87 | 88 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=is%20purely%20advisory.-,piece%20length,-maps%20to%20the) 89 | | 90 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=is%20purely%20advisory.-,piece%20length,-The%20number%20of) 91 | 92 | #### Inherited from 93 | 94 | [InfoBase](InfoBase.md).[piece length](InfoBase.md#piece length) 95 | 96 | #### Defined in 97 | 98 | [src/create.ts:232](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L232) 99 | 100 | ___ 101 | 102 | ### private 103 | 104 | • `Optional` **private**: `boolean` 105 | 106 | is private torrent 107 | 108 | #### Inherited from 109 | 110 | [InfoBase](InfoBase.md).[private](InfoBase.md#private) 111 | 112 | #### Defined in 113 | 114 | [src/create.ts:236](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L236) 115 | 116 | ___ 117 | 118 | ### source 119 | 120 | • `Optional` **source**: `string` 121 | 122 | source 123 | 124 | #### Inherited from 125 | 126 | [InfoBase](InfoBase.md).[source](InfoBase.md#source) 127 | 128 | #### Defined in 129 | 130 | [src/create.ts:240](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L240) 131 | -------------------------------------------------------------------------------- /docs/interfaces/IntegerToken.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / IntegerToken 2 | 3 | # Interface: IntegerToken 4 | 5 | ## Table of contents 6 | 7 | ### Properties 8 | 9 | - [type](IntegerToken.md#type) 10 | - [value](IntegerToken.md#value) 11 | 12 | ## Properties 13 | 14 | ### type 15 | 16 | • **type**: [`Integer`](../enums/TokenType.md#integer) 17 | 18 | #### Defined in 19 | 20 | [src/transformers/tokenizer.ts:22](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L22) 21 | 22 | ___ 23 | 24 | ### value 25 | 26 | • **value**: `Uint8Array` 27 | 28 | #### Defined in 29 | 30 | [src/transformers/tokenizer.ts:23](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L23) 31 | -------------------------------------------------------------------------------- /docs/interfaces/ListEndToken.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / ListEndToken 2 | 3 | # Interface: ListEndToken 4 | 5 | ## Table of contents 6 | 7 | ### Properties 8 | 9 | - [type](ListEndToken.md#type) 10 | 11 | ## Properties 12 | 13 | ### type 14 | 15 | • **type**: [`ListEnd`](../enums/TokenType.md#listend) 16 | 17 | #### Defined in 18 | 19 | [src/transformers/tokenizer.ts:36](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L36) 20 | -------------------------------------------------------------------------------- /docs/interfaces/ListStartToken.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / ListStartToken 2 | 3 | # Interface: ListStartToken 4 | 5 | ## Table of contents 6 | 7 | ### Properties 8 | 9 | - [type](ListStartToken.md#type) 10 | 11 | ## Properties 12 | 13 | ### type 14 | 15 | • **type**: [`ListStart`](../enums/TokenType.md#liststart) 16 | 17 | #### Defined in 18 | 19 | [src/transformers/tokenizer.ts:32](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/transformers/tokenizer.ts#L32) 20 | -------------------------------------------------------------------------------- /docs/interfaces/MetaInfoBase.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / MetaInfoBase 2 | 3 | # Interface: MetaInfoBase 4 | 5 | base meta info 6 | 7 | ## Hierarchy 8 | 9 | - [`BObject`](../modules.md#bobject)<``false``\> 10 | 11 | ↳ **`MetaInfoBase`** 12 | 13 | ↳↳ [`MetaInfoV1`](MetaInfoV1.md) 14 | 15 | ↳↳ [`MetaInfoV2`](MetaInfoV2.md) 16 | 17 | ↳↳ [`MetaInfoHybrid`](MetaInfoHybrid.md) 18 | 19 | ## Table of contents 20 | 21 | ### Properties 22 | 23 | - [announce](MetaInfoBase.md#announce) 24 | - [announce-list](MetaInfoBase.md#announce-list) 25 | - [comment](MetaInfoBase.md#comment) 26 | - [created by](MetaInfoBase.md#created by) 27 | - [creation date](MetaInfoBase.md#creation date) 28 | 29 | ## Properties 30 | 31 | ### announce 32 | 33 | • `Optional` **announce**: `string` 34 | 35 | The URL of the tracker 36 | 37 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=the%20following%20keys%3A-,announce,-The%20URL%20of) 38 | | 39 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=the%20following%20keys%3A-,announce,-The%20URL%20of) 40 | 41 | #### Defined in 42 | 43 | [src/create.ts:338](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L338) 44 | 45 | ___ 46 | 47 | ### announce-list 48 | 49 | • `Optional` **announce-list**: `string`[][] 50 | 51 | This key will refer to a list of lists of URLs, 52 | and will contain a list of tiers of announces 53 | 54 | [BEP 12](http://bittorrent.org/beps/bep_0012.html#:~:text=This%20key%20will%20refer%20to%20a%20list%20of%20lists%20of%20URLs%2C%20and%20will%20contain%20a%20list%20of%20tiers%20of%20announces) 55 | 56 | #### Defined in 57 | 58 | [src/create.ts:345](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L345) 59 | 60 | ___ 61 | 62 | ### comment 63 | 64 | • `Optional` **comment**: `string` 65 | 66 | Free-form textual comments of the author 67 | 68 | [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=00%3A00%20UTC%29-,comment,-%3A%20%28optional%29%20free%2Dform) 69 | 70 | #### Defined in 71 | 72 | [src/create.ts:351](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L351) 73 | 74 | ___ 75 | 76 | ### created by 77 | 78 | • `Optional` **created by**: `string` 79 | 80 | Name and version of the program used to create the .torrent 81 | 82 | [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=the%20author%20%28string%29-,created%20by,-%3A%20%28optional%29%20name%20and) 83 | 84 | #### Defined in 85 | 86 | [src/create.ts:357](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L357) 87 | 88 | ___ 89 | 90 | ### creation date 91 | 92 | • `Optional` **creation date**: `number` 93 | 94 | The creation time of the torrent, 95 | in standard UNIX epoch format 96 | 97 | [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=is%20here.-,creation%20date,-%3A%20%28optional%29%20the%20creation) 98 | 99 | #### Defined in 100 | 101 | [src/create.ts:364](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L364) 102 | -------------------------------------------------------------------------------- /docs/interfaces/MetaInfoHybrid.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / MetaInfoHybrid 2 | 3 | # Interface: MetaInfoHybrid 4 | 5 | hybrid meta info 6 | 7 | ## Hierarchy 8 | 9 | - [`MetaInfoBase`](MetaInfoBase.md) 10 | 11 | ↳ **`MetaInfoHybrid`** 12 | 13 | ## Table of contents 14 | 15 | ### Properties 16 | 17 | - [announce](MetaInfoHybrid.md#announce) 18 | - [announce-list](MetaInfoHybrid.md#announce-list) 19 | - [comment](MetaInfoHybrid.md#comment) 20 | - [created by](MetaInfoHybrid.md#created by) 21 | - [creation date](MetaInfoHybrid.md#creation date) 22 | - [info](MetaInfoHybrid.md#info) 23 | - [piece layers](MetaInfoHybrid.md#piece layers) 24 | 25 | ## Properties 26 | 27 | ### announce 28 | 29 | • `Optional` **announce**: `string` 30 | 31 | The URL of the tracker 32 | 33 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=the%20following%20keys%3A-,announce,-The%20URL%20of) 34 | | 35 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=the%20following%20keys%3A-,announce,-The%20URL%20of) 36 | 37 | #### Inherited from 38 | 39 | [MetaInfoBase](MetaInfoBase.md).[announce](MetaInfoBase.md#announce) 40 | 41 | #### Defined in 42 | 43 | [src/create.ts:338](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L338) 44 | 45 | ___ 46 | 47 | ### announce-list 48 | 49 | • `Optional` **announce-list**: `string`[][] 50 | 51 | This key will refer to a list of lists of URLs, 52 | and will contain a list of tiers of announces 53 | 54 | [BEP 12](http://bittorrent.org/beps/bep_0012.html#:~:text=This%20key%20will%20refer%20to%20a%20list%20of%20lists%20of%20URLs%2C%20and%20will%20contain%20a%20list%20of%20tiers%20of%20announces) 55 | 56 | #### Inherited from 57 | 58 | [MetaInfoBase](MetaInfoBase.md).[announce-list](MetaInfoBase.md#announce-list) 59 | 60 | #### Defined in 61 | 62 | [src/create.ts:345](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L345) 63 | 64 | ___ 65 | 66 | ### comment 67 | 68 | • `Optional` **comment**: `string` 69 | 70 | Free-form textual comments of the author 71 | 72 | [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=00%3A00%20UTC%29-,comment,-%3A%20%28optional%29%20free%2Dform) 73 | 74 | #### Inherited from 75 | 76 | [MetaInfoBase](MetaInfoBase.md).[comment](MetaInfoBase.md#comment) 77 | 78 | #### Defined in 79 | 80 | [src/create.ts:351](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L351) 81 | 82 | ___ 83 | 84 | ### created by 85 | 86 | • `Optional` **created by**: `string` 87 | 88 | Name and version of the program used to create the .torrent 89 | 90 | [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=the%20author%20%28string%29-,created%20by,-%3A%20%28optional%29%20name%20and) 91 | 92 | #### Inherited from 93 | 94 | [MetaInfoBase](MetaInfoBase.md).[created by](MetaInfoBase.md#created by) 95 | 96 | #### Defined in 97 | 98 | [src/create.ts:357](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L357) 99 | 100 | ___ 101 | 102 | ### creation date 103 | 104 | • `Optional` **creation date**: `number` 105 | 106 | The creation time of the torrent, 107 | in standard UNIX epoch format 108 | 109 | [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=is%20here.-,creation%20date,-%3A%20%28optional%29%20the%20creation) 110 | 111 | #### Inherited from 112 | 113 | [MetaInfoBase](MetaInfoBase.md).[creation date](MetaInfoBase.md#creation date) 114 | 115 | #### Defined in 116 | 117 | [src/create.ts:364](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L364) 118 | 119 | ___ 120 | 121 | ### info 122 | 123 | • **info**: [`InfoHybrid`](../modules.md#infohybrid) 124 | 125 | #### Defined in 126 | 127 | [src/create.ts:386](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L386) 128 | 129 | ___ 130 | 131 | ### piece layers 132 | 133 | • `Optional` **piece layers**: [`PieceLayers`](../modules.md#piecelayers) 134 | 135 | #### Defined in 136 | 137 | [src/create.ts:387](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L387) 138 | -------------------------------------------------------------------------------- /docs/interfaces/MetaInfoV1.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / MetaInfoV1 2 | 3 | # Interface: MetaInfoV1 4 | 5 | v1 meta info 6 | 7 | ## Hierarchy 8 | 9 | - [`MetaInfoBase`](MetaInfoBase.md) 10 | 11 | ↳ **`MetaInfoV1`** 12 | 13 | ## Table of contents 14 | 15 | ### Properties 16 | 17 | - [announce](MetaInfoV1.md#announce) 18 | - [announce-list](MetaInfoV1.md#announce-list) 19 | - [comment](MetaInfoV1.md#comment) 20 | - [created by](MetaInfoV1.md#created by) 21 | - [creation date](MetaInfoV1.md#creation date) 22 | - [info](MetaInfoV1.md#info) 23 | 24 | ## Properties 25 | 26 | ### announce 27 | 28 | • `Optional` **announce**: `string` 29 | 30 | The URL of the tracker 31 | 32 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=the%20following%20keys%3A-,announce,-The%20URL%20of) 33 | | 34 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=the%20following%20keys%3A-,announce,-The%20URL%20of) 35 | 36 | #### Inherited from 37 | 38 | [MetaInfoBase](MetaInfoBase.md).[announce](MetaInfoBase.md#announce) 39 | 40 | #### Defined in 41 | 42 | [src/create.ts:338](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L338) 43 | 44 | ___ 45 | 46 | ### announce-list 47 | 48 | • `Optional` **announce-list**: `string`[][] 49 | 50 | This key will refer to a list of lists of URLs, 51 | and will contain a list of tiers of announces 52 | 53 | [BEP 12](http://bittorrent.org/beps/bep_0012.html#:~:text=This%20key%20will%20refer%20to%20a%20list%20of%20lists%20of%20URLs%2C%20and%20will%20contain%20a%20list%20of%20tiers%20of%20announces) 54 | 55 | #### Inherited from 56 | 57 | [MetaInfoBase](MetaInfoBase.md).[announce-list](MetaInfoBase.md#announce-list) 58 | 59 | #### Defined in 60 | 61 | [src/create.ts:345](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L345) 62 | 63 | ___ 64 | 65 | ### comment 66 | 67 | • `Optional` **comment**: `string` 68 | 69 | Free-form textual comments of the author 70 | 71 | [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=00%3A00%20UTC%29-,comment,-%3A%20%28optional%29%20free%2Dform) 72 | 73 | #### Inherited from 74 | 75 | [MetaInfoBase](MetaInfoBase.md).[comment](MetaInfoBase.md#comment) 76 | 77 | #### Defined in 78 | 79 | [src/create.ts:351](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L351) 80 | 81 | ___ 82 | 83 | ### created by 84 | 85 | • `Optional` **created by**: `string` 86 | 87 | Name and version of the program used to create the .torrent 88 | 89 | [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=the%20author%20%28string%29-,created%20by,-%3A%20%28optional%29%20name%20and) 90 | 91 | #### Inherited from 92 | 93 | [MetaInfoBase](MetaInfoBase.md).[created by](MetaInfoBase.md#created by) 94 | 95 | #### Defined in 96 | 97 | [src/create.ts:357](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L357) 98 | 99 | ___ 100 | 101 | ### creation date 102 | 103 | • `Optional` **creation date**: `number` 104 | 105 | The creation time of the torrent, 106 | in standard UNIX epoch format 107 | 108 | [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=is%20here.-,creation%20date,-%3A%20%28optional%29%20the%20creation) 109 | 110 | #### Inherited from 111 | 112 | [MetaInfoBase](MetaInfoBase.md).[creation date](MetaInfoBase.md#creation date) 113 | 114 | #### Defined in 115 | 116 | [src/create.ts:364](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L364) 117 | 118 | ___ 119 | 120 | ### info 121 | 122 | • **info**: [`InfoV1`](../modules.md#infov1) 123 | 124 | #### Defined in 125 | 126 | [src/create.ts:371](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L371) 127 | -------------------------------------------------------------------------------- /docs/interfaces/MetaInfoV2.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / MetaInfoV2 2 | 3 | # Interface: MetaInfoV2 4 | 5 | v2 meta info 6 | 7 | ## Hierarchy 8 | 9 | - [`MetaInfoBase`](MetaInfoBase.md) 10 | 11 | ↳ **`MetaInfoV2`** 12 | 13 | ## Table of contents 14 | 15 | ### Properties 16 | 17 | - [announce](MetaInfoV2.md#announce) 18 | - [announce-list](MetaInfoV2.md#announce-list) 19 | - [comment](MetaInfoV2.md#comment) 20 | - [created by](MetaInfoV2.md#created by) 21 | - [creation date](MetaInfoV2.md#creation date) 22 | - [info](MetaInfoV2.md#info) 23 | - [piece layers](MetaInfoV2.md#piece layers) 24 | 25 | ## Properties 26 | 27 | ### announce 28 | 29 | • `Optional` **announce**: `string` 30 | 31 | The URL of the tracker 32 | 33 | [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=the%20following%20keys%3A-,announce,-The%20URL%20of) 34 | | 35 | [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=the%20following%20keys%3A-,announce,-The%20URL%20of) 36 | 37 | #### Inherited from 38 | 39 | [MetaInfoBase](MetaInfoBase.md).[announce](MetaInfoBase.md#announce) 40 | 41 | #### Defined in 42 | 43 | [src/create.ts:338](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L338) 44 | 45 | ___ 46 | 47 | ### announce-list 48 | 49 | • `Optional` **announce-list**: `string`[][] 50 | 51 | This key will refer to a list of lists of URLs, 52 | and will contain a list of tiers of announces 53 | 54 | [BEP 12](http://bittorrent.org/beps/bep_0012.html#:~:text=This%20key%20will%20refer%20to%20a%20list%20of%20lists%20of%20URLs%2C%20and%20will%20contain%20a%20list%20of%20tiers%20of%20announces) 55 | 56 | #### Inherited from 57 | 58 | [MetaInfoBase](MetaInfoBase.md).[announce-list](MetaInfoBase.md#announce-list) 59 | 60 | #### Defined in 61 | 62 | [src/create.ts:345](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L345) 63 | 64 | ___ 65 | 66 | ### comment 67 | 68 | • `Optional` **comment**: `string` 69 | 70 | Free-form textual comments of the author 71 | 72 | [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=00%3A00%20UTC%29-,comment,-%3A%20%28optional%29%20free%2Dform) 73 | 74 | #### Inherited from 75 | 76 | [MetaInfoBase](MetaInfoBase.md).[comment](MetaInfoBase.md#comment) 77 | 78 | #### Defined in 79 | 80 | [src/create.ts:351](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L351) 81 | 82 | ___ 83 | 84 | ### created by 85 | 86 | • `Optional` **created by**: `string` 87 | 88 | Name and version of the program used to create the .torrent 89 | 90 | [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=the%20author%20%28string%29-,created%20by,-%3A%20%28optional%29%20name%20and) 91 | 92 | #### Inherited from 93 | 94 | [MetaInfoBase](MetaInfoBase.md).[created by](MetaInfoBase.md#created by) 95 | 96 | #### Defined in 97 | 98 | [src/create.ts:357](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L357) 99 | 100 | ___ 101 | 102 | ### creation date 103 | 104 | • `Optional` **creation date**: `number` 105 | 106 | The creation time of the torrent, 107 | in standard UNIX epoch format 108 | 109 | [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=is%20here.-,creation%20date,-%3A%20%28optional%29%20the%20creation) 110 | 111 | #### Inherited from 112 | 113 | [MetaInfoBase](MetaInfoBase.md).[creation date](MetaInfoBase.md#creation date) 114 | 115 | #### Defined in 116 | 117 | [src/create.ts:364](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L364) 118 | 119 | ___ 120 | 121 | ### info 122 | 123 | • **info**: [`InfoV2`](InfoV2.md) 124 | 125 | #### Defined in 126 | 127 | [src/create.ts:378](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L378) 128 | 129 | ___ 130 | 131 | ### piece layers 132 | 133 | • `Optional` **piece layers**: [`PieceLayers`](../modules.md#piecelayers) 134 | 135 | #### Defined in 136 | 137 | [src/create.ts:379](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L379) 138 | -------------------------------------------------------------------------------- /docs/interfaces/TorrentOptionsBase.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / TorrentOptionsBase 2 | 3 | # Interface: TorrentOptionsBase 4 | 5 | base torrent options 6 | 7 | ## Hierarchy 8 | 9 | - **`TorrentOptionsBase`** 10 | 11 | ↳ [`TorrentOptionsV1`](TorrentOptionsV1.md) 12 | 13 | ↳ [`TorrentOptionsV2`](TorrentOptionsV2.md) 14 | 15 | ↳ [`TorrentOptionsHybrid`](TorrentOptionsHybrid.md) 16 | 17 | ## Table of contents 18 | 19 | ### Properties 20 | 21 | - [addCreatedBy](TorrentOptionsBase.md#addcreatedby) 22 | - [addCreationDate](TorrentOptionsBase.md#addcreationdate) 23 | - [announce](TorrentOptionsBase.md#announce) 24 | - [announceList](TorrentOptionsBase.md#announcelist) 25 | - [blockLength](TorrentOptionsBase.md#blocklength) 26 | - [comment](TorrentOptionsBase.md#comment) 27 | - [isPrivate](TorrentOptionsBase.md#isprivate) 28 | - [name](TorrentOptionsBase.md#name) 29 | - [pieceLength](TorrentOptionsBase.md#piecelength) 30 | - [source](TorrentOptionsBase.md#source) 31 | - [urlList](TorrentOptionsBase.md#urllist) 32 | 33 | ## Properties 34 | 35 | ### addCreatedBy 36 | 37 | • `Optional` **addCreatedBy**: `boolean` 38 | 39 | add created by whom 40 | 41 | #### Defined in 42 | 43 | [src/create.ts:71](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L71) 44 | 45 | ___ 46 | 47 | ### addCreationDate 48 | 49 | • `Optional` **addCreationDate**: `boolean` 50 | 51 | add creation date 52 | 53 | #### Defined in 54 | 55 | [src/create.ts:75](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L75) 56 | 57 | ___ 58 | 59 | ### announce 60 | 61 | • `Optional` **announce**: `string` 62 | 63 | announce url 64 | 65 | #### Defined in 66 | 67 | [src/create.ts:79](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L79) 68 | 69 | ___ 70 | 71 | ### announceList 72 | 73 | • `Optional` **announceList**: `string`[][] 74 | 75 | announce url list 76 | 77 | #### Defined in 78 | 79 | [src/create.ts:83](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L83) 80 | 81 | ___ 82 | 83 | ### blockLength 84 | 85 | • `Optional` **blockLength**: `number` 86 | 87 | block length: 16384 (16 KiB) by default 88 | (do not alter this value) 89 | 90 | #### Defined in 91 | 92 | [src/create.ts:88](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L88) 93 | 94 | ___ 95 | 96 | ### comment 97 | 98 | • `Optional` **comment**: `string` 99 | 100 | comment 101 | 102 | #### Defined in 103 | 104 | [src/create.ts:92](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L92) 105 | 106 | ___ 107 | 108 | ### isPrivate 109 | 110 | • `Optional` **isPrivate**: `boolean` 111 | 112 | is private torrent 113 | 114 | #### Defined in 115 | 116 | [src/create.ts:96](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L96) 117 | 118 | ___ 119 | 120 | ### name 121 | 122 | • `Optional` **name**: `string` 123 | 124 | torrent name 125 | 126 | #### Defined in 127 | 128 | [src/create.ts:100](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L100) 129 | 130 | ___ 131 | 132 | ### pieceLength 133 | 134 | • `Optional` **pieceLength**: `number` 135 | 136 | piece length: a power of 2 number, 137 | will automatically calculate when this value is missing 138 | 139 | #### Defined in 140 | 141 | [src/create.ts:105](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L105) 142 | 143 | ___ 144 | 145 | ### source 146 | 147 | • `Optional` **source**: `string` 148 | 149 | source 150 | 151 | #### Defined in 152 | 153 | [src/create.ts:109](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L109) 154 | 155 | ___ 156 | 157 | ### urlList 158 | 159 | • `Optional` **urlList**: `string`[] 160 | 161 | url list 162 | 163 | [BEP 9](http://www.bittorrent.org/beps/bep_0019.html#metadata-extension) 164 | 165 | #### Defined in 166 | 167 | [src/create.ts:115](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L115) 168 | -------------------------------------------------------------------------------- /docs/interfaces/TorrentOptionsHybrid.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / TorrentOptionsHybrid 2 | 3 | # Interface: TorrentOptionsHybrid 4 | 5 | v1 + v2 hybrid torrent options 6 | 7 | ## Hierarchy 8 | 9 | - [`TorrentOptionsBase`](TorrentOptionsBase.md) 10 | 11 | ↳ **`TorrentOptionsHybrid`** 12 | 13 | ## Table of contents 14 | 15 | ### Properties 16 | 17 | - [addCreatedBy](TorrentOptionsHybrid.md#addcreatedby) 18 | - [addCreationDate](TorrentOptionsHybrid.md#addcreationdate) 19 | - [announce](TorrentOptionsHybrid.md#announce) 20 | - [announceList](TorrentOptionsHybrid.md#announcelist) 21 | - [blockLength](TorrentOptionsHybrid.md#blocklength) 22 | - [comment](TorrentOptionsHybrid.md#comment) 23 | - [isPrivate](TorrentOptionsHybrid.md#isprivate) 24 | - [metaVersion](TorrentOptionsHybrid.md#metaversion) 25 | - [name](TorrentOptionsHybrid.md#name) 26 | - [pieceLength](TorrentOptionsHybrid.md#piecelength) 27 | - [source](TorrentOptionsHybrid.md#source) 28 | - [type](TorrentOptionsHybrid.md#type) 29 | - [urlList](TorrentOptionsHybrid.md#urllist) 30 | 31 | ## Properties 32 | 33 | ### addCreatedBy 34 | 35 | • `Optional` **addCreatedBy**: `boolean` 36 | 37 | add created by whom 38 | 39 | #### Inherited from 40 | 41 | [TorrentOptionsBase](TorrentOptionsBase.md).[addCreatedBy](TorrentOptionsBase.md#addcreatedby) 42 | 43 | #### Defined in 44 | 45 | [src/create.ts:71](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L71) 46 | 47 | ___ 48 | 49 | ### addCreationDate 50 | 51 | • `Optional` **addCreationDate**: `boolean` 52 | 53 | add creation date 54 | 55 | #### Inherited from 56 | 57 | [TorrentOptionsBase](TorrentOptionsBase.md).[addCreationDate](TorrentOptionsBase.md#addcreationdate) 58 | 59 | #### Defined in 60 | 61 | [src/create.ts:75](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L75) 62 | 63 | ___ 64 | 65 | ### announce 66 | 67 | • `Optional` **announce**: `string` 68 | 69 | announce url 70 | 71 | #### Inherited from 72 | 73 | [TorrentOptionsBase](TorrentOptionsBase.md).[announce](TorrentOptionsBase.md#announce) 74 | 75 | #### Defined in 76 | 77 | [src/create.ts:79](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L79) 78 | 79 | ___ 80 | 81 | ### announceList 82 | 83 | • `Optional` **announceList**: `string`[][] 84 | 85 | announce url list 86 | 87 | #### Inherited from 88 | 89 | [TorrentOptionsBase](TorrentOptionsBase.md).[announceList](TorrentOptionsBase.md#announcelist) 90 | 91 | #### Defined in 92 | 93 | [src/create.ts:83](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L83) 94 | 95 | ___ 96 | 97 | ### blockLength 98 | 99 | • `Optional` **blockLength**: `number` 100 | 101 | block length: 16384 (16 KiB) by default 102 | (do not alter this value) 103 | 104 | #### Inherited from 105 | 106 | [TorrentOptionsBase](TorrentOptionsBase.md).[blockLength](TorrentOptionsBase.md#blocklength) 107 | 108 | #### Defined in 109 | 110 | [src/create.ts:88](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L88) 111 | 112 | ___ 113 | 114 | ### comment 115 | 116 | • `Optional` **comment**: `string` 117 | 118 | comment 119 | 120 | #### Inherited from 121 | 122 | [TorrentOptionsBase](TorrentOptionsBase.md).[comment](TorrentOptionsBase.md#comment) 123 | 124 | #### Defined in 125 | 126 | [src/create.ts:92](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L92) 127 | 128 | ___ 129 | 130 | ### isPrivate 131 | 132 | • `Optional` **isPrivate**: `boolean` 133 | 134 | is private torrent 135 | 136 | #### Inherited from 137 | 138 | [TorrentOptionsBase](TorrentOptionsBase.md).[isPrivate](TorrentOptionsBase.md#isprivate) 139 | 140 | #### Defined in 141 | 142 | [src/create.ts:96](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L96) 143 | 144 | ___ 145 | 146 | ### metaVersion 147 | 148 | • `Optional` **metaVersion**: ``2`` 149 | 150 | meta version 151 | 152 | #### Defined in 153 | 154 | [src/create.ts:161](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L161) 155 | 156 | ___ 157 | 158 | ### name 159 | 160 | • `Optional` **name**: `string` 161 | 162 | torrent name 163 | 164 | #### Inherited from 165 | 166 | [TorrentOptionsBase](TorrentOptionsBase.md).[name](TorrentOptionsBase.md#name) 167 | 168 | #### Defined in 169 | 170 | [src/create.ts:100](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L100) 171 | 172 | ___ 173 | 174 | ### pieceLength 175 | 176 | • `Optional` **pieceLength**: `number` 177 | 178 | piece length: a power of 2 number, 179 | will automatically calculate when this value is missing 180 | 181 | #### Inherited from 182 | 183 | [TorrentOptionsBase](TorrentOptionsBase.md).[pieceLength](TorrentOptionsBase.md#piecelength) 184 | 185 | #### Defined in 186 | 187 | [src/create.ts:105](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L105) 188 | 189 | ___ 190 | 191 | ### source 192 | 193 | • `Optional` **source**: `string` 194 | 195 | source 196 | 197 | #### Inherited from 198 | 199 | [TorrentOptionsBase](TorrentOptionsBase.md).[source](TorrentOptionsBase.md#source) 200 | 201 | #### Defined in 202 | 203 | [src/create.ts:109](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L109) 204 | 205 | ___ 206 | 207 | ### type 208 | 209 | • **type**: [`HYBRID`](../enums/TorrentType.md#hybrid) 210 | 211 | torrent type: HYBRID 212 | 213 | #### Defined in 214 | 215 | [src/create.ts:165](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L165) 216 | 217 | ___ 218 | 219 | ### urlList 220 | 221 | • `Optional` **urlList**: `string`[] 222 | 223 | url list 224 | 225 | [BEP 9](http://www.bittorrent.org/beps/bep_0019.html#metadata-extension) 226 | 227 | #### Inherited from 228 | 229 | [TorrentOptionsBase](TorrentOptionsBase.md).[urlList](TorrentOptionsBase.md#urllist) 230 | 231 | #### Defined in 232 | 233 | [src/create.ts:115](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L115) 234 | -------------------------------------------------------------------------------- /docs/interfaces/TorrentOptionsV1.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / TorrentOptionsV1 2 | 3 | # Interface: TorrentOptionsV1 4 | 5 | v1 torrent options 6 | 7 | ## Hierarchy 8 | 9 | - [`TorrentOptionsBase`](TorrentOptionsBase.md) 10 | 11 | ↳ **`TorrentOptionsV1`** 12 | 13 | ## Table of contents 14 | 15 | ### Properties 16 | 17 | - [addCreatedBy](TorrentOptionsV1.md#addcreatedby) 18 | - [addCreationDate](TorrentOptionsV1.md#addcreationdate) 19 | - [addPaddingFiles](TorrentOptionsV1.md#addpaddingfiles) 20 | - [announce](TorrentOptionsV1.md#announce) 21 | - [announceList](TorrentOptionsV1.md#announcelist) 22 | - [blockLength](TorrentOptionsV1.md#blocklength) 23 | - [comment](TorrentOptionsV1.md#comment) 24 | - [isPrivate](TorrentOptionsV1.md#isprivate) 25 | - [name](TorrentOptionsV1.md#name) 26 | - [pieceLength](TorrentOptionsV1.md#piecelength) 27 | - [sortFiles](TorrentOptionsV1.md#sortfiles) 28 | - [source](TorrentOptionsV1.md#source) 29 | - [type](TorrentOptionsV1.md#type) 30 | - [urlList](TorrentOptionsV1.md#urllist) 31 | 32 | ## Properties 33 | 34 | ### addCreatedBy 35 | 36 | • `Optional` **addCreatedBy**: `boolean` 37 | 38 | add created by whom 39 | 40 | #### Inherited from 41 | 42 | [TorrentOptionsBase](TorrentOptionsBase.md).[addCreatedBy](TorrentOptionsBase.md#addcreatedby) 43 | 44 | #### Defined in 45 | 46 | [src/create.ts:71](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L71) 47 | 48 | ___ 49 | 50 | ### addCreationDate 51 | 52 | • `Optional` **addCreationDate**: `boolean` 53 | 54 | add creation date 55 | 56 | #### Inherited from 57 | 58 | [TorrentOptionsBase](TorrentOptionsBase.md).[addCreationDate](TorrentOptionsBase.md#addcreationdate) 59 | 60 | #### Defined in 61 | 62 | [src/create.ts:75](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L75) 63 | 64 | ___ 65 | 66 | ### addPaddingFiles 67 | 68 | • `Optional` **addPaddingFiles**: `boolean` 69 | 70 | add padding files 71 | this option is only available in V1 type torrents 72 | because files are forcibly padded in HYBRID type 73 | and don't need padding in V2 type 74 | 75 | #### Defined in 76 | 77 | [src/create.ts:128](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L128) 78 | 79 | ___ 80 | 81 | ### announce 82 | 83 | • `Optional` **announce**: `string` 84 | 85 | announce url 86 | 87 | #### Inherited from 88 | 89 | [TorrentOptionsBase](TorrentOptionsBase.md).[announce](TorrentOptionsBase.md#announce) 90 | 91 | #### Defined in 92 | 93 | [src/create.ts:79](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L79) 94 | 95 | ___ 96 | 97 | ### announceList 98 | 99 | • `Optional` **announceList**: `string`[][] 100 | 101 | announce url list 102 | 103 | #### Inherited from 104 | 105 | [TorrentOptionsBase](TorrentOptionsBase.md).[announceList](TorrentOptionsBase.md#announcelist) 106 | 107 | #### Defined in 108 | 109 | [src/create.ts:83](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L83) 110 | 111 | ___ 112 | 113 | ### blockLength 114 | 115 | • `Optional` **blockLength**: `number` 116 | 117 | block length: 16384 (16 KiB) by default 118 | (do not alter this value) 119 | 120 | #### Inherited from 121 | 122 | [TorrentOptionsBase](TorrentOptionsBase.md).[blockLength](TorrentOptionsBase.md#blocklength) 123 | 124 | #### Defined in 125 | 126 | [src/create.ts:88](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L88) 127 | 128 | ___ 129 | 130 | ### comment 131 | 132 | • `Optional` **comment**: `string` 133 | 134 | comment 135 | 136 | #### Inherited from 137 | 138 | [TorrentOptionsBase](TorrentOptionsBase.md).[comment](TorrentOptionsBase.md#comment) 139 | 140 | #### Defined in 141 | 142 | [src/create.ts:92](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L92) 143 | 144 | ___ 145 | 146 | ### isPrivate 147 | 148 | • `Optional` **isPrivate**: `boolean` 149 | 150 | is private torrent 151 | 152 | #### Inherited from 153 | 154 | [TorrentOptionsBase](TorrentOptionsBase.md).[isPrivate](TorrentOptionsBase.md#isprivate) 155 | 156 | #### Defined in 157 | 158 | [src/create.ts:96](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L96) 159 | 160 | ___ 161 | 162 | ### name 163 | 164 | • `Optional` **name**: `string` 165 | 166 | torrent name 167 | 168 | #### Inherited from 169 | 170 | [TorrentOptionsBase](TorrentOptionsBase.md).[name](TorrentOptionsBase.md#name) 171 | 172 | #### Defined in 173 | 174 | [src/create.ts:100](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L100) 175 | 176 | ___ 177 | 178 | ### pieceLength 179 | 180 | • `Optional` **pieceLength**: `number` 181 | 182 | piece length: a power of 2 number, 183 | will automatically calculate when this value is missing 184 | 185 | #### Inherited from 186 | 187 | [TorrentOptionsBase](TorrentOptionsBase.md).[pieceLength](TorrentOptionsBase.md#piecelength) 188 | 189 | #### Defined in 190 | 191 | [src/create.ts:105](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L105) 192 | 193 | ___ 194 | 195 | ### sortFiles 196 | 197 | • `Optional` **sortFiles**: `boolean` 198 | 199 | sort file: only available in V1 type 200 | files are forcibly sorted in V2 and HYBRID type 201 | 202 | #### Defined in 203 | 204 | [src/create.ts:133](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L133) 205 | 206 | ___ 207 | 208 | ### source 209 | 210 | • `Optional` **source**: `string` 211 | 212 | source 213 | 214 | #### Inherited from 215 | 216 | [TorrentOptionsBase](TorrentOptionsBase.md).[source](TorrentOptionsBase.md#source) 217 | 218 | #### Defined in 219 | 220 | [src/create.ts:109](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L109) 221 | 222 | ___ 223 | 224 | ### type 225 | 226 | • **type**: [`V1`](../enums/TorrentType.md#v1) 227 | 228 | torrent type: V1 229 | 230 | #### Defined in 231 | 232 | [src/create.ts:137](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L137) 233 | 234 | ___ 235 | 236 | ### urlList 237 | 238 | • `Optional` **urlList**: `string`[] 239 | 240 | url list 241 | 242 | [BEP 9](http://www.bittorrent.org/beps/bep_0019.html#metadata-extension) 243 | 244 | #### Inherited from 245 | 246 | [TorrentOptionsBase](TorrentOptionsBase.md).[urlList](TorrentOptionsBase.md#urllist) 247 | 248 | #### Defined in 249 | 250 | [src/create.ts:115](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L115) 251 | -------------------------------------------------------------------------------- /docs/interfaces/TorrentOptionsV2.md: -------------------------------------------------------------------------------- 1 | [torrefy](../README.md) / [Exports](../modules.md) / TorrentOptionsV2 2 | 3 | # Interface: TorrentOptionsV2 4 | 5 | v2 torrent options 6 | 7 | ## Hierarchy 8 | 9 | - [`TorrentOptionsBase`](TorrentOptionsBase.md) 10 | 11 | ↳ **`TorrentOptionsV2`** 12 | 13 | ## Table of contents 14 | 15 | ### Properties 16 | 17 | - [addCreatedBy](TorrentOptionsV2.md#addcreatedby) 18 | - [addCreationDate](TorrentOptionsV2.md#addcreationdate) 19 | - [announce](TorrentOptionsV2.md#announce) 20 | - [announceList](TorrentOptionsV2.md#announcelist) 21 | - [blockLength](TorrentOptionsV2.md#blocklength) 22 | - [comment](TorrentOptionsV2.md#comment) 23 | - [isPrivate](TorrentOptionsV2.md#isprivate) 24 | - [metaVersion](TorrentOptionsV2.md#metaversion) 25 | - [name](TorrentOptionsV2.md#name) 26 | - [pieceLength](TorrentOptionsV2.md#piecelength) 27 | - [source](TorrentOptionsV2.md#source) 28 | - [type](TorrentOptionsV2.md#type) 29 | - [urlList](TorrentOptionsV2.md#urllist) 30 | 31 | ## Properties 32 | 33 | ### addCreatedBy 34 | 35 | • `Optional` **addCreatedBy**: `boolean` 36 | 37 | add created by whom 38 | 39 | #### Inherited from 40 | 41 | [TorrentOptionsBase](TorrentOptionsBase.md).[addCreatedBy](TorrentOptionsBase.md#addcreatedby) 42 | 43 | #### Defined in 44 | 45 | [src/create.ts:71](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L71) 46 | 47 | ___ 48 | 49 | ### addCreationDate 50 | 51 | • `Optional` **addCreationDate**: `boolean` 52 | 53 | add creation date 54 | 55 | #### Inherited from 56 | 57 | [TorrentOptionsBase](TorrentOptionsBase.md).[addCreationDate](TorrentOptionsBase.md#addcreationdate) 58 | 59 | #### Defined in 60 | 61 | [src/create.ts:75](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L75) 62 | 63 | ___ 64 | 65 | ### announce 66 | 67 | • `Optional` **announce**: `string` 68 | 69 | announce url 70 | 71 | #### Inherited from 72 | 73 | [TorrentOptionsBase](TorrentOptionsBase.md).[announce](TorrentOptionsBase.md#announce) 74 | 75 | #### Defined in 76 | 77 | [src/create.ts:79](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L79) 78 | 79 | ___ 80 | 81 | ### announceList 82 | 83 | • `Optional` **announceList**: `string`[][] 84 | 85 | announce url list 86 | 87 | #### Inherited from 88 | 89 | [TorrentOptionsBase](TorrentOptionsBase.md).[announceList](TorrentOptionsBase.md#announcelist) 90 | 91 | #### Defined in 92 | 93 | [src/create.ts:83](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L83) 94 | 95 | ___ 96 | 97 | ### blockLength 98 | 99 | • `Optional` **blockLength**: `number` 100 | 101 | block length: 16384 (16 KiB) by default 102 | (do not alter this value) 103 | 104 | #### Inherited from 105 | 106 | [TorrentOptionsBase](TorrentOptionsBase.md).[blockLength](TorrentOptionsBase.md#blocklength) 107 | 108 | #### Defined in 109 | 110 | [src/create.ts:88](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L88) 111 | 112 | ___ 113 | 114 | ### comment 115 | 116 | • `Optional` **comment**: `string` 117 | 118 | comment 119 | 120 | #### Inherited from 121 | 122 | [TorrentOptionsBase](TorrentOptionsBase.md).[comment](TorrentOptionsBase.md#comment) 123 | 124 | #### Defined in 125 | 126 | [src/create.ts:92](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L92) 127 | 128 | ___ 129 | 130 | ### isPrivate 131 | 132 | • `Optional` **isPrivate**: `boolean` 133 | 134 | is private torrent 135 | 136 | #### Inherited from 137 | 138 | [TorrentOptionsBase](TorrentOptionsBase.md).[isPrivate](TorrentOptionsBase.md#isprivate) 139 | 140 | #### Defined in 141 | 142 | [src/create.ts:96](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L96) 143 | 144 | ___ 145 | 146 | ### metaVersion 147 | 148 | • `Optional` **metaVersion**: ``2`` 149 | 150 | meta version 151 | 152 | #### Defined in 153 | 154 | [src/create.ts:147](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L147) 155 | 156 | ___ 157 | 158 | ### name 159 | 160 | • `Optional` **name**: `string` 161 | 162 | torrent name 163 | 164 | #### Inherited from 165 | 166 | [TorrentOptionsBase](TorrentOptionsBase.md).[name](TorrentOptionsBase.md#name) 167 | 168 | #### Defined in 169 | 170 | [src/create.ts:100](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L100) 171 | 172 | ___ 173 | 174 | ### pieceLength 175 | 176 | • `Optional` **pieceLength**: `number` 177 | 178 | piece length: a power of 2 number, 179 | will automatically calculate when this value is missing 180 | 181 | #### Inherited from 182 | 183 | [TorrentOptionsBase](TorrentOptionsBase.md).[pieceLength](TorrentOptionsBase.md#piecelength) 184 | 185 | #### Defined in 186 | 187 | [src/create.ts:105](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L105) 188 | 189 | ___ 190 | 191 | ### source 192 | 193 | • `Optional` **source**: `string` 194 | 195 | source 196 | 197 | #### Inherited from 198 | 199 | [TorrentOptionsBase](TorrentOptionsBase.md).[source](TorrentOptionsBase.md#source) 200 | 201 | #### Defined in 202 | 203 | [src/create.ts:109](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L109) 204 | 205 | ___ 206 | 207 | ### type 208 | 209 | • **type**: [`V2`](../enums/TorrentType.md#v2) 210 | 211 | torrent type: V2 212 | 213 | #### Defined in 214 | 215 | [src/create.ts:151](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L151) 216 | 217 | ___ 218 | 219 | ### urlList 220 | 221 | • `Optional` **urlList**: `string`[] 222 | 223 | url list 224 | 225 | [BEP 9](http://www.bittorrent.org/beps/bep_0019.html#metadata-extension) 226 | 227 | #### Inherited from 228 | 229 | [TorrentOptionsBase](TorrentOptionsBase.md).[urlList](TorrentOptionsBase.md#urllist) 230 | 231 | #### Defined in 232 | 233 | [src/create.ts:115](https://github.com/Sec-ant/bepjs/blob/f9eb2df/src/create.ts#L115) 234 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "torrefy", 3 | "version": "2.0.8", 4 | "description": "An ESM package that uses Web Streams API to create v1, v2 or hybrid torrents in your web browser.", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "files": [ 9 | "./dist/**/*.ts", 10 | "./dist/**/*.js" 11 | ], 12 | "exports": { 13 | "import": "./dist/index.js", 14 | "browser": "./dist/index.umd.js", 15 | "default": "./dist/index.js" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/Sec-ant/torrefy.git" 20 | }, 21 | "homepage": "https://github.com/Sec-ant/torrefy", 22 | "bugs": { 23 | "url": "https://github.com/Sec-ant/torrefy/issues" 24 | }, 25 | "keywords": [ 26 | "torrent", 27 | "bittorrent", 28 | "bencode", 29 | "bdecode", 30 | "metainfo", 31 | "infohash", 32 | "webtorrent", 33 | "esm", 34 | "webstream" 35 | ], 36 | "author": "Ze-Zheng Wu", 37 | "license": "MIT", 38 | "scripts": { 39 | "clean": "rm -fr ./dist/*.*s", 40 | "lint": "eslint ./src", 41 | "build": "npm run lint && npm run clean && webpack", 42 | "serve": "http-server -c-1 dist", 43 | "docs": "typedoc --plugin typedoc-plugin-markdown ./src/index.ts" 44 | }, 45 | "devDependencies": { 46 | "@types/uuid": "^9.0.0", 47 | "@types/wicg-file-system-access": "^2020.9.5", 48 | "@typescript-eslint/eslint-plugin": "^5.45.1", 49 | "@typescript-eslint/parser": "^5.45.1", 50 | "eslint": "^8.29.0", 51 | "http-server": "^14.1.1", 52 | "mocha": "^10.1.0", 53 | "ts-loader": "^9.4.2", 54 | "tslib": "^2.4.1", 55 | "typedoc": "^0.23.21", 56 | "typedoc-plugin-markdown": "^3.14.0", 57 | "typescript": "^4.9.3", 58 | "webpack": "^5.75.0", 59 | "webpack-cli": "^5.0.1" 60 | }, 61 | "dependencies": { 62 | "jssha": "github:Sec-ant/jsSHA#dd69d437ee912b95007a330f9afec7635f83271e", 63 | "uuid": "^9.0.0", 64 | "workbox-streams": "^6.5.4" 65 | }, 66 | "peerDependencies": { 67 | "@sec-ant/trie-map": "^1.1.5" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/create.ts: -------------------------------------------------------------------------------- 1 | import { concatenate as concatenateStreams } from "workbox-streams"; 2 | 3 | import { BlockHasher } from "./transformers/blockHasher.js"; 4 | import { ChunkSplitter } from "./transformers/chunkSplitter.js"; 5 | import { MerkleRootCalculator } from "./transformers/merkleRootCalculator.js"; 6 | import { MerkleTreeBalancer } from "./transformers/merkleTreeBalancer.js"; 7 | import { PieceHasher } from "./transformers/pieceHasher.js"; 8 | import { BObject } from "./utils/codec.js"; 9 | import { 10 | FileAttrs, 11 | FileListV1, 12 | FileTreeDirNode, 13 | FileTreeFileNode, 14 | populateFileTree, 15 | resolveCommonDirAndTorrentName, 16 | } from "./utils/fileTree.js"; 17 | import { FileDirLikes } from "./utils/fileDirLike.js"; 18 | import { nextPowerOfTwo, getTimeStampSecondsNow } from "./utils/misc.js"; 19 | 20 | /** 21 | * support padding attribute on file 22 | */ 23 | declare global { 24 | interface File { 25 | readonly padding?: boolean; 26 | } 27 | } 28 | 29 | /** 30 | * created by: name + version 31 | * inject value when build 32 | */ 33 | declare const CREATED_BY: string; 34 | 35 | /** 36 | * meta version can only be 2 at the time being 37 | */ 38 | export type MetaVersion = 2; 39 | 40 | /** 41 | * torrent type: v1, v2, hybrid 42 | */ 43 | export enum TorrentType { 44 | /** 45 | * v1 torrent 46 | * 47 | * [BEP 3](https://www.bittorrent.org/beps/bep_0003.html) 48 | */ 49 | V1 = "v1", 50 | /** 51 | * v2 torrent 52 | * 53 | * [BEP 52](https://www.bittorrent.org/beps/bep_0052.html) 54 | */ 55 | V2 = "v2", 56 | /** 57 | * v1 + v2 hybrid torrent 58 | * 59 | * [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path) 60 | */ 61 | HYBRID = "hybrid", 62 | } 63 | 64 | /** 65 | * base torrent options 66 | */ 67 | export interface TorrentOptionsBase { 68 | /** 69 | * add created by whom 70 | */ 71 | addCreatedBy?: boolean; 72 | /** 73 | * add creation date 74 | */ 75 | addCreationDate?: boolean; 76 | /** 77 | * announce url 78 | */ 79 | announce?: string; 80 | /** 81 | * announce url list 82 | */ 83 | announceList?: string[][]; 84 | /** 85 | * block length: 16384 (16 KiB) by default 86 | * (do not alter this value) 87 | */ 88 | blockLength?: number; 89 | /** 90 | * comment 91 | */ 92 | comment?: string; 93 | /** 94 | * is private torrent 95 | */ 96 | isPrivate?: boolean; 97 | /** 98 | * torrent name 99 | */ 100 | name?: string; 101 | /** 102 | * piece length: a power of 2 number, 103 | * will automatically calculate when this value is missing 104 | */ 105 | pieceLength?: number; 106 | /** 107 | * source 108 | */ 109 | source?: string; 110 | /** 111 | * url list 112 | * 113 | * [BEP 9](http://www.bittorrent.org/beps/bep_0019.html#metadata-extension) 114 | */ 115 | urlList?: string[]; 116 | } 117 | 118 | /** 119 | * v1 torrent options 120 | */ 121 | export interface TorrentOptionsV1 extends TorrentOptionsBase { 122 | /** 123 | * add padding files 124 | * this option is only available in V1 type torrents 125 | * because files are forcibly padded in HYBRID type 126 | * and don't need padding in V2 type 127 | */ 128 | addPaddingFiles?: boolean; 129 | /** 130 | * sort file: only available in V1 type 131 | * files are forcibly sorted in V2 and HYBRID type 132 | */ 133 | sortFiles?: boolean; 134 | /** 135 | * torrent type: V1 136 | */ 137 | type: TorrentType.V1; 138 | } 139 | 140 | /** 141 | * v2 torrent options 142 | */ 143 | export interface TorrentOptionsV2 extends TorrentOptionsBase { 144 | /** 145 | * meta version 146 | */ 147 | metaVersion?: MetaVersion; 148 | /** 149 | * torrent type: V2 150 | */ 151 | type: TorrentType.V2; 152 | } 153 | 154 | /** 155 | * v1 + v2 hybrid torrent options 156 | */ 157 | export interface TorrentOptionsHybrid extends TorrentOptionsBase { 158 | /** 159 | * meta version 160 | */ 161 | metaVersion?: MetaVersion; 162 | /** 163 | * torrent type: HYBRID 164 | */ 165 | type: TorrentType.HYBRID; 166 | } 167 | 168 | /** 169 | * torrent options 170 | */ 171 | export type TorrentOptions = 172 | T extends TorrentType.V1 173 | ? TorrentOptionsV1 174 | : T extends TorrentType.V2 175 | ? TorrentOptionsV2 176 | : T extends TorrentType.HYBRID 177 | ? TorrentOptionsHybrid 178 | : never; 179 | 180 | type UnrequiredOptions = 181 | | "announce" 182 | | "announceList" 183 | | "comment" 184 | | "name" 185 | | "source" 186 | | "urlList"; 187 | 188 | type InternalTorrentOptionsV1 = TorrentOptionsV1 & 189 | Required>; 190 | 191 | type InternalTorrentOptionsV2 = TorrentOptionsV2 & 192 | Required>; 193 | 194 | type InternalTorrentOptionsHybrid = TorrentOptionsHybrid & 195 | Required>; 196 | 197 | /** 198 | * internal torrent options 199 | */ 200 | type InternalTorrentOptions = 201 | T extends TorrentType.V1 202 | ? InternalTorrentOptionsV1 203 | : T extends TorrentType.V2 204 | ? InternalTorrentOptionsV2 205 | : T extends TorrentType.HYBRID 206 | ? InternalTorrentOptionsHybrid 207 | : never; 208 | 209 | //=================================================================================== 210 | 211 | /** 212 | * info base 213 | */ 214 | export interface InfoBase extends BObject { 215 | /** 216 | * The suggested name to save the file (or directory) as. 217 | * It is purely advisory 218 | * 219 | * [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=The-,name,-key%20maps%20to) 220 | * | 221 | * [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=info%20dictionary-,name,-A%20display%20name) 222 | */ 223 | name: string; 224 | /** 225 | * The number of bytes that each logical piece 226 | * in the peer protocol refers to 227 | * 228 | * [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=is%20purely%20advisory.-,piece%20length,-maps%20to%20the) 229 | * | 230 | * [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=is%20purely%20advisory.-,piece%20length,-The%20number%20of) 231 | */ 232 | ["piece length"]: number; 233 | /** 234 | * is private torrent 235 | */ 236 | private?: boolean; 237 | /** 238 | * source 239 | */ 240 | source?: string; 241 | } 242 | 243 | /** 244 | * v1 info base 245 | */ 246 | export interface InfoBaseV1 extends InfoBase { 247 | /** 248 | * Pieces maps to a string whose length is a multiple of 20 249 | * 250 | * [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=M%20as%20default%29.-,pieces,-maps%20to%20a) 251 | */ 252 | pieces: ArrayBuffer | string; 253 | } 254 | 255 | /** 256 | * v1 single file info 257 | */ 258 | export interface InfoSingleFileV1 extends InfoBaseV1 { 259 | length: number; 260 | } 261 | 262 | /** 263 | * v1 multi file info 264 | */ 265 | export interface InfoMultiFileV1 extends InfoBaseV1 { 266 | files: FileListV1; 267 | } 268 | 269 | /** 270 | * v1 info 271 | */ 272 | export type InfoV1 = InfoSingleFileV1 | InfoMultiFileV1; 273 | 274 | /** 275 | * v2 info 276 | */ 277 | export interface InfoV2 extends InfoBase { 278 | /** 279 | * A tree of dictionaries where dictionary keys 280 | * represent UTF-8 encoded path elements 281 | * 282 | * [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=about%20invalid%20files.-,file%20tree,-A%20tree%20of) 283 | */ 284 | ["file tree"]: FileTreeDirNode; 285 | /** 286 | * An integer value, set to 2 to indicate compatibility 287 | * with the current revision of this specification 288 | * 289 | * [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=an%20alignment%20gap.-,meta%20version,-An%20integer%20value) 290 | */ 291 | ["meta version"]: number; 292 | } 293 | 294 | /** 295 | * hybrid single file info 296 | */ 297 | export interface InfoSingleFileHybrid extends InfoSingleFileV1, InfoV2 {} 298 | 299 | /** 300 | * hybrid multi file info 301 | */ 302 | export interface InfoMultiFileHybrid extends InfoMultiFileV1, InfoV2 {} 303 | 304 | /** 305 | * hybrid info 306 | */ 307 | export type InfoHybrid = InfoSingleFileHybrid | InfoMultiFileHybrid; 308 | 309 | /** 310 | * info 311 | */ 312 | export type Info = T extends TorrentType.V1 313 | ? InfoV1 314 | : T extends TorrentType.V2 315 | ? InfoV2 316 | : T extends TorrentType.HYBRID 317 | ? InfoHybrid 318 | : never; 319 | 320 | //=================================================== 321 | 322 | /** 323 | * v2 piece layers 324 | */ 325 | export type PieceLayers = Map; 326 | 327 | /** 328 | * base meta info 329 | */ 330 | export interface MetaInfoBase extends BObject { 331 | /** 332 | * The URL of the tracker 333 | * 334 | * [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=the%20following%20keys%3A-,announce,-The%20URL%20of) 335 | * | 336 | * [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=the%20following%20keys%3A-,announce,-The%20URL%20of) 337 | */ 338 | announce?: string; 339 | /** 340 | * This key will refer to a list of lists of URLs, 341 | * and will contain a list of tiers of announces 342 | * 343 | * [BEP 12](http://bittorrent.org/beps/bep_0012.html#:~:text=This%20key%20will%20refer%20to%20a%20list%20of%20lists%20of%20URLs%2C%20and%20will%20contain%20a%20list%20of%20tiers%20of%20announces) 344 | */ 345 | ["announce-list"]?: string[][]; 346 | /** 347 | * Free-form textual comments of the author 348 | * 349 | * [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=00%3A00%20UTC%29-,comment,-%3A%20%28optional%29%20free%2Dform) 350 | */ 351 | comment?: string; 352 | /** 353 | * Name and version of the program used to create the .torrent 354 | * 355 | * [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=the%20author%20%28string%29-,created%20by,-%3A%20%28optional%29%20name%20and) 356 | */ 357 | ["created by"]?: string; 358 | /** 359 | * The creation time of the torrent, 360 | * in standard UNIX epoch format 361 | * 362 | * [BitTorrent Specification](https://courses.edsa-project.eu/pluginfile.php/1514/mod_resource/content/0/bitTorrent_part2.htm#:~:text=is%20here.-,creation%20date,-%3A%20%28optional%29%20the%20creation) 363 | */ 364 | ["creation date"]?: number; 365 | } 366 | 367 | /** 368 | * v1 meta info 369 | */ 370 | export interface MetaInfoV1 extends MetaInfoBase { 371 | info: Info; 372 | } 373 | 374 | /** 375 | * v2 meta info 376 | */ 377 | export interface MetaInfoV2 extends MetaInfoBase { 378 | info: Info; 379 | ["piece layers"]?: PieceLayers; 380 | } 381 | 382 | /** 383 | * hybrid meta info 384 | */ 385 | export interface MetaInfoHybrid extends MetaInfoBase { 386 | info: Info; 387 | ["piece layers"]?: PieceLayers; 388 | } 389 | 390 | /** 391 | * meta info 392 | */ 393 | export type MetaInfo = 394 | T extends TorrentType.V1 395 | ? MetaInfoV1 396 | : T extends TorrentType.V2 397 | ? MetaInfoV2 398 | : T extends TorrentType.HYBRID 399 | ? MetaInfoHybrid 400 | : never; 401 | 402 | /** 403 | * common piece lengths 404 | */ 405 | 406 | export enum CommonPieceLength { 407 | "16KB" = 1 << 14, 408 | "32KB" = 1 << 15, 409 | "64KB" = 1 << 16, 410 | "128KB" = 1 << 17, 411 | "256KB" = 1 << 18, 412 | "512KB" = 1 << 19, 413 | "1MB" = 1 << 20, 414 | "2MB" = 1 << 21, 415 | "4MB" = 1 << 22, 416 | "8MB" = 1 << 23, 417 | "16MB" = 1 << 24, 418 | "32MB" = 1 << 25, 419 | } 420 | 421 | /** 422 | * default block length 1 << 14 = 16384 423 | */ 424 | export const BLOCK_LENGTH = 1 << 14; 425 | 426 | /** 427 | * default meta version = 2 428 | */ 429 | export const META_VERSION: MetaVersion = 2; 430 | 431 | /** 432 | * default v1 torrent options 433 | */ 434 | const defaultTorrentOptionsV1: InternalTorrentOptions = { 435 | type: TorrentType.V1, 436 | addCreatedBy: true, 437 | addCreationDate: true, 438 | addPaddingFiles: false, 439 | blockLength: BLOCK_LENGTH, 440 | pieceLength: NaN, 441 | sortFiles: true, 442 | isPrivate: false, 443 | }; 444 | 445 | /** 446 | * default v2 torrent options 447 | */ 448 | const defaultTorrentOptionsV2: InternalTorrentOptions = { 449 | type: TorrentType.V2, 450 | addCreatedBy: true, 451 | addCreationDate: true, 452 | blockLength: BLOCK_LENGTH, 453 | pieceLength: NaN, 454 | metaVersion: META_VERSION, 455 | isPrivate: false, 456 | }; 457 | 458 | /** 459 | * default hybrid torrent options 460 | */ 461 | const defaultTorrentOptionsHybrid: InternalTorrentOptions = 462 | { 463 | type: TorrentType.HYBRID, 464 | addCreatedBy: true, 465 | addCreationDate: true, 466 | blockLength: BLOCK_LENGTH, 467 | pieceLength: NaN, 468 | metaVersion: META_VERSION, 469 | isPrivate: false, 470 | }; 471 | 472 | export type OnProgress = ( 473 | current: number, 474 | total: number 475 | ) => void | Promise; 476 | 477 | function getTotalPieces(iterableFiles: Iterable, pieceLength: number) { 478 | let totalPieces = 0; 479 | for (const file of iterableFiles) { 480 | totalPieces += Math.ceil(file.size / pieceLength); 481 | } 482 | return totalPieces; 483 | } 484 | 485 | async function createV1( 486 | fileDirLikes: FileDirLikes, 487 | opts: TorrentOptions, 488 | onProgress?: OnProgress 489 | ) { 490 | // assign options 491 | const iOpts: InternalTorrentOptions = { 492 | ...defaultTorrentOptionsV1, 493 | ...opts, 494 | }; 495 | // build file tree 496 | const { fileTree, traverseTree, totalFileCount, totalFileSize } = 497 | await populateFileTree(fileDirLikes, { 498 | sort: iOpts.sortFiles, 499 | }); 500 | // early exit 501 | if (totalFileCount === 0) { 502 | return; 503 | } 504 | // get all files 505 | const files: File[] = []; 506 | for (const [, file] of traverseTree(fileTree)) { 507 | files.push(file); 508 | } 509 | // auto calculate piece length 510 | if (isNaN(iOpts.pieceLength)) { 511 | // auto calculate piece length if not assigned 512 | iOpts.pieceLength = calculatePieceLength(totalFileSize, iOpts.blockLength); 513 | } 514 | // Piece length is almost always a power of two in v1 torrents 515 | // https://www.bittorrent.org/beps/bep_0003.html#:~:text=piece%20length%20is%20almost%20always%20a%20power%20of%20two 516 | if ((iOpts.pieceLength & (iOpts.pieceLength - 1)) !== 0) { 517 | console.warn(`piece length ${iOpts.pieceLength} is not a power of 2`); 518 | } 519 | // collapse announce list 520 | iOpts.announceList = collapseAnnounceList(iOpts.announceList); 521 | // auto assign announce if possible 522 | if ( 523 | typeof iOpts.announce === "undefined" && 524 | typeof iOpts.announceList !== "undefined" 525 | ) { 526 | iOpts.announce = iOpts.announceList[0]?.[0]; 527 | } 528 | // sanitize url list 529 | iOpts.urlList = sanitizeUrlList(iOpts.urlList); 530 | // progress hook 531 | const [updateProgress, setProgressTotal] = useProgress(0, onProgress); 532 | // torrent name and common dir 533 | const { commonDir, name } = resolveCommonDirAndTorrentName( 534 | iOpts.name, 535 | fileTree 536 | ); 537 | iOpts.name = name; 538 | // declare v1 piece readable stream 539 | let v1PiecesReadableStream: ReadableStream; 540 | // add padding files 541 | if (iOpts.addPaddingFiles) { 542 | // assign progress total (in piece unit) 543 | const totalPieces = getTotalPieces(files, iOpts.pieceLength); 544 | await setProgressTotal(totalPieces); 545 | 546 | const pieceLayerReadableStreamPromises: Promise< 547 | ReadableStream 548 | >[] = []; 549 | const lastFileIndex = totalFileCount - 1; 550 | for (let fileIndex = lastFileIndex; fileIndex >= 0; --fileIndex) { 551 | const file = files[fileIndex] as File; 552 | pieceLayerReadableStreamPromises.unshift( 553 | Promise.resolve( 554 | getPieceLayerReadableStream(file.stream(), { 555 | pieceLength: iOpts.pieceLength, 556 | padding: fileIndex !== lastFileIndex, 557 | updateProgress, 558 | }) 559 | ) 560 | ); 561 | if (fileIndex === lastFileIndex) { 562 | continue; 563 | } 564 | const remainderSize = file.size % iOpts.pieceLength; 565 | if (remainderSize === 0) { 566 | continue; 567 | } 568 | const paddingSize = iOpts.pieceLength - remainderSize; 569 | const paddingFile = createPaddingFile(paddingSize, commonDir); 570 | files.splice(fileIndex + 1, 0, paddingFile); 571 | } 572 | v1PiecesReadableStream = concatenateStreams( 573 | pieceLayerReadableStreamPromises 574 | ).stream as ReadableStream; 575 | } 576 | // no padding files 577 | else { 578 | // assign progress total (in piece unit) 579 | const totalPieces = Math.ceil(totalFileSize / iOpts.pieceLength); 580 | await setProgressTotal(totalPieces); 581 | 582 | // concatenate all files into a single stream first 583 | const { 584 | stream: concatenatedFileReadableStream, 585 | }: { stream: ReadableStream } = concatenateStreams( 586 | files.map((file) => Promise.resolve(file.stream())) 587 | ); 588 | // and then hash it 589 | v1PiecesReadableStream = getPieceLayerReadableStream( 590 | concatenatedFileReadableStream, 591 | { 592 | pieceLength: iOpts.pieceLength, 593 | padding: false, 594 | updateProgress, 595 | } 596 | ); 597 | } 598 | 599 | const metaInfo: MetaInfo = { 600 | ...(typeof iOpts.announce === "undefined" 601 | ? {} 602 | : { announce: iOpts.announce }), 603 | ...(iOpts.announceList ? { "announce-list": iOpts.announceList } : {}), 604 | ...(typeof iOpts.comment === "undefined" ? {} : { comment: iOpts.comment }), 605 | ...(iOpts.addCreatedBy ? { "created by": CREATED_BY } : {}), 606 | ...(iOpts.addCreationDate 607 | ? { "creation date": getTimeStampSecondsNow() } 608 | : {}), 609 | info: { 610 | ...(totalFileCount > 1 611 | ? { 612 | files: files.map((file) => { 613 | // get file path segments 614 | const filePath = (file.webkitRelativePath || file.name).split( 615 | "/" 616 | ); 617 | // remove common dir 618 | commonDir && filePath.shift(); 619 | // emit 620 | return { 621 | ...(file.padding ? { attr: "p" as FileAttrs } : {}), 622 | length: file.size, 623 | path: filePath, 624 | }; 625 | }), 626 | } 627 | : { 628 | length: totalFileSize, 629 | }), 630 | name: iOpts.name, 631 | "piece length": iOpts.pieceLength, 632 | pieces: await new Response(v1PiecesReadableStream).arrayBuffer(), 633 | ...(iOpts.isPrivate ? { private: true } : {}), 634 | ...(typeof iOpts.source === "undefined" ? {} : { source: iOpts.source }), 635 | }, 636 | ...(iOpts.urlList ? { "url-list": iOpts.urlList } : {}), 637 | }; 638 | 639 | return metaInfo; 640 | } 641 | 642 | async function createV2( 643 | fileDirLikes: FileDirLikes, 644 | opts: TorrentOptions, 645 | onProgress?: OnProgress 646 | ) { 647 | // assign options 648 | const iOpts: InternalTorrentOptions = { 649 | ...defaultTorrentOptionsV2, 650 | ...opts, 651 | }; 652 | // build file tree 653 | const { fileTree, traverseTree, totalFileCount, totalFileSize } = 654 | await populateFileTree(fileDirLikes); 655 | // early exit 656 | if (totalFileCount === 0) { 657 | return; 658 | } 659 | // get all files 660 | const files: File[] = []; 661 | const fileNodeToFileEntries: [FileTreeFileNode, File][] = []; 662 | for (const [fileNode, file] of traverseTree(fileTree)) { 663 | files.push(file); 664 | fileNodeToFileEntries.push([fileNode, file]); 665 | } 666 | // auto calculate piece length 667 | if (isNaN(iOpts.pieceLength)) { 668 | // auto calculate piece length if not assigned 669 | iOpts.pieceLength = calculatePieceLength(totalFileSize, iOpts.blockLength); 670 | } 671 | // Piece length cannot be smaller than block length in v2 torrents 672 | // https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=of%20two%20and-,at%20least%2016KiB,-. 673 | if (iOpts.pieceLength < iOpts.blockLength) { 674 | throw new Error( 675 | `piece length ${iOpts.pieceLength} is smaller than block length ${iOpts.blockLength}` 676 | ); 677 | } 678 | // Piece length must be a power of two in v2 torrents. 679 | // https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=It%20must%20be%20a%20power%20of%20two 680 | if ((iOpts.pieceLength & (iOpts.pieceLength - 1)) !== 0) { 681 | throw new Error(`piece length ${iOpts.pieceLength} is not a power of 2`); 682 | } 683 | // calculate blocks per piece 684 | const blocksPerPiece = iOpts.pieceLength / iOpts.blockLength; 685 | // collapse announce list 686 | iOpts.announceList = collapseAnnounceList(iOpts.announceList); 687 | // auto assign announce if possible 688 | if ( 689 | typeof iOpts.announce === "undefined" && 690 | typeof iOpts.announceList !== "undefined" 691 | ) { 692 | iOpts.announce = iOpts.announceList[0]?.[0]; 693 | } 694 | // sanitize url list 695 | iOpts.urlList = sanitizeUrlList(iOpts.urlList); 696 | // assign progress total (in piece unit) 697 | const totalPieces = getTotalPieces(files, iOpts.pieceLength); 698 | // progress hook 699 | const [updateProgress] = useProgress(totalPieces, onProgress); 700 | // torrent name 701 | const { name } = resolveCommonDirAndTorrentName(iOpts.name, fileTree); 702 | iOpts.name = name; 703 | // init piece layers 704 | const pieceLayers: PieceLayers = new Map(); 705 | // populate piece layers and file nodes 706 | await Promise.all( 707 | fileNodeToFileEntries.map(async ([fileNode, file]) => { 708 | await populatePieceLayersAndFileNodes( 709 | file.stream(), 710 | pieceLayers, 711 | fileNode, 712 | { 713 | blockLength: iOpts.blockLength, 714 | pieceLength: iOpts.pieceLength, 715 | blocksPerPiece, 716 | updateProgress, 717 | } 718 | ); 719 | }) 720 | ); 721 | 722 | const metaInfo: MetaInfo = { 723 | ...(typeof iOpts.announce === "undefined" 724 | ? {} 725 | : { announce: iOpts.announce }), 726 | ...(iOpts.announceList ? { "announce-list": iOpts.announceList } : {}), 727 | ...(typeof iOpts.comment === "undefined" ? {} : { comment: iOpts.comment }), 728 | ...(iOpts.addCreatedBy ? { "created by": CREATED_BY } : {}), 729 | ...(iOpts.addCreationDate 730 | ? { "creation date": getTimeStampSecondsNow() } 731 | : {}), 732 | info: { 733 | ["file tree"]: fileTree, 734 | "meta version": iOpts.metaVersion, 735 | name: iOpts.name, 736 | "piece length": iOpts.pieceLength, 737 | // only add private field when it is private 738 | ...(iOpts.isPrivate ? { private: true } : {}), 739 | ...(typeof iOpts.source === "undefined" ? {} : { source: iOpts.source }), 740 | }, 741 | // piece layers must not be abscent 742 | "piece layers": pieceLayers, 743 | ...(iOpts.urlList ? { "url-list": iOpts.urlList } : {}), 744 | }; 745 | 746 | return metaInfo; 747 | } 748 | 749 | async function createHybrid( 750 | fileDirLikes: FileDirLikes, 751 | opts: TorrentOptions, 752 | onProgress?: OnProgress 753 | ) { 754 | // assign options 755 | const iOpts: InternalTorrentOptions = { 756 | ...defaultTorrentOptionsHybrid, 757 | ...opts, 758 | }; 759 | // build file tree 760 | const { fileTree, traverseTree, totalFileCount, totalFileSize } = 761 | await populateFileTree(fileDirLikes); 762 | // early exit 763 | if (totalFileCount === 0) { 764 | return; 765 | } 766 | // get all files 767 | const files: File[] = []; 768 | const fileNodeToFileEntries: [FileTreeFileNode, File][] = []; 769 | for (const [fileNode, file] of traverseTree(fileTree)) { 770 | files.push(file); 771 | fileNodeToFileEntries.push([fileNode, file]); 772 | } 773 | // auto calculate piece length 774 | if (isNaN(iOpts.pieceLength)) { 775 | // auto calculate piece length if not assigned 776 | iOpts.pieceLength = calculatePieceLength(totalFileSize, iOpts.blockLength); 777 | } 778 | // Piece length cannot be smaller than block length in hybrid torrents 779 | // https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=of%20two%20and-,at%20least%2016KiB,-. 780 | if (iOpts.pieceLength < iOpts.blockLength) { 781 | throw new Error( 782 | `piece length ${iOpts.pieceLength} is smaller than block length ${iOpts.blockLength}` 783 | ); 784 | } 785 | // Piece length must be a power of two in hybrid torrents. 786 | // https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=It%20must%20be%20a%20power%20of%20two 787 | if ((iOpts.pieceLength & (iOpts.pieceLength - 1)) !== 0) { 788 | throw new Error(`piece length ${iOpts.pieceLength} is not a power of 2`); 789 | } 790 | // calculate blocks per piece 791 | const blocksPerPiece = iOpts.pieceLength / iOpts.blockLength; 792 | // collapse announce list 793 | iOpts.announceList = collapseAnnounceList(iOpts.announceList); 794 | // auto assign announce if possible 795 | if ( 796 | typeof iOpts.announce === "undefined" && 797 | typeof iOpts.announceList !== "undefined" 798 | ) { 799 | iOpts.announce = iOpts.announceList[0]?.[0]; 800 | } 801 | // sanitize url list 802 | iOpts.urlList = sanitizeUrlList(iOpts.urlList); 803 | // progress hook 804 | const [updateProgress] = useProgress( 805 | getTotalPieces(files, iOpts.pieceLength) * 2, 806 | onProgress 807 | ); 808 | // torrent name 809 | const { commonDir, name } = resolveCommonDirAndTorrentName( 810 | iOpts.name, 811 | fileTree 812 | ); 813 | iOpts.name = name; 814 | // init piece layers 815 | const pieceLayers: PieceLayers = new Map(); 816 | 817 | const pieceLayerReadableStreamPromise: Promise>[] = 818 | []; 819 | let lastFileIndex = totalFileCount - 1; 820 | let fileIndex = -1; 821 | for (const [fileNode, file] of fileNodeToFileEntries) { 822 | ++fileIndex; 823 | // we need to tee one stream into two streams for v1 and v2 824 | const [v1FileStream, v2FileStream] = file.stream().tee(); 825 | pieceLayerReadableStreamPromise.unshift( 826 | Promise.resolve( 827 | getPieceLayerReadableStream(v1FileStream, { 828 | pieceLength: iOpts.pieceLength, 829 | padding: fileIndex !== lastFileIndex, 830 | updateProgress, 831 | }) 832 | ) 833 | ); 834 | // v2 835 | await populatePieceLayersAndFileNodes(v2FileStream, pieceLayers, fileNode, { 836 | blockLength: iOpts.blockLength, 837 | pieceLength: iOpts.pieceLength, 838 | blocksPerPiece, 839 | updateProgress, 840 | }); 841 | if (fileIndex === lastFileIndex) { 842 | break; 843 | } 844 | const remainderSize = file.size % iOpts.pieceLength; 845 | if (remainderSize === 0) { 846 | continue; 847 | } 848 | const paddingSize = iOpts.pieceLength - remainderSize; 849 | const paddingFile = createPaddingFile(paddingSize, commonDir); 850 | files.splice(++fileIndex, 0, paddingFile); 851 | ++lastFileIndex; 852 | } 853 | const v1PiecesReadableStream = concatenateStreams( 854 | pieceLayerReadableStreamPromise 855 | ).stream as ReadableStream; 856 | 857 | const metaInfo: MetaInfo = { 858 | ...(typeof iOpts.announce === "undefined" 859 | ? {} 860 | : { announce: iOpts.announce }), 861 | ...(iOpts.announceList ? { "announce-list": iOpts.announceList } : {}), 862 | ...(typeof iOpts.comment === "undefined" ? {} : { comment: iOpts.comment }), 863 | ...(iOpts.addCreatedBy ? { "created by": CREATED_BY } : {}), 864 | ...(iOpts.addCreationDate 865 | ? { "creation date": getTimeStampSecondsNow() } 866 | : {}), 867 | info: { 868 | "file tree": fileTree, 869 | ...(totalFileCount > 1 870 | ? { 871 | files: files.map((file) => { 872 | // get file path segments 873 | const filePath = (file.webkitRelativePath || file.name).split( 874 | "/" 875 | ); 876 | // remove common dir 877 | commonDir && filePath.shift(); 878 | // emit 879 | return { 880 | ...(file.padding ? { attr: "p" as FileAttrs } : {}), 881 | length: file.size, 882 | path: filePath, 883 | }; 884 | }), 885 | } 886 | : { 887 | length: totalFileSize, 888 | }), 889 | "meta version": iOpts.metaVersion, 890 | name: iOpts.name, 891 | "piece length": iOpts.pieceLength, 892 | // stream to array buffer 893 | pieces: await new Response(v1PiecesReadableStream).arrayBuffer(), 894 | // only add private field when it is private 895 | ...(iOpts.isPrivate ? { private: true } : {}), 896 | ...(typeof iOpts.source === "undefined" ? {} : { source: iOpts.source }), 897 | }, 898 | // piece layers must not be abscent 899 | "piece layers": pieceLayers, 900 | ...(iOpts.urlList ? { "url-list": iOpts.urlList } : {}), 901 | }; 902 | 903 | return metaInfo; 904 | } 905 | 906 | export async function create( 907 | fileDirLikes: FileDirLikes, 908 | opts: TorrentOptions = { type: TorrentType.V1 }, 909 | onProgress?: OnProgress 910 | ) { 911 | switch (opts.type) { 912 | case TorrentType.V1: 913 | return await createV1(fileDirLikes, opts, onProgress); 914 | case TorrentType.V2: 915 | return await createV2(fileDirLikes, opts, onProgress); 916 | case TorrentType.HYBRID: 917 | return await createHybrid(fileDirLikes, opts, onProgress); 918 | } 919 | } 920 | 921 | /** 922 | * Populate piece layers and file node in a v2 torrent 923 | * @param fileStream 924 | * @param pieceLayers 925 | * @param fileNode 926 | * @param opts 927 | */ 928 | async function populatePieceLayersAndFileNodes( 929 | fileStream: ReadableStream, 930 | pieceLayers: PieceLayers, 931 | fileNode: FileTreeFileNode, 932 | opts: { 933 | blockLength: number; 934 | pieceLength: number; 935 | blocksPerPiece: number; 936 | updateProgress?: UpdateProgress; 937 | } 938 | ) { 939 | // get pieces root and piece layer readable streams 940 | const { piecesRootReadableStream, pieceLayerReadableStream } = 941 | getPiecesRootAndPieceLayerReadableStreams(fileStream, opts); 942 | // get buffer promise from pieces root 943 | const piecesRootArrayBufferPromise = new Response( 944 | piecesRootReadableStream 945 | ).arrayBuffer(); 946 | // only files that are larger than the piece size have piece layer entries 947 | // https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=For%20each%20file%20in%20the%20file%20tree%20that%20is%20larger%20than%20the%20piece%20size%20it%20contains%20one%20string%20value 948 | if (fileNode[""].length > opts.pieceLength) { 949 | const pieceLayerArrayBufferPromise = new Response( 950 | pieceLayerReadableStream 951 | ).arrayBuffer(); 952 | pieceLayers.set( 953 | await piecesRootArrayBufferPromise, 954 | await pieceLayerArrayBufferPromise 955 | ); 956 | } 957 | // only non-empty files have ["pieces root"] property 958 | // https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=For-,non%2Dempty,-files%20this%20is 959 | if (fileNode[""].length > 0) { 960 | fileNode[""]["pieces root"] = await piecesRootArrayBufferPromise; 961 | } 962 | } 963 | 964 | /** 965 | * Returns the piece layer of file(s) in a v1 type torrent 966 | * @param stream file stream 967 | * @param opts options 968 | * @returns piece layer 969 | */ 970 | function getPieceLayerReadableStream( 971 | stream: ReadableStream, 972 | opts: { 973 | pieceLength: number; 974 | padding: boolean; 975 | updateProgress?: UpdateProgress; 976 | } 977 | ): ReadableStream { 978 | const pieceLayerReadableStream = stream 979 | .pipeThrough( 980 | new ChunkSplitter(opts.pieceLength, { 981 | padding: opts.padding, 982 | }) 983 | ) 984 | .pipeThrough(new PieceHasher(opts.updateProgress)); 985 | return pieceLayerReadableStream; 986 | } 987 | 988 | /** 989 | * Returns the pieces root and piece layer of a file in a v2 type torrent 990 | * @param stream 991 | * @param opts 992 | * @returns 993 | */ 994 | function getPiecesRootAndPieceLayerReadableStreams( 995 | stream: ReadableStream, 996 | opts: { 997 | blockLength: number; 998 | blocksPerPiece: number; 999 | updateProgress?: UpdateProgress; 1000 | } 1001 | ) { 1002 | const pieceLayerReadableStream: ReadableStream = stream 1003 | .pipeThrough(new ChunkSplitter(opts.blockLength)) 1004 | .pipeThrough(new BlockHasher(opts.blocksPerPiece)) 1005 | .pipeThrough(new MerkleRootCalculator(opts.updateProgress)); 1006 | 1007 | const [pl1ReadableStream, pl2ReadableStream] = pieceLayerReadableStream.tee(); 1008 | 1009 | return { 1010 | piecesRootReadableStream: pl2ReadableStream 1011 | .pipeThrough(new MerkleTreeBalancer(opts.blocksPerPiece)) 1012 | .pipeThrough(new MerkleRootCalculator()), 1013 | pieceLayerReadableStream: pl1ReadableStream, 1014 | }; 1015 | } 1016 | 1017 | function createPaddingFile(paddingSize: number, commonDir: string | undefined) { 1018 | const paddingFile = new File( 1019 | [new ArrayBuffer(paddingSize)], 1020 | `${paddingSize}`, 1021 | { 1022 | type: "application/octet-stream", 1023 | } 1024 | ); 1025 | const paddingFilePath = `.pad/${paddingSize}`; 1026 | const nestedPath = 1027 | typeof commonDir === "undefined" 1028 | ? paddingFilePath 1029 | : `${commonDir}/${paddingFilePath}`; 1030 | Object.defineProperties(paddingFile, { 1031 | webkitRelativePath: { 1032 | configurable: true, 1033 | enumerable: true, 1034 | get: () => nestedPath, 1035 | }, 1036 | padding: { 1037 | configurable: true, 1038 | enumerable: false, 1039 | get: () => true, 1040 | }, 1041 | }); 1042 | return paddingFile; 1043 | } 1044 | 1045 | export type SetProgressTotal = ( 1046 | totalNumberOrFunction: 1047 | | number 1048 | | ((total: number, current?: number) => number | Promise) 1049 | ) => Promise; 1050 | 1051 | export type UpdateProgress = () => Promise; 1052 | 1053 | function useProgress( 1054 | initTotal: number, 1055 | onProgress?: OnProgress 1056 | ): [UpdateProgress, SetProgressTotal] { 1057 | // init progress parameters 1058 | const progressRef = { 1059 | // progress current (in piece unit) 1060 | current: 0, 1061 | // progress total (in piece unit) 1062 | total: initTotal, 1063 | }; 1064 | // update progress 1065 | const updateProgress: UpdateProgress = async () => { 1066 | if (onProgress) { 1067 | await onProgress(++progressRef.current, progressRef.total); 1068 | } 1069 | }; 1070 | // set progress total 1071 | const setProgressTotal: SetProgressTotal = async (totalNumberOrFunction) => { 1072 | if (typeof totalNumberOrFunction === "number") { 1073 | progressRef.total = totalNumberOrFunction; 1074 | } else { 1075 | progressRef.total = await totalNumberOrFunction( 1076 | progressRef.total, 1077 | progressRef.current 1078 | ); 1079 | } 1080 | }; 1081 | return [updateProgress, setProgressTotal]; 1082 | } 1083 | 1084 | /** 1085 | * Collapse announce list 1086 | * @param announceList 1087 | * @returns collapsed announce list 1088 | */ 1089 | function collapseAnnounceList( 1090 | announceList: string[][] | undefined 1091 | ): string[][] | undefined { 1092 | if (typeof announceList === "undefined") { 1093 | return undefined; 1094 | } 1095 | const collapsedAnnounceList: string[][] = []; 1096 | for (const tier of announceList) { 1097 | if (tier.length === 0) { 1098 | continue; 1099 | } 1100 | collapsedAnnounceList.push(tier); 1101 | } 1102 | if (collapsedAnnounceList.length === 0) { 1103 | return undefined; 1104 | } 1105 | return collapsedAnnounceList; 1106 | } 1107 | 1108 | /** 1109 | * Sanitize url list 1110 | * @param urlList 1111 | * @returns sanitized url list 1112 | */ 1113 | function sanitizeUrlList(urlList: string[] | undefined): string[] | undefined { 1114 | if (typeof urlList === "undefined") { 1115 | return undefined; 1116 | } 1117 | if (urlList.length === 0) { 1118 | return undefined; 1119 | } 1120 | return urlList; 1121 | } 1122 | 1123 | /** 1124 | * Calculate piece length from file size 1125 | * @param fileSize 1126 | * @returns 1127 | */ 1128 | function calculatePieceLength(fileSize: number, blockLength: number) { 1129 | return Math.max(blockLength, nextPowerOfTwo(fileSize >>> 10)); 1130 | } 1131 | -------------------------------------------------------------------------------- /src/decode.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BData, 3 | BList, 4 | BObject, 5 | BYTE_0, 6 | BYTE_MINUS, 7 | isDigitByte, 8 | } from "./utils/codec.js"; 9 | import { Token, TokenType, Tokenizer } from "./transformers/tokenizer.js"; 10 | 11 | export async function parse( 12 | tokenReadableStream: ReadableStream 13 | ): Promise { 14 | let parsedResult: BData | undefined; 15 | const contextStack: (BObject | BList)[] = []; 16 | const tokenStreamReader = tokenReadableStream.getReader(); 17 | let dictionaryKey: string | undefined; 18 | // eslint-disable-next-line no-constant-condition 19 | while (true) { 20 | const { done, value: token } = await tokenStreamReader.read(); 21 | if (done) { 22 | break; 23 | } 24 | const currentContext = contextStack.at(-1); 25 | // current context: global 26 | if (!currentContext) { 27 | if (typeof parsedResult !== "undefined") { 28 | throw new SyntaxError(`Unexpected token: ${JSON.stringify(token)}`); 29 | } 30 | switch (token.type) { 31 | case TokenType.Integer: 32 | parsedResult = parseInteger(token.value); 33 | break; 34 | case TokenType.ByteString: 35 | parsedResult = parseByteString(token.value); 36 | break; 37 | case TokenType.DictionaryStart: { 38 | const nextContext = Object.create(null) as BObject; 39 | contextStack.push(nextContext); 40 | parsedResult = nextContext; 41 | break; 42 | } 43 | case TokenType.ListStart: { 44 | const nextContext: BList = []; 45 | contextStack.push(nextContext); 46 | parsedResult = nextContext; 47 | break; 48 | } 49 | default: 50 | throw new SyntaxError(`Unexpected token: ${JSON.stringify(token)}`); 51 | } 52 | } 53 | // current context: list 54 | else if (Array.isArray(currentContext)) { 55 | switch (token.type) { 56 | case TokenType.Integer: 57 | currentContext.push(parseInteger(token.value)); 58 | break; 59 | case TokenType.ByteString: 60 | currentContext.push(parseByteString(token.value)); 61 | break; 62 | case TokenType.DictionaryStart: { 63 | const nextContext = Object.create(null) as BObject; 64 | currentContext.push(nextContext); 65 | contextStack.push(nextContext); 66 | break; 67 | } 68 | case TokenType.ListStart: { 69 | const nextContext: BList = []; 70 | currentContext.push(nextContext); 71 | contextStack.push(nextContext); 72 | break; 73 | } 74 | case TokenType.ListEnd: 75 | contextStack.pop(); 76 | break; 77 | default: 78 | throw new SyntaxError(`Unexpected token: ${JSON.stringify(token)}`); 79 | } 80 | } 81 | // current context: dictionary 82 | else { 83 | // dictionary key 84 | if (typeof dictionaryKey === "undefined") { 85 | switch (token.type) { 86 | case TokenType.ByteString: 87 | dictionaryKey = parseByteString(token.value); 88 | break; 89 | case TokenType.DictionaryEnd: 90 | contextStack.pop(); 91 | break; 92 | default: 93 | throw new SyntaxError(`Unexpected token: ${JSON.stringify(token)}`); 94 | } 95 | } 96 | // dictionary value 97 | else { 98 | switch (token.type) { 99 | case TokenType.Integer: 100 | currentContext[dictionaryKey] = parseInteger(token.value); 101 | break; 102 | case TokenType.ByteString: 103 | currentContext[dictionaryKey] = parseByteString(token.value); 104 | break; 105 | case TokenType.DictionaryStart: { 106 | const nextContext = Object.create(null) as BObject; 107 | currentContext[dictionaryKey] = nextContext; 108 | contextStack.push(nextContext); 109 | break; 110 | } 111 | case TokenType.ListStart: { 112 | const nextContext: BList = []; 113 | currentContext[dictionaryKey] = nextContext; 114 | contextStack.push(nextContext); 115 | break; 116 | } 117 | default: 118 | throw new SyntaxError(`Unexpected token: ${JSON.stringify(token)}`); 119 | } 120 | dictionaryKey = undefined; 121 | } 122 | } 123 | } 124 | if (contextStack.length) { 125 | throw new Error(`Unexpected end of token stream`); 126 | } 127 | return parsedResult; 128 | } 129 | 130 | export async function decode( 131 | torrentReadableStream: ReadableStream 132 | ): Promise { 133 | const tokenizer = new Tokenizer(); 134 | const tokenReadableStream = torrentReadableStream.pipeThrough(tokenizer); 135 | return await parse(tokenReadableStream); 136 | } 137 | 138 | function parseInteger(tokenValue: Uint8Array) { 139 | let integer: number | bigint = 0; 140 | let isPositive = true; 141 | for (const [index, byte] of tokenValue.entries()) { 142 | // branch order is important! 143 | // handle negative sign 144 | if (index === 0 && byte === BYTE_MINUS) { 145 | isPositive = false; 146 | continue; 147 | } 148 | // negative zero is not allowed 149 | if (index === 1 && !isPositive && byte === BYTE_0) { 150 | throw new SyntaxError("Negative zero is not a valid integer"); 151 | } 152 | // leading zeros are not allowed 153 | if (index === 1 && integer === 0) { 154 | throw new SyntaxError("Leading zeros are not allowed"); 155 | } 156 | // not a digit or negative sign 157 | if (!isDigitByte(byte)) { 158 | throw new SyntaxError(`Unexpected byte: ${byte}`); 159 | } 160 | const byteNumber = byte - BYTE_0; 161 | // handle immediate overflow 162 | if ( 163 | isPositive && 164 | typeof integer === "number" && 165 | integer > (Number.MAX_SAFE_INTEGER - byteNumber) / 10 166 | ) { 167 | integer = BigInt(integer); 168 | } 169 | // handle immediate underflow 170 | else if ( 171 | !isPositive && 172 | typeof integer === "number" && 173 | integer < (Number.MIN_SAFE_INTEGER + byteNumber) / 10 174 | ) { 175 | integer = BigInt(integer); 176 | } 177 | // handle number 178 | if (typeof integer === "number") { 179 | if (isPositive) { 180 | integer = integer * 10 + byteNumber; 181 | } else { 182 | integer = integer * 10 - byteNumber; 183 | } 184 | } 185 | // handle big int 186 | else { 187 | if (isPositive) { 188 | integer = integer * 10n + BigInt(byteNumber); 189 | } else { 190 | integer = integer * 10n - BigInt(byteNumber); 191 | } 192 | } 193 | } 194 | return integer; 195 | } 196 | 197 | function parseByteString(tokenValue: Uint8Array) { 198 | const textDecoder = new TextDecoder(); 199 | return textDecoder.decode(tokenValue); 200 | } 201 | -------------------------------------------------------------------------------- /src/encode.ts: -------------------------------------------------------------------------------- 1 | import { TrieMap } from "@sec-ant/trie-map"; 2 | import { BData, BUFF_L, BUFF_E, BUFF_D } from "./utils/codec.js"; 3 | import { iterableSort } from "./utils/misc.js"; 4 | 5 | /** 6 | * encoder hook 7 | */ 8 | export type EncoderHook = ( 9 | result: IteratorResult 10 | ) => void; 11 | 12 | /** 13 | * encoder hook path 14 | */ 15 | export type EncoderHookPath = Iterable; 16 | 17 | /** 18 | * encoder hook system 19 | */ 20 | export type EncoderHookSystem = TrieMap; 21 | 22 | /** 23 | * a global weakmap that keeps all consumed hook systems, for internal use 24 | */ 25 | const consumedHookSystems = new WeakMap(); 26 | 27 | /** 28 | * bencode readable stream underlying source 29 | */ 30 | class EncoderUnderlyingSource implements UnderlyingSource { 31 | textEncoder = new TextEncoder(); 32 | textDecoder = new TextDecoder(); 33 | data: BData; 34 | path: (EncoderHookPath extends Iterable 35 | ? PathElement 36 | : never)[] = []; 37 | hookSystem?: EncoderHookSystem; 38 | attachedHooks = new WeakMap(); 39 | constructor(data: BData, hookSystem?: EncoderHookSystem) { 40 | this.data = data; 41 | this.hookSystem = hookSystem; 42 | if (hookSystem) { 43 | consumedHookSystems.set(hookSystem, true); 44 | } 45 | } 46 | start(controller: ReadableStreamController) { 47 | this.encode(this.data, controller); 48 | // wind up 49 | if (this.hookSystem) { 50 | for (const hook of this.hookSystem.values()) { 51 | // only done once, in case of closing controller twice 52 | if (!this.attachedHooks.get(hook)) { 53 | hook({ value: undefined, done: true }); 54 | } 55 | } 56 | } 57 | controller.close(); 58 | } 59 | encode(data: BData, controller: ReadableStreamController) { 60 | // undefined or null: return 61 | if (typeof data === "undefined" || data === null) { 62 | return; 63 | } 64 | // boolean: integer 65 | if (typeof data === "boolean") { 66 | this.encode(data ? 1 : 0, controller); 67 | } 68 | // number: integer 69 | else if (typeof data === "number") { 70 | const integer = Math.round(data); 71 | controller.enqueue(this.textEncoder.encode(`i${integer}e`)); 72 | if (integer !== data) { 73 | console.warn( 74 | `WARNING: Possible data corruption detected with value "${data}":`, 75 | `Bencoding only defines support for integers, value was converted to "${integer}"` 76 | ); 77 | console.trace(); 78 | } 79 | } 80 | // bigint: integer 81 | else if (typeof data === "bigint") { 82 | controller.enqueue(this.textEncoder.encode(`i${data}e`)); 83 | } 84 | // string: byte string 85 | else if (typeof data === "string") { 86 | const byteData = this.textEncoder.encode(data); 87 | const byteLength = byteData.byteLength; 88 | controller.enqueue(this.textEncoder.encode(`${byteLength}:`)); 89 | controller.enqueue(byteData); 90 | } 91 | // array buffer: byte string 92 | else if (data instanceof ArrayBuffer) { 93 | const byteData = new Uint8Array(data); 94 | const byteLength = byteData.byteLength; 95 | controller.enqueue(this.textEncoder.encode(`${byteLength}:`)); 96 | controller.enqueue(byteData); 97 | } 98 | // array: list 99 | else if (Array.isArray(data)) { 100 | controller.enqueue(BUFF_L); 101 | let counter = 0; 102 | for (const member of data) { 103 | // push path 104 | this.path.push(counter); 105 | // if hook system is provided 106 | let hook: EncoderHook | undefined; 107 | if (this.hookSystem && (hook = this.hookSystem.get(this.path))) { 108 | const newController = attachHook(controller, hook); 109 | this.encode(member, newController); 110 | hook({ value: undefined, done: true }); 111 | this.attachedHooks.set(hook, true); 112 | } else { 113 | this.encode(member, controller); 114 | } 115 | // pop path 116 | this.path.pop(); 117 | ++counter; 118 | } 119 | controller.enqueue(BUFF_E); 120 | } 121 | // map: dictionary 122 | else if (data instanceof Map) { 123 | controller.enqueue(BUFF_D); 124 | // sort keys order 125 | const keys = iterableSort(data.keys(), (a, b) => { 126 | a = typeof a === "string" ? a : this.textDecoder.decode(a); 127 | b = typeof b === "string" ? b : this.textDecoder.decode(b); 128 | return a < b ? -1 : a > b ? 1 : 0; 129 | }); 130 | // iterate keys 131 | for (const key of keys) { 132 | const value = data.get(key); 133 | // ignore nullables 134 | if (typeof value === "undefined" || data === null) { 135 | continue; 136 | } 137 | if (key instanceof ArrayBuffer) { 138 | this.encode(key, controller); 139 | this.encode(value, controller); 140 | } else { 141 | // push path 142 | this.path.push(key); 143 | // encode key 144 | this.encode(key, controller); 145 | // if hook system is provided 146 | let hook: EncoderHook | undefined; 147 | if (this.hookSystem && (hook = this.hookSystem.get(this.path))) { 148 | const newController = attachHook(controller, hook); 149 | this.encode(value, newController); 150 | hook({ value: undefined, done: true }); 151 | this.attachedHooks.set(hook, true); 152 | } else { 153 | this.encode(value, controller); 154 | } 155 | // pop path 156 | this.path.pop(); 157 | } 158 | } 159 | controller.enqueue(BUFF_E); 160 | } 161 | // object: dictionary 162 | else { 163 | controller.enqueue(BUFF_D); 164 | const keys = Object.keys(data).sort(); 165 | for (const key of keys) { 166 | const value = data[key]; 167 | if (typeof value === "undefined" || data === null) { 168 | continue; 169 | } 170 | // push path 171 | this.path.push(key); 172 | // encode key 173 | this.encode(key, controller); 174 | // if hook system is provided 175 | let hook: EncoderHook | undefined; 176 | if (this.hookSystem && (hook = this.hookSystem.get(this.path))) { 177 | const newController = attachHook(controller, hook); 178 | this.encode(value, newController); 179 | hook({ value: undefined, done: true }); 180 | this.attachedHooks.set(hook, true); 181 | } else { 182 | this.encode(value, controller); 183 | } 184 | // pop path 185 | this.path.pop(); 186 | } 187 | controller.enqueue(BUFF_E); 188 | } 189 | } 190 | } 191 | 192 | /** 193 | * Bencode 194 | * @param data 195 | * @param hookSystem 196 | * @returns readable stream of the bencoded data 197 | */ 198 | export function encode(data: BData, hookSystem?: EncoderHookSystem) { 199 | return new ReadableStream(new EncoderUnderlyingSource(data, hookSystem)); 200 | } 201 | 202 | /** 203 | * Attach hook to controller 204 | * @param controller 205 | * @param hook 206 | * @returns 207 | */ 208 | function attachHook( 209 | controller: ReadableStreamController, 210 | hook: EncoderHook 211 | ) { 212 | const newController = new Proxy(controller, { 213 | get: function (target, prop, receiver) { 214 | switch (prop) { 215 | case "enqueue": 216 | return ((chunk: Uint8Array) => { 217 | target.enqueue(chunk); 218 | hook({ 219 | value: chunk, 220 | done: false, 221 | }); 222 | }).bind(target); 223 | default: 224 | return Reflect.get(target, prop, receiver) as unknown; 225 | } 226 | }, 227 | }); 228 | return newController; 229 | } 230 | 231 | /** 232 | * Register a hook and consume the result as an uint8 array readable stream 233 | * @param path 234 | * @param hookSystem 235 | * @returns an uint8 array readable stream 236 | */ 237 | export function useUint8ArrayStreamHook( 238 | path: EncoderHookPath, 239 | hookSystem: EncoderHookSystem 240 | ): ReadableStream { 241 | const ref = { 242 | controller: null as ReadableStreamController | null, 243 | }; 244 | 245 | const hook = ({ value, done }: IteratorResult) => { 246 | if (ref.controller === null) { 247 | return; 248 | } 249 | if (!done) { 250 | ref.controller.enqueue(value); 251 | } else { 252 | ref.controller.close(); 253 | } 254 | }; 255 | 256 | const readableStream = new ReadableStream({ 257 | start(controller) { 258 | ref.controller = controller; 259 | }, 260 | pull(controller) { 261 | if (!consumedHookSystems.get(hookSystem)) { 262 | // prevent endless awaiting when this readable stream is consumed as promise 263 | console.warn("You need to call encode() first and then consume hooks."); 264 | controller.close(); 265 | ref.controller = null; 266 | } 267 | }, 268 | }); 269 | 270 | // register hook in hook system 271 | hookSystem.set(path, hook); 272 | 273 | return readableStream; 274 | } 275 | 276 | /** 277 | * Register a hook and consume the result as an array buffer promise 278 | * @param path 279 | * @param hookSystem 280 | * @returns an array buffer promise 281 | */ 282 | export function useArrayBufferPromiseHook( 283 | path: EncoderHookPath, 284 | hookSystem: EncoderHookSystem 285 | ): Promise { 286 | const readableStream = useUint8ArrayStreamHook(path, hookSystem); 287 | const arrayBufferPromise = new Response(readableStream).arrayBuffer(); 288 | return arrayBufferPromise; 289 | } 290 | 291 | /** 292 | * Register a hook and consume the result as a text promise 293 | * @param path 294 | * @param hookSystem 295 | * @returns a text promise 296 | */ 297 | export function useTextPromiseHook( 298 | path: EncoderHookPath, 299 | hookSystem: EncoderHookSystem 300 | ): Promise { 301 | const readableStream = useUint8ArrayStreamHook(path, hookSystem); 302 | const textPromise = new Response(readableStream).text(); 303 | return textPromise; 304 | } 305 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./encode.js"; 2 | export * from "./decode.js"; 3 | export * from "./create.js"; 4 | export * from "./transformers/index.js"; 5 | export * from "./utils/index.js"; 6 | -------------------------------------------------------------------------------- /src/transformers/blockHasher.ts: -------------------------------------------------------------------------------- 1 | import { nextPowerOfTwo } from "../utils/misc.js"; 2 | 3 | /** 4 | * 32-byte-zeros 5 | */ 6 | const ZEROS_32_BYTES = new Uint8Array(32); 7 | 8 | /** 9 | * Block hasher transformer class 10 | */ 11 | class BlockHasherTransformer implements Transformer { 12 | blockCount = 0; 13 | merkleLeaves: Uint8Array[] = []; 14 | blocksPerPiece; 15 | constructor(blocksPerPiece: number) { 16 | this.blocksPerPiece = blocksPerPiece; 17 | } 18 | async transform( 19 | chunk: Uint8Array, 20 | controller: TransformStreamDefaultController 21 | ) { 22 | ++this.blockCount; 23 | let blockHash: Uint8Array; 24 | try { 25 | blockHash = new Uint8Array(await crypto.subtle.digest("SHA-256", chunk)); 26 | } catch { 27 | const { default: jsSHA256 } = await import("jssha/sha256"); 28 | const sha256Obj = new jsSHA256("SHA-256", "UINT8ARRAY"); 29 | sha256Obj.update(chunk); 30 | blockHash = sha256Obj.getHash("UINT8ARRAY"); 31 | } 32 | this.merkleLeaves.push(blockHash); 33 | if (this.merkleLeaves.length === this.blocksPerPiece) { 34 | controller.enqueue(this.merkleLeaves); 35 | this.merkleLeaves = []; 36 | } 37 | } 38 | flush(controller: TransformStreamDefaultController) { 39 | if (this.blockCount === 0) { 40 | return; 41 | } 42 | // http://bittorrent.org/beps/bep_0052.html#:~:text=The%20remaining%20leaf%20hashes%20beyond%20the%20end%20of%20the%20file%20required%20to%20construct%20upper%20layers%20of%20the%20merkle%20tree%20are%20set%20to%20zero 43 | let restBlockCount = 0; 44 | // If the file is smaller than one piece then the block hashes 45 | // should be padded to the next power of two instead of the next 46 | // piece boundary. 47 | if (this.blockCount < this.blocksPerPiece) { 48 | restBlockCount = nextPowerOfTwo(this.blockCount) - this.blockCount; 49 | } else { 50 | const residue = this.blockCount % this.blocksPerPiece; 51 | if (residue > 0) { 52 | restBlockCount = this.blocksPerPiece - residue; 53 | } 54 | } 55 | if (restBlockCount > 0) { 56 | for (let i = 0; i < restBlockCount; ++i) { 57 | this.merkleLeaves.push(ZEROS_32_BYTES); 58 | } 59 | } 60 | if (this.merkleLeaves.length > 0) { 61 | controller.enqueue(this.merkleLeaves); 62 | } 63 | } 64 | } 65 | 66 | export class BlockHasher extends TransformStream { 67 | constructor(blocksPerPiece: number) { 68 | const transformer = new BlockHasherTransformer(blocksPerPiece); 69 | super(transformer); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/transformers/chunkSplitter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Chunk splitter transformer class 3 | */ 4 | class ChunkSplitterTransformer implements Transformer { 5 | residuePointer = 0; 6 | chunkLength; 7 | residue; 8 | opts; 9 | constructor(chunkLength: number, opts = { padding: false }) { 10 | this.chunkLength = chunkLength; 11 | this.opts = opts; 12 | this.residue = new Uint8Array(this.chunkLength); 13 | } 14 | transform( 15 | chunk: Uint8Array, 16 | controller: TransformStreamDefaultController 17 | ) { 18 | while (this.residuePointer + chunk.byteLength >= this.chunkLength) { 19 | const chunkEnd = this.chunkLength - this.residuePointer; 20 | this.residue.set(chunk.subarray(0, chunkEnd), this.residuePointer); 21 | this.residuePointer = 0; 22 | controller.enqueue(new Uint8Array(this.residue)); 23 | chunk = chunk.subarray(chunkEnd); 24 | } 25 | this.residue.set(chunk, this.residuePointer); 26 | this.residuePointer += chunk.byteLength; 27 | } 28 | flush(controller: TransformStreamDefaultController) { 29 | if (this.residuePointer <= 0) { 30 | return; 31 | } 32 | if (this.opts.padding) { 33 | this.residue.set( 34 | new Uint8Array(this.chunkLength - this.residuePointer), 35 | this.residuePointer 36 | ); 37 | controller.enqueue(this.residue); 38 | } else { 39 | controller.enqueue(this.residue.subarray(0, this.residuePointer)); 40 | } 41 | } 42 | } 43 | 44 | export class ChunkSplitter extends TransformStream { 45 | constructor(chunkLength: number, opts = { padding: false }) { 46 | const transformer = new ChunkSplitterTransformer(chunkLength, opts); 47 | super(transformer); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/transformers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./blockHasher.js"; 2 | export * from "./chunkSplitter.js"; 3 | export * from "./merkleRootCalculator.js"; 4 | export * from "./merkleTreeBalancer.js"; 5 | export * from "./pieceHasher.js"; 6 | export * from "./tokenizer.js"; 7 | -------------------------------------------------------------------------------- /src/transformers/merkleRootCalculator.ts: -------------------------------------------------------------------------------- 1 | import { UpdateProgress } from "../create.js"; 2 | import { merkleRoot } from "../utils/misc.js"; 3 | /** 4 | * Merkle root calculator transformer class 5 | */ 6 | class MerkleRootCalculatorTransformer 7 | implements Transformer 8 | { 9 | updateProgress; 10 | constructor(updateProgress?: UpdateProgress) { 11 | this.updateProgress = updateProgress; 12 | } 13 | async transform( 14 | chunk: Uint8Array[], 15 | controller: TransformStreamDefaultController 16 | ) { 17 | controller.enqueue(await merkleRoot(chunk)); 18 | if (this.updateProgress) { 19 | await this.updateProgress(); 20 | } 21 | } 22 | } 23 | 24 | export class MerkleRootCalculator extends TransformStream< 25 | Uint8Array[], 26 | Uint8Array 27 | > { 28 | constructor(updateProgress?: UpdateProgress) { 29 | const transformer = new MerkleRootCalculatorTransformer(updateProgress); 30 | super(transformer); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/transformers/merkleTreeBalancer.ts: -------------------------------------------------------------------------------- 1 | import { nextPowerOfTwo, merkleRoot } from "../utils/misc.js"; 2 | 3 | /** 4 | * Merkle tree balancer transformer class 5 | */ 6 | class MerkleTreeBalancerTransformer 7 | implements Transformer 8 | { 9 | leafCount = 0; 10 | merkleLeaves: Uint8Array[] = []; 11 | blocksPerPiece; 12 | constructor(blocksPerPiece: number) { 13 | this.blocksPerPiece = blocksPerPiece; 14 | } 15 | transform(chunk: Uint8Array) { 16 | ++this.leafCount; 17 | this.merkleLeaves.push(chunk); 18 | } 19 | async flush(controller: TransformStreamDefaultController) { 20 | const restLeafCount = nextPowerOfTwo(this.leafCount) - this.leafCount; 21 | if (restLeafCount > 0) { 22 | const padLeaf = await this.padLeafPromise; 23 | for (let i = 0; i < restLeafCount; ++i) { 24 | this.merkleLeaves.push(padLeaf); 25 | } 26 | } 27 | controller.enqueue(this.merkleLeaves); 28 | } 29 | get padLeafPromise() { 30 | return merkleRoot( 31 | Array(this.blocksPerPiece).fill(new Uint8Array(32)) 32 | ); 33 | } 34 | } 35 | 36 | export class MerkleTreeBalancer extends TransformStream< 37 | Uint8Array, 38 | Uint8Array[] 39 | > { 40 | constructor(blocksPerPiece: number) { 41 | const transformer = new MerkleTreeBalancerTransformer(blocksPerPiece); 42 | super(transformer); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/transformers/pieceHasher.ts: -------------------------------------------------------------------------------- 1 | import { UpdateProgress } from "../create.js"; 2 | 3 | /** 4 | * Piece hasher transformer class 5 | */ 6 | class PieceHasherTransformer implements Transformer { 7 | updateProgress; 8 | constructor(updateProgress?: UpdateProgress) { 9 | this.updateProgress = updateProgress; 10 | } 11 | async transform( 12 | chunk: Uint8Array, 13 | controller: TransformStreamDefaultController 14 | ) { 15 | let pieceHash: Uint8Array; 16 | try { 17 | pieceHash = new Uint8Array(await crypto.subtle.digest("SHA-1", chunk)); 18 | } catch { 19 | const { default: jsSHA1 } = await import("jssha/sha1"); 20 | const sha1Obj = new jsSHA1("SHA-1", "UINT8ARRAY"); 21 | sha1Obj.update(chunk); 22 | pieceHash = sha1Obj.getHash("UINT8ARRAY"); 23 | } 24 | controller.enqueue(pieceHash); 25 | if (this.updateProgress) { 26 | await this.updateProgress(); 27 | } 28 | } 29 | } 30 | 31 | export class PieceHasher extends TransformStream { 32 | constructor(updateProgress?: UpdateProgress) { 33 | const transformer = new PieceHasherTransformer(updateProgress); 34 | super(transformer); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/transformers/tokenizer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BYTE_0, 3 | BYTE_COLON, 4 | BYTE_D, 5 | BYTE_E, 6 | BYTE_I, 7 | BYTE_L, 8 | isDigitByte, 9 | } from "../utils/codec.js"; 10 | import { concatUint8Arrays } from "../utils/misc.js"; 11 | 12 | export enum TokenType { 13 | Integer = "Integer", 14 | ByteString = "ByteString", 15 | ListStart = "ListStart", 16 | ListEnd = "ListEnd", 17 | DictionaryStart = "DictionaryStart", 18 | DictionaryEnd = "DictionaryEnd", 19 | } 20 | 21 | export interface IntegerToken { 22 | type: TokenType.Integer; 23 | value: Uint8Array; 24 | } 25 | 26 | export interface ByteStringToken { 27 | type: TokenType.ByteString; 28 | value: Uint8Array; 29 | } 30 | 31 | export interface ListStartToken { 32 | type: TokenType.ListStart; 33 | } 34 | 35 | export interface ListEndToken { 36 | type: TokenType.ListEnd; 37 | } 38 | 39 | export interface DictionaryStartToken { 40 | type: TokenType.DictionaryStart; 41 | } 42 | 43 | export interface DictionaryEndToken { 44 | type: TokenType.DictionaryEnd; 45 | } 46 | 47 | export type Token = T extends TokenType.Integer 48 | ? IntegerToken 49 | : T extends TokenType.ByteString 50 | ? ByteStringToken 51 | : T extends TokenType.ListStart 52 | ? ListStartToken 53 | : T extends TokenType.ListEnd 54 | ? ListEndToken 55 | : T extends TokenType.DictionaryStart 56 | ? DictionaryStartToken 57 | : T extends TokenType.DictionaryEnd 58 | ? DictionaryEndToken 59 | : never; 60 | 61 | class TokenizerTransformer implements Transformer { 62 | endStack: (TokenType.DictionaryEnd | TokenType.ListEnd)[] = []; 63 | token: Token | null = null; 64 | byteStringLength = -1; 65 | byteStringOffset = 0; 66 | transform( 67 | chunk: Uint8Array, 68 | controller: TransformStreamDefaultController 69 | ) { 70 | this.tokenize(chunk, controller); 71 | } 72 | flush() { 73 | if ( 74 | this.endStack.length || 75 | this.token || 76 | this.byteStringLength !== -1 || 77 | this.byteStringOffset 78 | ) { 79 | throw new SyntaxError("Unexpected end of torrent stream"); 80 | } 81 | } 82 | tokenize( 83 | chunk: Uint8Array, 84 | controller: TransformStreamDefaultController 85 | ) { 86 | // empty chunk 87 | if (chunk.byteLength === 0) { 88 | return; 89 | } 90 | // check token type 91 | if (this.token === null && this.byteStringLength === -1) { 92 | const firstByte = chunk[0] as number; 93 | // integer 94 | if (firstByte === BYTE_I) { 95 | this.token = { 96 | type: TokenType.Integer, 97 | value: new Uint8Array(), 98 | }; 99 | } 100 | // byte string length 101 | else if (isDigitByte(firstByte)) { 102 | // digit to number 103 | this.byteStringLength = firstByte - BYTE_0; 104 | } 105 | // list start 106 | else if (firstByte === BYTE_L) { 107 | // push list end byte to stack 108 | this.endStack.push(TokenType.ListEnd); 109 | // enqueue list start token 110 | controller.enqueue({ 111 | type: TokenType.ListStart, 112 | }); 113 | } 114 | // dictionary start 115 | else if (firstByte === BYTE_D) { 116 | // push dictionary end byte to stack 117 | this.endStack.push(TokenType.DictionaryEnd); 118 | // enqueue dictionary start token 119 | controller.enqueue({ 120 | type: TokenType.DictionaryStart, 121 | }); 122 | } 123 | // list or dictionary end 124 | else if (firstByte === BYTE_E) { 125 | // pop end byte from stack 126 | const tokenType = this.endStack.pop(); 127 | // nothing is popped: unbalanced start and end byte 128 | if (!tokenType) { 129 | throw new SyntaxError("Unbalanced delimiter"); 130 | } 131 | // enqueue list or dictionary end token 132 | controller.enqueue({ 133 | type: tokenType, 134 | }); 135 | } 136 | // unexpected first byte 137 | else { 138 | throw new SyntaxError(`Unexpected byte: ${firstByte}`); 139 | } 140 | // tokenize following bytes 141 | this.tokenize(chunk.subarray(1), controller); 142 | } 143 | // process token 144 | else if (this.token && this.byteStringLength === -1) { 145 | // integer 146 | if (this.token.type === TokenType.Integer) { 147 | const indexOfE = chunk.indexOf(BYTE_E); 148 | // integer end not found 149 | if (indexOfE === -1) { 150 | // gather all bytes into token value 151 | this.token.value = this.token.value 152 | ? concatUint8Arrays(this.token.value, chunk) 153 | : chunk; 154 | } 155 | // integer end found 156 | else { 157 | // gather bytes before end into token value 158 | this.token.value = this.token.value 159 | ? concatUint8Arrays(this.token.value, chunk.subarray(0, indexOfE)) 160 | : chunk.subarray(0, indexOfE); 161 | // equeue integer token (token is copied) 162 | controller.enqueue(this.token); 163 | // reset token to null 164 | this.token = null; 165 | // tokenize following bytes 166 | this.tokenize(chunk.subarray(indexOfE + 1), controller); 167 | } 168 | } 169 | // byte string 170 | else if (this.token.type === TokenType.ByteString) { 171 | // total byte string length 172 | const byteStringLength = this.token.value.byteLength; 173 | // remaining byte string length 174 | const remainingByteStringLength = 175 | byteStringLength - this.byteStringOffset; 176 | // chunk length 177 | const chunkLength = chunk.byteLength; 178 | // chunk length smaller than remaining byte string length 179 | if (chunkLength < remainingByteStringLength) { 180 | // gather all bytes from the chunk 181 | this.token.value.set(chunk, this.byteStringOffset); 182 | // update offset 183 | this.byteStringOffset += chunkLength; 184 | } 185 | // chunk length equal to or greater than remaining byte string length 186 | else { 187 | // gather bytes before end into token value 188 | this.token.value.set( 189 | chunk.subarray(0, remainingByteStringLength), 190 | this.byteStringOffset 191 | ); 192 | // reset byte string offset 193 | this.byteStringOffset = 0; 194 | // equeue byte string token (token is copied) 195 | controller.enqueue(this.token); 196 | // reset token to null 197 | this.token = null; 198 | // tokenize following bytes 199 | this.tokenize(chunk.subarray(remainingByteStringLength), controller); 200 | } 201 | } 202 | // program shouldn't reach here 203 | else { 204 | throw new Error("This is a bug"); 205 | } 206 | } 207 | // process byte length 208 | else if (this.byteStringLength > -1 && this.token === null) { 209 | let indexOfColon = -1; 210 | for (const [index, byte] of chunk.entries()) { 211 | // byte string length digit 212 | if (isDigitByte(byte)) { 213 | // let's assume the byte string length is smaller than the max_safe_integer 214 | // or the torrent file would be huuuuuuuuuuuuuge! 215 | this.byteStringLength = 10 * this.byteStringLength - BYTE_0 + byte; 216 | } 217 | // byte string length end 218 | else if (byte === BYTE_COLON) { 219 | indexOfColon = index; 220 | break; 221 | } 222 | // unexpected byte 223 | else { 224 | throw new SyntaxError(`Unexpected byte: ${byte}`); 225 | } 226 | } 227 | // colon is found 228 | if (indexOfColon !== -1) { 229 | // initialize a byte string token with a fixed length uint8 array 230 | this.token = { 231 | type: TokenType.ByteString, 232 | value: new Uint8Array(this.byteStringLength), 233 | }; 234 | // reset byte string length 235 | this.byteStringLength = -1; 236 | // tokenize following bytes 237 | this.tokenize(chunk.subarray(indexOfColon + 1), controller); 238 | } 239 | } 240 | // program shouldn't reach here 241 | else { 242 | throw new Error("This is a bug"); 243 | } 244 | } 245 | } 246 | 247 | export class Tokenizer extends TransformStream { 248 | constructor() { 249 | const transformer = new TokenizerTransformer(); 250 | super(transformer); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/utils/codec.ts: -------------------------------------------------------------------------------- 1 | // bencode integer type 2 | export type BIntegerStrict = number | bigint; 3 | export type BIntegerLoose = BIntegerStrict | boolean; 4 | export type BInteger = Strict extends true 5 | ? BIntegerStrict 6 | : BIntegerLoose; 7 | 8 | // bencode byte string type 9 | export type BByteStringStrict = string; 10 | export type BByteStringLoose = BByteStringStrict | ArrayBuffer; 11 | export type BByteString = Strict extends true 12 | ? BByteStringStrict 13 | : BByteStringLoose; 14 | 15 | // bencode list type 16 | export type BListStrict = BData[]; 17 | export type BListLoose = BData[]; 18 | export type BList = Strict extends true 19 | ? BListStrict 20 | : BListLoose; 21 | 22 | // bencode dictionary type 23 | export interface BObjectStrict { 24 | [key: BByteString]: BData; 25 | } 26 | export interface BObjectLoose { 27 | [key: BByteString]: BData; 28 | } 29 | export type BObject = Strict extends true 30 | ? BObjectStrict 31 | : BObjectLoose; 32 | export type BMap = Map, BData>; 33 | 34 | export type BDictionaryStrict = BObject; 35 | export type BDictionaryLoose = BObject | BMap; 36 | export type BDictionary = Strict extends true 37 | ? BDictionaryStrict 38 | : BDictionaryLoose; 39 | 40 | // bencode data type 41 | export type BDataStrict = 42 | | BInteger 43 | | BByteString 44 | | BList 45 | | BDictionary; 46 | export type BDataLoose = 47 | | BInteger 48 | | BByteString 49 | | BList 50 | | BDictionary 51 | | undefined 52 | | null; 53 | export type BData = Strict extends true 54 | ? BDataStrict 55 | : BDataLoose; 56 | 57 | /** 58 | * bencode byte: l (stands for list start) 59 | */ 60 | export const BYTE_L = 108; 61 | 62 | /** 63 | * bencode buff: l (stands for list start) 64 | */ 65 | export const BUFF_L = new Uint8Array([BYTE_L]); 66 | 67 | /** 68 | * bencode byte: d (stands for dictionary start) 69 | */ 70 | export const BYTE_D = 100; 71 | 72 | /** 73 | * bencode buff: d (stands for dictionary start) 74 | */ 75 | export const BUFF_D = new Uint8Array([BYTE_D]); 76 | 77 | /** 78 | * bencode byte: e (stands for end) 79 | */ 80 | export const BYTE_E = 101; 81 | 82 | /** 83 | * bencode buff: e (stands for end) 84 | */ 85 | export const BUFF_E = new Uint8Array([BYTE_E]); 86 | 87 | /** 88 | * bencode byte: i (stands for integer start) 89 | */ 90 | export const BYTE_I = 105; 91 | 92 | /** 93 | * bencode buff: i (stands for integer start) 94 | */ 95 | export const BUFF_I = new Uint8Array([BYTE_I]); 96 | 97 | /** 98 | * bencode byte: : (stands for byte string length end) 99 | */ 100 | export const BYTE_COLON = 58; 101 | 102 | /** 103 | * bencode buff: : (stands for byte string length end) 104 | */ 105 | export const BUFF_COLON = new Uint8Array([BYTE_COLON]); 106 | 107 | /** 108 | * bencode byte: - (stands for -) 109 | */ 110 | export const BYTE_MINUS = 45; 111 | 112 | /** 113 | * bencode buff: - (stands for -) 114 | */ 115 | export const BUFF_MINUS = new Uint8Array([BYTE_MINUS]); 116 | 117 | /** 118 | * bencode byte: 0 (stands for 0) 119 | */ 120 | export const BYTE_0 = 48; 121 | 122 | /** 123 | * bencode buff: 0 (stands for 0) 124 | */ 125 | export const BUFF_0 = new Uint8Array([BYTE_0]); 126 | 127 | /** 128 | * is byte digit 129 | * @param byte 130 | * @returns 131 | */ 132 | export function isDigitByte(byte: number) { 133 | if (byte >= BYTE_0 && byte <= BYTE_0 + 9) { 134 | return true; 135 | } 136 | return false; 137 | } 138 | -------------------------------------------------------------------------------- /src/utils/fileDirLike.ts: -------------------------------------------------------------------------------- 1 | export type FileDirLike = FileSystemHandle | FileSystemEntry | File; 2 | 3 | export type FileDirLikes = Iterable | AsyncIterable; 4 | 5 | export function isFile(fileDirLike: FileDirLike): fileDirLike is File { 6 | return fileDirLike instanceof File; 7 | } 8 | 9 | export function isFileSystemDirectoryEntry( 10 | fileDirLike: FileDirLike 11 | ): fileDirLike is FileSystemDirectoryEntry { 12 | return ( 13 | !isFileSystemDirectoryHandle(fileDirLike) && 14 | !isFile(fileDirLike) && 15 | fileDirLike.isDirectory 16 | ); 17 | } 18 | 19 | export function isFileSystemFileEntry( 20 | fileDirLike: FileDirLike 21 | ): fileDirLike is FileSystemFileEntry { 22 | return ( 23 | !isFileSystemDirectoryHandle(fileDirLike) && 24 | !isFile(fileDirLike) && 25 | fileDirLike.isFile 26 | ); 27 | } 28 | 29 | export function isFileSystemDirectoryHandle( 30 | fileDirLike: FileDirLike 31 | ): fileDirLike is FileSystemDirectoryHandle { 32 | try { 33 | if ( 34 | fileDirLike instanceof FileSystemHandle && 35 | fileDirLike.kind === "directory" 36 | ) { 37 | return true; 38 | } 39 | } catch (e) { 40 | /* empty */ 41 | } 42 | return false; 43 | } 44 | 45 | export function isFileSystemFileHandle( 46 | fileDirLike: FileDirLike 47 | ): fileDirLike is FileSystemFileHandle { 48 | try { 49 | if ( 50 | fileDirLike instanceof FileSystemHandle && 51 | fileDirLike.kind === "file" 52 | ) { 53 | return true; 54 | } 55 | } catch (e) { 56 | /* empty */ 57 | } 58 | return false; 59 | } 60 | 61 | function readBatchOfEntries( 62 | dirReader: FileSystemDirectoryReader 63 | ): Promise { 64 | return new Promise((resolve, reject) => { 65 | dirReader.readEntries(resolve, reject); 66 | }); 67 | } 68 | 69 | export async function* getEntriesOfDirEntry( 70 | dirEntry: FileSystemDirectoryEntry 71 | ) { 72 | const dirReader = dirEntry.createReader(); 73 | let batchesOfEntries: FileSystemEntry[] = []; 74 | do { 75 | batchesOfEntries = await readBatchOfEntries(dirReader); 76 | for (const entry of batchesOfEntries) { 77 | yield entry; 78 | } 79 | } while (batchesOfEntries.length > 0); 80 | } 81 | 82 | export function getFileOfFileEntry( 83 | fileSystemFileEntry: FileSystemFileEntry 84 | ): Promise { 85 | return new Promise((resolve, reject) => { 86 | fileSystemFileEntry.file(resolve, reject); 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /src/utils/fileTree.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FileDirLike, 3 | FileDirLikes, 4 | isFile, 5 | isFileSystemDirectoryEntry, 6 | isFileSystemDirectoryHandle, 7 | isFileSystemFileEntry, 8 | isFileSystemFileHandle, 9 | getFileOfFileEntry, 10 | getEntriesOfDirEntry, 11 | } from "./fileDirLike.js"; 12 | 13 | import { BObject } from "./codec.js"; 14 | 15 | import { getSortedIndex } from "./misc.js"; 16 | import { v4 } from "uuid"; 17 | 18 | /** 19 | * symlink file attribute 20 | */ 21 | export type SymlinkAttr = "s"; 22 | 23 | /** 24 | * executable file attribute 25 | */ 26 | export type ExecutableAttr = "x"; 27 | 28 | /** 29 | * hidden file attribute 30 | */ 31 | export type HiddenAttr = "h"; 32 | 33 | /** 34 | * padding file attribute 35 | */ 36 | export type PaddingFileAttr = "p"; 37 | 38 | /** 39 | * permutations template 40 | */ 41 | export type Permutations< 42 | T extends string, 43 | U extends string = T 44 | > = T extends unknown ? T | `${T}${Permutations>}` : never; 45 | 46 | /** 47 | * file attributes 48 | */ 49 | export type FileAttrs = Permutations< 50 | SymlinkAttr | ExecutableAttr | HiddenAttr | PaddingFileAttr 51 | >; 52 | 53 | /** 54 | * base file props 55 | */ 56 | export interface FilePropsBase extends BObject { 57 | /** 58 | * Length of the file in bytes 59 | * 60 | * [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=the%20following%20keys%3A-,length,-%2D%20The%20length%20of) 61 | * | 62 | * [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#upgrade-path:~:text=pieces%20root32%3Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaeeeeeee-,length,-Length%20of%20the) 63 | */ 64 | length: number; 65 | /** 66 | * A variable-length string. When present, 67 | * the characters each represent a file attribute: 68 | * ``` 69 | * l = symlink 70 | * x = executable 71 | * h = hidden 72 | * p = padding file 73 | * ``` 74 | * [BEP 47](https://www.bittorrent.org/beps/bep_0047.html#:~:text=20%20bytes%3E%2C%0A%20%20%20%20...%0A%20%20%7D%2C%0A%20%20...%0A%7D-,attr,-A%20variable%2Dlength) 75 | */ 76 | attr?: FileAttrs; 77 | } 78 | 79 | /** 80 | * v1 file props 81 | */ 82 | export interface FilePropsV1 extends FilePropsBase { 83 | /** 84 | * A list of UTF-8 encoded strings 85 | * corresponding to **subdirectory** names 86 | * 87 | * [BEP 3](https://www.bittorrent.org/beps/bep_0003.html#:~:text=file%2C%20in%20bytes.-,path,-%2D%20A%20list%20of) 88 | */ 89 | path: string[]; 90 | } 91 | 92 | /** 93 | * v2 file props 94 | */ 95 | export interface FilePropsV2 extends FilePropsBase { 96 | /** 97 | * For **non-empty** files this is the the root hash 98 | * of a merkle tree with a branching factor of 2, 99 | * constructed from 16KiB blocks of the file 100 | * 101 | * [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=any%20sibling%20entries.-,pieces%20root,-For%20non%2Dempty) 102 | */ 103 | ["pieces root"]?: ArrayBuffer; 104 | } 105 | 106 | /** 107 | * v1 file list 108 | */ 109 | export type FileListV1 = FilePropsV1[]; 110 | 111 | /** 112 | * v2 file tree file node 113 | */ 114 | export interface FileTreeFileNode extends BObject { 115 | /** 116 | * Entries with zero-length keys describe the properties 117 | * of the composed path at that point 118 | * 119 | * [BEP 52](https://www.bittorrent.org/beps/bep_0052.html#:~:text=Entries%20with%20zero%2Dlength%20keys%20describe%20the%20properties%20of%20the%20composed%20path%20at%20that%20point) 120 | */ 121 | "": FilePropsV2; 122 | } 123 | 124 | /** 125 | * v2 file entry 126 | */ 127 | export type FileTreeFileEntry = [filename: string, value: FileTreeFileNode]; 128 | 129 | /** 130 | * v2 file tree dir node 131 | */ 132 | export type FileTreeDirNode = Map; 133 | 134 | /** 135 | * v2 dir entry 136 | */ 137 | export type FileTreeDirEntry = [dirname: string, value: FileTreeEntries]; 138 | 139 | /** 140 | * v2 packed dir entry 141 | */ 142 | export type PackedFileTreeDirEntry = [dirname: string, value: FileTreeDirNode]; 143 | 144 | /** 145 | * v2 file or dir entries 146 | */ 147 | export type FileTreeEntries = (FileTreeFileEntry | FileTreeDirEntry)[]; 148 | 149 | /** 150 | * v2 packed file or dir entries 151 | */ 152 | export type PackedFileTreeEntries = ( 153 | | FileTreeFileEntry 154 | | PackedFileTreeDirEntry 155 | )[]; 156 | 157 | export function resolveCommonDirAndTorrentName( 158 | name: string | undefined, 159 | fileTree: FileTreeDirNode 160 | ): { name: string; commonDir: string | undefined } { 161 | // zero or multi file or folders 162 | if (fileTree.size > 1 || fileTree.size === 0) { 163 | return { 164 | // TODO: change UUID v4 to v5 ? 165 | name: name || v4(), 166 | commonDir: undefined, 167 | }; 168 | } 169 | const [firstNodeName, firstNode] = fileTree.entries().next().value as 170 | | FileTreeFileEntry 171 | | PackedFileTreeDirEntry; 172 | // one file 173 | if (isFileTreeFileNode(firstNode)) { 174 | return { 175 | name: name || firstNodeName, 176 | commonDir: undefined, 177 | }; 178 | } 179 | // one dir 180 | return { 181 | name: name || firstNodeName, 182 | commonDir: firstNodeName, 183 | }; 184 | } 185 | 186 | export type PopulateOptions = 187 | | { 188 | sort?: false; 189 | compareFunction?: never; 190 | polyfillWebkitRelativePath?: boolean; 191 | } 192 | | { 193 | sort?: true; 194 | compareFunction?: ( 195 | entry: FileTreeFileEntry | FileTreeDirEntry, 196 | name: string 197 | ) => number; 198 | polyfillWebkitRelativePath?: boolean; 199 | }; 200 | 201 | export type TraverseTree = ( 202 | node: FileTreeDirNode | FileTreeFileNode 203 | ) => Generator<[FileTreeFileNode, File], void, unknown>; 204 | 205 | /** 206 | * Parse file-dir-likes into a file tree 207 | * @param fileDirLikes 208 | * @param opts populate options 209 | * @returns file tree and a traverse function 210 | */ 211 | export async function populateFileTree( 212 | fileDirLikes: FileDirLikes, 213 | { 214 | sort = true, 215 | compareFunction = compareEntryNames, 216 | polyfillWebkitRelativePath = true, 217 | }: PopulateOptions = { 218 | sort: true, 219 | compareFunction: compareEntryNames, 220 | polyfillWebkitRelativePath: true, 221 | } 222 | ): Promise<{ 223 | fileTree: FileTreeDirNode; 224 | traverseTree: TraverseTree; 225 | totalFileSize: number; 226 | totalFileCount: number; 227 | }> { 228 | let totalFileSize = 0; 229 | let totalFileCount = 0; 230 | // weak map for internal use 231 | const fileEntryToHandleMap: WeakMap = 232 | new WeakMap(); 233 | const dirEntryToHandleMap: WeakMap< 234 | FileTreeDirEntry, 235 | FileSystemDirectoryHandle 236 | > = new WeakMap(); 237 | // weak map for traversal 238 | const fileNodeToFileMap: WeakMap = new WeakMap(); 239 | // states 240 | const rootDirEntry: FileTreeDirEntry = ["file tree", []]; 241 | const dirEntryStack: FileTreeDirEntry[] = [rootDirEntry]; 242 | // flag: should deep pack 243 | let shouldDeepPack = false; 244 | // recursively populate 245 | for await (const fileDirLike of fileDirLikes) { 246 | await recursivelyPopulateFileTree(fileDirLike); 247 | } 248 | return { 249 | // NOTE: TYPE MUTATION! 250 | fileTree: packEntriesToDirNode(rootDirEntry[1], shouldDeepPack), 251 | traverseTree, 252 | totalFileSize, 253 | totalFileCount, 254 | }; 255 | // recursive function 256 | async function recursivelyPopulateFileTree(fileDirLike: FileDirLike) { 257 | // get current entry 258 | const currentDirEntry = dirEntryStack.at(-1); 259 | if (!currentDirEntry) { 260 | throw new Error("This is a bug"); 261 | } 262 | // flag: input is file 263 | const inputIsFile = isFile(fileDirLike); 264 | // flag: input is file system directory handle 265 | const inputIsFileSystemDirectoryHandle = 266 | isFileSystemDirectoryHandle(fileDirLike); 267 | // flag: input is file system directory entry 268 | const inputIsFileSystemDirectoryEntry = 269 | isFileSystemDirectoryEntry(fileDirLike); 270 | // flag: input is file system file handle 271 | const inputIsFileSystemFileHandle = isFileSystemFileHandle(fileDirLike); 272 | // flag: input is file system file entry 273 | const inputIsFileSystemFileEntry = isFileSystemFileEntry(fileDirLike); 274 | // directory handle or entry 275 | if (inputIsFileSystemDirectoryHandle || inputIsFileSystemDirectoryEntry) { 276 | const dirName = fileDirLike.name; 277 | const { index, result } = getSortedIndex( 278 | currentDirEntry[1], 279 | dirName, 280 | compareFunction 281 | ); 282 | let subDirEntry = result; 283 | if (isFileTreeFileEntry(subDirEntry)) { 284 | throw new Error("A same name file already exists"); 285 | } 286 | if (isFileTreeDirEntry(subDirEntry) && inputIsFileSystemDirectoryHandle) { 287 | const registeredHandle = dirEntryToHandleMap.get(subDirEntry); 288 | const isUnregistered = 289 | !registeredHandle || 290 | !(await fileDirLike.isSameEntry(registeredHandle)); 291 | if (isUnregistered) { 292 | throw new Error("File system handle not match"); 293 | } 294 | } 295 | if (!subDirEntry) { 296 | subDirEntry = [dirName, []]; 297 | sort 298 | ? currentDirEntry[1].splice(index, 0, subDirEntry) 299 | : currentDirEntry[1].push(subDirEntry); 300 | if (inputIsFileSystemDirectoryHandle) { 301 | dirEntryToHandleMap.set(subDirEntry, fileDirLike); 302 | } 303 | } 304 | dirEntryStack.push(subDirEntry); 305 | for await (const childFileSystemHandle of inputIsFileSystemDirectoryHandle 306 | ? fileDirLike.values() 307 | : getEntriesOfDirEntry(fileDirLike)) { 308 | await recursivelyPopulateFileTree(childFileSystemHandle); 309 | } 310 | // NOTE: TYPE MUTATION! 311 | (subDirEntry[1] as unknown as FileTreeDirNode) = new Map< 312 | string, 313 | FileTreeDirNode | FileTreeFileNode 314 | >(subDirEntry[1] as PackedFileTreeEntries); 315 | dirEntryStack.pop(); 316 | } 317 | // file handle or entry 318 | else if (inputIsFileSystemFileHandle || inputIsFileSystemFileEntry) { 319 | const fileName = fileDirLike.name; 320 | const { index, result } = getSortedIndex( 321 | currentDirEntry[1], 322 | fileName, 323 | compareFunction 324 | ); 325 | let fileEntry = result; 326 | if (isFileTreeFileEntry(fileEntry) && inputIsFileSystemFileHandle) { 327 | const registeredHandle = fileEntryToHandleMap.get(fileEntry); 328 | const isUnregistered = 329 | !registeredHandle || 330 | !(await fileDirLike.isSameEntry(registeredHandle)); 331 | if (isUnregistered) { 332 | throw new Error("A same name file already exists"); 333 | } 334 | } 335 | if (isFileTreeDirEntry(fileEntry)) { 336 | throw new Error("A same name directory already exists"); 337 | } 338 | if (fileEntry) { 339 | return; 340 | } 341 | const file = await (inputIsFileSystemFileHandle 342 | ? fileDirLike.getFile() 343 | : getFileOfFileEntry(fileDirLike)); 344 | const fileSize = file.size; 345 | if (polyfillWebkitRelativePath) { 346 | const webkitRelativePath = 347 | dirEntryStack.reduce( 348 | (prevPath, dirEntry, index) => 349 | index === 0 ? prevPath : prevPath + dirEntry[0] + "/", 350 | "" 351 | ) + file.name; 352 | Object.defineProperty(file, "webkitRelativePath", { 353 | configurable: true, 354 | enumerable: true, 355 | get: () => webkitRelativePath, 356 | }); 357 | } 358 | const fileTreeFileNode: FileTreeFileNode = { 359 | "": { 360 | length: fileSize, 361 | }, 362 | }; 363 | fileEntry = [fileName, fileTreeFileNode]; 364 | sort 365 | ? currentDirEntry[1].splice(index, 0, fileEntry) 366 | : currentDirEntry[1].push(fileEntry); 367 | if (inputIsFileSystemFileHandle) { 368 | fileEntryToHandleMap.set(fileEntry, fileDirLike); 369 | } 370 | fileNodeToFileMap.set(fileTreeFileNode, file); 371 | totalFileSize += fileSize; 372 | totalFileCount += 1; 373 | } 374 | // file 375 | else if (inputIsFile) { 376 | if (polyfillWebkitRelativePath && fileDirLike.webkitRelativePath === "") { 377 | Object.defineProperty(fileDirLike, "webkitRelativePath", { 378 | configurable: true, 379 | enumerable: true, 380 | get: () => fileDirLike.name, 381 | }); 382 | } 383 | const pathSegments = ( 384 | fileDirLike.webkitRelativePath || fileDirLike.name 385 | ).split("/"); 386 | const localDirEntryStack = [rootDirEntry]; 387 | for (const [segmentIndex, pathSegment] of pathSegments.entries()) { 388 | if (segmentIndex >= 1) { 389 | shouldDeepPack = true; 390 | } 391 | const localCurrentDirEntry = localDirEntryStack.at(-1); 392 | if (!localCurrentDirEntry) { 393 | throw new Error("This is a bug"); 394 | } 395 | const { index, result } = getSortedIndex( 396 | localCurrentDirEntry[1], 397 | pathSegment, 398 | compareFunction 399 | ); 400 | // dir 401 | if (segmentIndex < pathSegments.length - 1) { 402 | let subDirEntry = result; 403 | if (isFileTreeFileEntry(subDirEntry)) { 404 | throw new Error("A same name file already exists"); 405 | } 406 | if (!subDirEntry) { 407 | subDirEntry = [pathSegment, []]; 408 | sort 409 | ? localCurrentDirEntry[1].splice(index, 0, subDirEntry) 410 | : localCurrentDirEntry[1].push(subDirEntry); 411 | } 412 | localDirEntryStack.push(subDirEntry); 413 | } 414 | // file 415 | else { 416 | let fileEntry = result; 417 | if (isFileTreeDirEntry(fileEntry)) { 418 | throw new Error("A same name directory already exists"); 419 | } 420 | if (fileEntry) { 421 | return; 422 | } 423 | const fileSize = fileDirLike.size; 424 | const fileTreeFileNode: FileTreeFileNode = { 425 | "": { 426 | length: fileSize, 427 | }, 428 | }; 429 | fileEntry = [pathSegment, fileTreeFileNode]; 430 | sort 431 | ? localCurrentDirEntry[1].splice(index, 0, fileEntry) 432 | : localCurrentDirEntry[1].push(fileEntry); 433 | fileNodeToFileMap.set(fileTreeFileNode, fileDirLike); 434 | totalFileSize += fileSize; 435 | totalFileCount += 1; 436 | } 437 | } 438 | } 439 | // unrecognized type 440 | else { 441 | throw new Error("Unrecognized type of input"); 442 | } 443 | } 444 | // traverse function 445 | function* traverseTree( 446 | node: FileTreeDirNode | FileTreeFileNode 447 | ): Generator<[FileTreeFileNode, File], void, unknown> { 448 | if (isFileTreeDirNode(node)) { 449 | for (const subNode of node.values()) { 450 | yield* traverseTree(subNode); 451 | } 452 | } else { 453 | yield [node, fileNodeToFileMap.get(node) as File]; 454 | } 455 | } 456 | } 457 | 458 | export function packEntriesToDirNode( 459 | entries: PackedFileTreeEntries | FileTreeEntries, 460 | shouldDeepPack = false 461 | ): FileTreeDirNode { 462 | if (shouldDeepPack) { 463 | recursivelyPack(entries as FileTreeEntries); 464 | } 465 | return new Map( 466 | entries as PackedFileTreeEntries 467 | ); 468 | } 469 | 470 | function recursivelyPack(fileTreeEntries: FileTreeEntries) { 471 | for (const fileTreeEntry of fileTreeEntries) { 472 | if (isFileTreeDirEntry(fileTreeEntry)) { 473 | const newEntries = fileTreeEntry[1]; 474 | recursivelyPack(newEntries); 475 | (fileTreeEntry[1] as unknown as FileTreeDirNode) = new Map< 476 | string, 477 | FileTreeDirNode | FileTreeFileNode 478 | >(newEntries as PackedFileTreeEntries); 479 | } 480 | } 481 | } 482 | 483 | export function isFileTreeFileNode( 484 | node: FileTreeFileNode | FileTreeDirNode | undefined 485 | ): node is FileTreeFileNode { 486 | return node ? "" in node : false; 487 | } 488 | 489 | export function isFileTreeDirNode( 490 | node: FileTreeFileNode | FileTreeDirNode | undefined 491 | ): node is FileTreeDirNode { 492 | return node instanceof Map; 493 | } 494 | 495 | export function isFileTreeFileEntry( 496 | entry: FileTreeFileEntry | FileTreeDirEntry | undefined 497 | ): entry is FileTreeFileEntry { 498 | return entry ? "" in entry[1] : false; 499 | } 500 | 501 | export function isFileTreeDirEntry( 502 | entry: FileTreeFileEntry | FileTreeDirEntry | undefined 503 | ): entry is FileTreeDirEntry { 504 | return entry ? !("" in entry[1]) : false; 505 | } 506 | 507 | // compare function 508 | export function compareEntryNames( 509 | entry: FileTreeFileEntry | FileTreeDirEntry, 510 | name: string 511 | ) { 512 | return entry[0] < name ? -1 : entry[0] > name ? 1 : 0; 513 | } 514 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./codec.js"; 2 | export * from "./fileDirLike.js"; 3 | export * from "./fileTree.js"; 4 | export * from "./misc.js"; 5 | -------------------------------------------------------------------------------- /src/utils/misc.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A helper function to determine the index 3 | * of a new value in an existed sorted array 4 | * @param array an existed sorted array 5 | * @param value the new value whose index is to be determined 6 | * @param compareFunction a compare function to determine the order, like the one in Array.prototype.sort() 7 | * @returns index of the value in the sorted array and indexed result if found 8 | */ 9 | export function getSortedIndex( 10 | array: T[], 11 | value: V, 12 | compareFunction: (a: T, b: V) => number 13 | ) { 14 | let low = 0; 15 | let high = array.length; 16 | while (low < high) { 17 | const mid = (low + high) >>> 1; 18 | const result = array[mid] as T; 19 | const compareResult = compareFunction(result, value); 20 | if (compareResult === 0) { 21 | return { 22 | index: mid, 23 | result: array[mid], 24 | }; 25 | } 26 | if (compareResult < 0) { 27 | low = mid + 1; 28 | } else { 29 | high = mid; 30 | } 31 | } 32 | return { 33 | index: low, 34 | result: undefined, 35 | }; 36 | } 37 | 38 | /** 39 | * Sort iterable into a sorted array 40 | * @param iterable 41 | * @param compareFunction 42 | * @returns sorted array 43 | */ 44 | export function iterableSort( 45 | iterable: IterableIterator, 46 | compareFunction: (a: T, b: T) => number 47 | ) { 48 | const sorted: T[] = []; 49 | for (const item of iterable) { 50 | const { index } = getSortedIndex(sorted, item, compareFunction); 51 | sorted.splice(index, 0, item); 52 | } 53 | return sorted; 54 | } 55 | 56 | /** 57 | * concat uint8 arrays 58 | * @param uint8Arrays 59 | * @returns 60 | */ 61 | export function concatUint8Arrays(...uint8Arrays: Uint8Array[]) { 62 | const result = new Uint8Array( 63 | uint8Arrays.reduce( 64 | (length, uint8Array) => length + uint8Array.byteLength, 65 | 0 66 | ) 67 | ); 68 | uint8Arrays.reduce((offset, uint8Array) => { 69 | result.set(uint8Array, offset); 70 | return offset + uint8Array.byteLength; 71 | }, 0); 72 | return result; 73 | } 74 | 75 | /** 76 | * Calculate the next nearest power of 2 77 | * @param number number 78 | * @returns next nearest power of 2 79 | */ 80 | export function nextPowerOfTwo(number: number) { 81 | return 1 << (32 - Math.clz32(number - 1)); 82 | } 83 | 84 | /** 85 | * Get time stamp seconds now 86 | * @returns time stamp now in seconds 87 | */ 88 | export function getTimeStampSecondsNow() { 89 | return Math.round(Date.now() / 1000); 90 | } 91 | 92 | /** 93 | * Calculate the root hash of the merkle tree 94 | * constructed by given leaves 95 | * @param leaves merkle leaves 96 | * @returns root hash 97 | */ 98 | export async function merkleRoot(leaves: Uint8Array[]): Promise { 99 | if (leaves.length === 0) { 100 | throw new Error("Empty leaves"); 101 | } 102 | const content = new Uint8Array(64); 103 | while (leaves.length > 1) { 104 | let hashFlag = false; 105 | const hashResult: Uint8Array[] = []; 106 | for (const leaf of leaves) { 107 | if (!hashFlag) { 108 | content.set(leaf); 109 | hashFlag = true; 110 | } else { 111 | content.set(leaf, 32); 112 | let hash: Uint8Array; 113 | try { 114 | hash = new Uint8Array(await crypto.subtle.digest("SHA-256", content)); 115 | } catch { 116 | const { default: jsSHA256 } = await import("jssha/sha256"); 117 | const sha256Obj = new jsSHA256("SHA-256", "UINT8ARRAY"); 118 | sha256Obj.update(content); 119 | hash = sha256Obj.getHash("UINT8ARRAY"); 120 | } 121 | hashResult.push(hash); 122 | hashFlag = false; 123 | } 124 | } 125 | leaves = hashResult; 126 | } 127 | return leaves[0] as Uint8Array; 128 | } 129 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "NodeNext", 6 | "types": ["wicg-file-system-access"], 7 | "allowJs": true, 8 | "outDir": "dist", 9 | "declaration": true, 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "strict": true, 13 | "noImplicitAny": true, 14 | "noUnusedLocals": true, 15 | "skipLibCheck": true, 16 | "noUncheckedIndexedAccess": true 17 | }, 18 | "include": ["src/**/*"], 19 | "exclude": ["dist", "test"] 20 | } 21 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | import webpack from "webpack"; 2 | import { fileURLToPath } from "node:url"; 3 | import { resolve, dirname } from "node:path"; 4 | import pkg from "./package.json" assert { type: "json" }; 5 | 6 | const __filename = fileURLToPath(import.meta.url); 7 | const __dirname = dirname(__filename); 8 | 9 | /** 10 | * 11 | * @param {"esm"|"umd"} type 12 | * @returns { webpack.Configuration } config 13 | */ 14 | function getConfig(type) { 15 | return { 16 | mode: "production", 17 | entry: { 18 | index: resolve(__dirname, "src/index.ts"), 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.ts$/, 24 | use: { 25 | loader: "ts-loader", 26 | ...(type === "umd" && { 27 | options: { 28 | compilerOptions: { 29 | declaration: false, 30 | }, 31 | }, 32 | }), 33 | }, 34 | exclude: [resolve(__dirname, "node_modules")], 35 | }, 36 | ], 37 | }, 38 | resolve: { 39 | extensions: [".ts", ".js"], 40 | extensionAlias: { 41 | ".js": [".ts", ".js"], 42 | }, 43 | }, 44 | plugins: [ 45 | new webpack.DefinePlugin({ 46 | CREATED_BY: JSON.stringify(`${pkg.name} v${pkg.version}`), 47 | }), 48 | new webpack.optimize.LimitChunkCountPlugin({ 49 | maxChunks: 1, 50 | }), 51 | ], 52 | output: 53 | type === "umd" 54 | ? { 55 | filename: "[name].umd.js", 56 | path: resolve(__dirname, "dist"), 57 | library: { 58 | name: pkg.name.replace(/-([a-z])/g, function (g) { 59 | return g[1].toUpperCase(); 60 | }), 61 | type: "umd", 62 | }, 63 | } 64 | : { 65 | filename: "[name].js", 66 | path: resolve(__dirname, "dist"), 67 | library: { 68 | type: "module", 69 | }, 70 | chunkFormat: "module", 71 | }, 72 | experiments: { 73 | outputModule: type === "umd" ? false : true, 74 | }, 75 | target: "web", 76 | }; 77 | } 78 | 79 | /** 80 | * Webpack Configuration 81 | * @type { webpack.Configuration } 82 | */ 83 | export default [getConfig("esm"), getConfig("umd")]; 84 | --------------------------------------------------------------------------------