├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── SECURITY.md ├── SUPPORT.md └── pull_request_template.md ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dist ├── iota-client-load-balancer.js └── iota-client-load-balancer.min.js ├── docs ├── README.md └── api.md ├── es ├── composeAPI.js ├── index.js ├── internals.js ├── mam.js ├── models │ ├── failMode.js │ ├── loadBalancerSettings.js │ ├── nodeConfiguration.js │ ├── nodeWalkStrategy.js │ └── successMode.js └── walkStrategies │ ├── baseWalkStrategy.js │ ├── linearWalkStrategy.js │ └── randomWalkStrategy.js ├── examples ├── README.md ├── mam │ ├── index.js │ ├── package-lock.json │ └── package.json └── simple │ ├── index.js │ ├── package-lock.json │ └── package.json ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── composeAPI.ts ├── index.ts ├── internals.ts ├── mam.ts ├── models │ ├── failMode.ts │ ├── loadBalancerSettings.ts │ ├── nodeConfiguration.ts │ ├── nodeWalkStrategy.ts │ └── successMode.ts └── walkStrategies │ ├── baseWalkStrategy.ts │ ├── linearWalkStrategy.ts │ └── randomWalkStrategy.ts ├── test ├── baseWalkStrategy.spec.ts ├── linearWalkStrategy.spec.ts └── randomWalkStrategy.spec.ts ├── tsconfig.json ├── tslint.json └── typings ├── composeAPI.d.ts ├── index.d.ts ├── internals.d.ts ├── mam.d.ts ├── models ├── failMode.d.ts ├── loadBalancerSettings.d.ts ├── nodeConfiguration.d.ts ├── nodeWalkStrategy.d.ts └── successMode.d.ts └── walkStrategies ├── baseWalkStrategy.d.ts ├── linearWalkStrategy.d.ts └── randomWalkStrategy.d.ts /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Code of Conduct 2 | 3 | In the IOTA community, participants from all over the world come together to create. This is made possible by the support, hard work and enthusiasm of thousands of people, including those who create and use the IOTA technology. 4 | 5 | This document offers some guidance to ensure IOTA participants can cooperate effectively in a positive and inspiring atmosphere, and to explain how together we can strengthen and support each other. 6 | 7 | This Code of Conduct is shared by all contributors and users who engage with the IOTA Foundation team and its community services. 8 | 9 | ## Overview 10 | 11 | This Code of Conduct presents a summary of the shared values and “common sense” thinking in our community. The basic social ingredients that hold our project together include: 12 | 13 | - Being considerate 14 | - Being respectful 15 | - Being collaborative 16 | - Being pragmatic 17 | - Supporting others in the community 18 | - Getting support from others in the community 19 | 20 | This Code of Conduct reflects the agreed standards of behavior for members of the IOTA community, in any social media platform, forum, mailing list, wiki, web site, discord channel, public meeting or private correspondence within the context of the IOTA Foundation team and the IOTA Tangle technology. The community acts according to the standards written down in this Code of Conduct and will defend these standards for the benefit of the community. Leaders of any group, such as moderators of social media groups, mailing lists, discord channels, forums, etc., will exercise the right to suspend access to any person who persistently breaks our shared Code of Conduct. 21 | 22 | ## Be considerate 23 | 24 | Your actions and work will affect and be used by other people and you, in turn, will depend on the work and actions of others. Any decision you take will affect other community members, and we expect you to take those consequences into account when making decisions. 25 | 26 | As a user, remember that community members work hard on their part of IOTA and take great pride in it. 27 | 28 | ## Be respectful 29 | 30 | In order for the IOTA community to stay healthy, its members must feel comfortable and accepted. Treating one another with respect is absolutely necessary for this. In a disagreement, in the first instance, assume that people mean well. 31 | 32 | We do not tolerate personal attacks, racism, sexism or any other form of discrimination. Disagreement is inevitable, from time to time, but respect for the views of others will go a long way to winning respect for your own view. Respecting other people, their work, their contributions and assuming well-meaning motivation will make community members feel comfortable and safe and will result in motivation and productivity. 33 | 34 | We expect members of our community to be respectful when dealing with other contributors, users, and communities. Remember that IOTA is an international project and that you may be unaware of important aspects of other cultures. 35 | 36 | ## Be collaborative 37 | 38 | Your feedback is important, as is its form. Poorly thought out comments can cause pain and the demotivation of other community members, but considerate discussion of problems can bring positive results. An encouraging word works wonders. 39 | 40 | ## Be pragmatic 41 | 42 | The IOTA community is pragmatic and fair. We value tangible results over having the last word in a discussion. We defend our core values like freedom and respectful collaboration, but we don’t let arguments about minor issues get in the way of achieving more important results. We are open to suggestions and welcome solutions regardless of their origin. When in doubt support a solution which helps to get things done over one which has theoretical merits, but isn’t being worked on. Use the tools and methods which help to get the job done. Let decisions be taken by those who do the work. 43 | 44 | ## Support others in the community 45 | 46 | The IOTA community is made strong by mutual respect, collaboration and pragmatic, responsible behavior. Sometimes there are situations where this has to be defended and other community members need help. 47 | 48 | If you witness others being attacked, think first about how you can offer them personal support. If you feel that the situation is beyond your ability to help individually, go privately to the victim and ask if some form of official intervention is needed. 49 | 50 | When problems do arise, consider respectfully reminding those involved of our shared Code of Conduct as a first action. Leaders are defined by their actions and can help set a good example by working to resolve issues in the spirit of this Code of Conduct before they escalate. 51 | 52 | ## Get support from others in the community 53 | 54 | Disagreements, both political and technical, happen all the time. Our community is no exception to the rule. The goal is not to avoid disagreements or differing views but to resolve them constructively. You should turn to the community to seek advice and to resolve disagreements and where possible consult the team most directly involved. 55 | 56 | Think deeply before turning a disagreement into a public dispute. If necessary, request mediation, and try to resolve differences in a less emotional medium. If you do feel that you or your work is being attacked, take your time to think things through before writing heated replies. Consider a 24-hour moratorium if emotional language is being used – a cooling-off period is sometimes all that is needed. If you really want to go a different way, then we encourage you to publish your ideas and your work, so that it can be tried and tested. 57 | 58 | This work, "IOTA Community Guidelines", is a derivative of the [Community code of conduct by ownCloud](https://owncloud.org/community/code-of-conduct/), used under [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/). "IOTA Community Guidelines" is licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) by IOTA Foundation. -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute to the Client Load Balancer 2 | 3 | This document describes how to contribute to the Client Load Balancer. 4 | 5 | We encourage everyone with knowledge of IOTA technology to contribute. 6 | 7 | Thanks! :heart: 8 | 9 |
10 | Do you have a question :question: 11 |
12 | 13 | If you have a general or technical question, you can use one of the following resources instead of submitting an issue: 14 | 15 | - [**Developer documentation:**](https://docs.iota.org/) For official information about developing with IOTA technology 16 | - [**Discord:**](https://discord.iota.org/) For real-time chats with the developers and community members 17 | - [**IOTA cafe:**](https://iota.cafe/) For technical discussions with the Research and Development Department at the IOTA Foundation 18 | - [**StackExchange:**](https://iota.stackexchange.com/) For technical and troubleshooting questions 19 |
20 | 21 |
22 | 23 |
24 | Ways to contribute :mag: 25 |
26 | 27 | To contribute to the Client Load Balancer on GitHub, you can: 28 | 29 | - Report a bug 30 | - Suggest a new feature 31 | - Build a new feature 32 |
33 | 34 |
35 | 36 |
37 | Report a bug :bug: 38 |
39 | 40 | This section guides you through reporting a bug. Following these guidelines helps maintainers and the community understand the bug, reproduce the behavior, and find related bugs. 41 | 42 | ### Before reporting a bug 43 | 44 | Please check the following list: 45 | 46 | - **Do not open a GitHub issue for [security vulnerabilities](.github/SECURITY.MD)**, instead, please contact us at [security@iota.org](mailto:security@iota.org). 47 | 48 | - **Ensure the bug was not already reported** by searching on GitHub under [**Issues**](https://github.com/iotaledger/client-load-balancer/issues). If the bug has already been reported **and the issue is still open**, add a comment to the existing issue instead of opening a new one. 49 | 50 | **Note:** If you find a **Closed** issue that seems similar to what you're experiencing, open a new issue and include a link to the original issue in the body of your new one. 51 | 52 | ### Submitting A Bug Report 53 | 54 | To report a bug, [open a new issue](https://github.com/iotaledger/client-load-balancer/issues/new), and be sure to include as many details as possible, using the template. 55 | 56 | **Note:** Minor changes such as fixing a typo can but do not need an open issue. 57 | 58 | If you also want to fix the bug, submit a [pull request](#pull-requests) and reference the issue. 59 |
60 | 61 |
62 | 63 |
64 | Suggest a new feature :bulb: 65 |
66 | 67 | This section guides you through suggesting a new feature. Following these guidelines helps maintainers and the community collaborate to find the best possible way forward with your suggestion. 68 | 69 | ### Before suggesting a new feature 70 | 71 | **Ensure the feature has not already been suggested** by searching on GitHub under [**Issues**](https://github.com/iotaledger/client-load-balancer/issues). 72 | 73 | ### Suggesting a new feature 74 | 75 | To suggest a new feature, talk to the IOTA community and IOTA Foundation members on [Discord](https://discord.iota.org/). 76 | 77 | If the team members approve your feature, they will create an issue for it. 78 |
79 | 80 |
81 | 82 |
83 | Build a new feature :hammer: 84 |
85 | 86 | This section guides you through building a new feature. Following these guidelines helps give your feature the best chance of being approved and merged. 87 | 88 | ### Before building a new feature 89 | 90 | Make sure to discuss the feature on [Discord](https://discord.iota.org/). 91 | 92 | ### Building a new feature 93 | 94 | To build a new feature, check out a new branch based on the `master` branch, and be sure to consider the following: 95 | 96 | - If the feature has a public facing API, make sure to document it, using JSDoc code comments 97 |
98 | 99 |
100 | 101 |
102 | Pull requests :mega: 103 |
104 | 105 | This section guides you through submitting a pull request (PR). Following these guidelines helps give your PR the best chance of being approved and merged. 106 | 107 | ### Before submitting a pull request 108 | 109 | When creating a pull request, please follow these steps to have your contribution considered by the maintainers: 110 | 111 | - A pull request should have exactly one concern (for example one feature or one bug). If a PR addresses more than one concern, it should be split into two or more PRs. 112 | 113 | - A pull request can be merged only if it references an open issue 114 | 115 | **Note:** Minor changes such as fixing a typo can but do not need an open issue. 116 | 117 | ### Submitting a pull request 118 | 119 | The following is a typical workflow for submitting a new pull request: 120 | 121 | 1. Fork this repository 122 | 2. Create a new branch based on your fork. For example, `git checkout -b fix/my-fix` or ` git checkout -b feat/my-feature`. 123 | 3. Commit changes and push them to your fork 124 | 4. Target your pull request to be merged with `master` 125 | 126 | If all [status checks](https://help.github.com/articles/about-status-checks/) pass, and the maintainer approves the PR, it will be merged. 127 | 128 | **Note:** Reviewers may ask you to complete additional work, tests, or other changes before your pull request can be approved and merged. 129 |
130 | 131 |
132 | 133 |
134 | Code of Conduct :clipboard: 135 |
136 | 137 | This project and everyone participating in it is governed by the [IOTA Code of Conduct](.github/CODE_OF_CONDUCT.md). -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report a bug in the Client Load Balancer 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: bug 6 | --- 7 | 8 | ## Bug description 9 | 10 | Briefly describe the bug. 11 | 12 | ## Node.js version 13 | 14 | Which version of Node.js are you running? 15 | 16 | - Node.js version: 17 | 18 | ## Hardware specification 19 | 20 | What hardware are you using? 21 | 22 | - Operating system: 23 | - RAM: 24 | - Cores: 25 | - Device: 26 | 27 | ## Steps To reproduce the bug 28 | 29 | Explain how the maintainer can reproduce the bug. 30 | 31 | 1. 32 | 2. 33 | 3. 34 | 35 | ## Expected behaviour 36 | 37 | Describe what you expect to happen. 38 | 39 | ## Actual behaviour 40 | 41 | Describe what actually happens. 42 | 43 | ## Errors 44 | 45 | Paste any errors that you see, including logs, errors, or screenshots. 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discord 4 | url: https://discord.iota.org/ 5 | about: Please ask and answer questions here. 6 | - name: Security vulnerabilities 7 | url: security@iota.org 8 | about: Please report security vulnerabilities here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Request a feature for the Client Load Balancer 3 | about: Request a feature 4 | --- 5 | 6 | ## Description 7 | 8 | Briefly describe the feature that you are requesting. 9 | 10 | ## Motivation 11 | 12 | Explain why this feature is needed. 13 | 14 | ## Requirements 15 | 16 | Write a list of what you want this feature to do. 17 | 18 | 1. 19 | 2. 20 | 3. 21 | 22 | ## Open questions (optional) 23 | 24 | Use this section to ask any questions that are related to the feature. 25 | 26 | ## Are you planning to do it yourself in a pull request? 27 | 28 | Yes/No. 29 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 |

Responsible disclosure policy

2 | 3 | At the IOTA Foundation, we consider the security of our systems a top priority. But no matter how much effort we put into system security, there can still be vulnerabilities present. If you've discovered a vulnerability, please follow the guidelines below to report it to our security team: 4 | 7 | Please follow these rules when testing/reporting vulnerabilities: 8 | 15 | What we promise: 16 | 22 | We sincerely appreciate the efforts of security researchers in keeping our community safe. 23 | 24 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Community resources 2 | 3 | If you have a general or technical question, you can use one of the following resources instead of submitting an issue: 4 | 5 | - [**Developer documentation:**](https://docs.iota.org/) For official information about developing with IOTA technology 6 | - [**Discord:**](https://discord.iota.org/) For real-time chats with the developers and community members 7 | - [**IOTA cafe:**](https://iota.cafe/) For technical discussions with the Research and Development Department at the IOTA Foundation 8 | - [**StackExchange:**](https://iota.stackexchange.com/) For technical and troubleshooting questions -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description of change 2 | 3 | Please write a summary of your changes and why you made them. Be sure to reference any related issues by adding `fixes # (issue)`. 4 | 5 | ## Type of change 6 | 7 | Choose a type of change, and delete any options that are not relevant. 8 | 9 | - Bug fix (a non-breaking change which fixes an issue) 10 | - Enhancement (a non-breaking change which adds functionality) 11 | - Breaking change (fix or feature that would cause existing functionality to not work as expected) 12 | - Documentation Fix 13 | 14 | ## How the change has been tested 15 | 16 | Describe the tests that you ran to verify your changes. 17 | 18 | Make sure to provide instructions for the maintainer as well as any relevant configurations. 19 | 20 | ## Change checklist 21 | 22 | Add an `x` to the boxes that are relevant to your changes, and delete any items that are not. 23 | 24 | - [] I have followed the contribution guidelines for this project 25 | - [] I have performed a self-review of my own code 26 | - [] I have commented my code, particularly in hard-to-understand areas 27 | - [] I have made corresponding changes to the documentation 28 | - [] I have added tests that prove my fix is effective or that my feature works 29 | - [] New and existing unit tests pass locally with my changes 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .history 3 | node_modules 4 | /coverage 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | .env 9 | build 10 | 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.0.1 4 | 5 | * RandomWalkStrategy retains order when calling next from exhausted list after error 6 | 7 | ## v1.0.0 8 | 9 | * Initial public release 10 | 11 | ## v0.1.4 12 | 13 | * Pass attachToTangle into composeAPICore 14 | 15 | ## v0.1.3 16 | 17 | * Fix - mam wrapping improved for fetch 18 | * Fix - mam.subscribe updated to match @iota/mam signature 19 | 20 | ## v0.1.2 21 | 22 | * Fix - wrapMethodCallbackOrAsync updated to use methodsName for getTrytes check 23 | 24 | ## v0.1.1 25 | 26 | * Fix - wrapMethodCallbackOrAsync now uses param for wrapping mwm and depth instead of relying on method name as minification breaks other method 27 | 28 | ## v0.1.0 29 | 30 | * Initial Release 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 IOTA Stiftung 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

A load balancer for connecting to IOTA nodes

2 | 3 |

4 | Discord 5 | StackExchange 6 | MIT license 7 |

8 | 9 |

10 | About ◈ 11 | Prerequisites ◈ 12 | Installation ◈ 13 | Getting started ◈ 14 | API reference ◈ 15 | Supporting the project ◈ 16 | Joining the discussion 17 |

18 | 19 | --- 20 | 21 | ## About 22 | 23 | The Client Load Balancer is a utility package for sending commands to a list of nodes instead of just a single provider. 24 | 25 | This package is compatible with the [IOTA JavaScript client library](https://github.com/iotaledger/iota.js) and [mam.client.js](https://github.com/iotaledger/mam.client.js). 26 | 27 | There is a separate branch [https://github.com/iotaledger/client-load-balancer/tree/no-mam](https://github.com/iotaledger/client-load-balancer/tree/no-mam) which contains a version of this library for use with [mam.js](https://github.com/iotaledger/mam.js). This branch removes all the MAM specific methods and references as `mam.js` utilises the regular `composeAPI` for its communications. 28 | 29 | Features include: 30 | 31 | * Snapshot aware option which tries alternate nodes if getTrytes returns all 9s for result. 32 | 33 | * Node list walk strategies: 34 | 35 | * Linear - Walks the list of nodes sequentially 36 | 37 | * Random - Randomly picks a node from the list 38 | 39 | * Custom - Create your own walk strategy 40 | 41 | * Success mode for when an operation completes successfully: 42 | 43 | * Keep - Keep using the same node until it fails. 44 | 45 | * Next - Always step to the next node. 46 | 47 | * Fail mode for when a node does not respond or returns error: 48 | 49 | * Single - Fails the whole call after a single failure. 50 | 51 | * All - Tries all the nodes until one succeeds. 52 | 53 | * Timeout for non-responsive nodes. 54 | 55 | * Blacklist limit, nodes failing a number of times are longer used until all nodes exhausted. 56 | 57 | * Settings available on a per node or global level: 58 | 59 | * Minimum weight magnitude 60 | * Depth 61 | * AttachToTangle 62 | * TimeoutMs 63 | 64 | This is beta software, so there may be performance and stability issues. 65 | Please report any issues in our [issue tracker](https://github.com/iotaledger/client-load-balancer/issues/new). 66 | 67 | ## Prerequisites 68 | 69 | To use the Client Load Balancer in your own applications, you need [Node.js](https://nodejs.org/en/download/) installed on your device. 70 | 71 | To check if you have Node.js installed, run the following command: 72 | 73 | ```bash 74 | node -v 75 | ``` 76 | 77 | If Node.js is installed, you should see the version that's installed. 78 | 79 | ## Installation 80 | 81 | To install this package, use one of the following commands: 82 | 83 | 84 | - `npm install iotaledger/client-load-balancer` 85 | 86 | 87 | - `yarn add iotaledger/client-load-balancer` 88 | 89 | ## Getting started 90 | 91 | To jump in now, see the following code sample: 92 | 93 | ```js 94 | const { composeAPI, FailMode, RandomWalkStrategy, SuccessMode } = require('@iota/client-load-balancer'); 95 | 96 | (async function () { 97 | try { 98 | const api = composeAPI({ 99 | nodeWalkStrategy: new RandomWalkStrategy( 100 | [ 101 | { 102 | "provider": "https://altnodes.devnet.iota.org:443", 103 | "depth": 3, 104 | "mwm": 9 105 | }, 106 | { 107 | "provider": "https://nodes.devnet.iota.org:443", 108 | "depth": 3, 109 | "mwm": 9 110 | } 111 | ] 112 | ), 113 | successMode: SuccessMode.next, 114 | failMode: FailMode.all, 115 | timeoutMs: 5000, 116 | tryNodeCallback: (node) => { 117 | console.log(`Trying node ${node.provider}`); 118 | }, 119 | failNodeCallback: (node, err) => { 120 | console.log(`Failed node ${node.provider}, ${err.message}`); 121 | } 122 | }); 123 | 124 | const res = await api.getNodeInfo(); 125 | console.log("App Name:", res.appName); 126 | console.log("App Version:", res.appVersion); 127 | } catch (err) { 128 | console.error(`Error: ${err.message}`); 129 | } 130 | })(); 131 | ``` 132 | 133 | Will output: 134 | 135 | ```shell 136 | Trying node https://nodes.devnet.iota.org:443 137 | App Name: IRI Testnet 138 | App Version: 1.5.6-RELEASE 139 | ``` 140 | 141 | ## MWM and Depth 142 | 143 | The `mwm` and `depth` parameters can be set at multiple levels within the configuration. You can specify them at a node level, but they are optional. 144 | 145 | ```js 146 | { 147 | provider: "https://altnodes.devnet.iota.org:443", 148 | depth?: 3, 149 | mwm?: 9 150 | } 151 | ``` 152 | If you don't specify them for each node you can set them at the top level load balancer settings. 153 | 154 | ```js 155 | const api = composeAPI({ 156 | nodeWalkStrategy: ..., 157 | depth?: 3, 158 | mwm?: 9 159 | }); 160 | ``` 161 | 162 | Or you can just provide them when calling the regular methods that require them. 163 | 164 | ```js 165 | const iota = composeAPI(loadBalancerSettings); 166 | const trytes = await iota.prepareTransfers(seed, transfers, options); 167 | await iota.sendTrytes(trytes, 3, 9); 168 | ``` 169 | 170 | If you want methods like `sendTrytes` to use the values from the configuration just pass `undefined` instead, in `JavaScript` you can skip the parameters altogether but `TypeScript` will require some values, hence `undefined`. 171 | 172 | In this case of `undefined` parameters the code will first look at the configuration for the node that it is currently using, if that does not provide values it uses the load balancer settings. If they are not provided the defaults are `depth=3` and `mwm=9` 173 | 174 | ```js 175 | await iota.sendTrytes(trytes, undefined, undefined); 176 | ``` 177 | 178 | ## API Reference 179 | 180 | See the [JavaScript API reference](./docs/README.md). 181 | 182 | ## Supporting the project 183 | 184 | If you want to contribute, consider submitting a [bug report](https://github.com/iotaledger/client-load-balancer/issues/new), [feature request](https://github.com/iotaledger/client-load-balancer/issues/new) or a [pull request](https://github.com/iotaledger/client-load-balancer/pulls/). 185 | 186 | See our [contributing guidelines](.github/CONTRIBUTING.md) for more information. 187 | 188 | ## Joining the discussion 189 | 190 | If you want to get involved in the community, need help with getting set up, have any issues or just want to discuss IOTA, Distributed Registry Technology (DRT), and IoT with other people, feel free to join our [Discord](https://discord.iota.org/). 191 | -------------------------------------------------------------------------------- /dist/iota-client-load-balancer.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@iota/core'), require('@iota/validators'), require('@iota/mam'), require('bluebird')) : 3 | typeof define === 'function' && define.amd ? define(['exports', '@iota/core', '@iota/validators', '@iota/mam', 'bluebird'], factory) : 4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.IotaClientLoadBalancer = {}, global.core, global.validators, global.MamCore, global.Bluebird)); 5 | }(this, (function (exports, core, validators, MamCore, Bluebird) { 'use strict'; 6 | 7 | function _interopNamespace(e) { 8 | if (e && e.__esModule) return e; 9 | var n = Object.create(null); 10 | if (e) { 11 | Object.keys(e).forEach(function (k) { 12 | if (k !== 'default') { 13 | var d = Object.getOwnPropertyDescriptor(e, k); 14 | Object.defineProperty(n, k, d.get ? d : { 15 | enumerable: true, 16 | get: function () { 17 | return e[k]; 18 | } 19 | }); 20 | } 21 | }); 22 | } 23 | n['default'] = e; 24 | return Object.freeze(n); 25 | } 26 | 27 | var Bluebird__namespace = /*#__PURE__*/_interopNamespace(Bluebird); 28 | 29 | /*! ***************************************************************************** 30 | Copyright (c) Microsoft Corporation. All rights reserved. 31 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 32 | this file except in compliance with the License. You may obtain a copy of the 33 | License at http://www.apache.org/licenses/LICENSE-2.0 34 | 35 | THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 36 | KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 37 | WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 38 | MERCHANTABLITY OR NON-INFRINGEMENT. 39 | 40 | See the Apache Version 2.0 License for specific language governing permissions 41 | and limitations under the License. 42 | ***************************************************************************** */ 43 | /* global Reflect, Promise */ 44 | 45 | var extendStatics = function(d, b) { 46 | extendStatics = Object.setPrototypeOf || 47 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 48 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 49 | return extendStatics(d, b); 50 | }; 51 | 52 | function __extends(d, b) { 53 | extendStatics(d, b); 54 | function __() { this.constructor = d; } 55 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 56 | } 57 | 58 | function __awaiter(thisArg, _arguments, P, generator) { 59 | return new (P || (P = Promise))(function (resolve, reject) { 60 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 61 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 62 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 63 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 64 | }); 65 | } 66 | 67 | function __generator(thisArg, body) { 68 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 69 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 70 | function verb(n) { return function (v) { return step([n, v]); }; } 71 | function step(op) { 72 | if (f) throw new TypeError("Generator is already executing."); 73 | while (_) try { 74 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 75 | if (y = 0, t) op = [op[0] & 2, t.value]; 76 | switch (op[0]) { 77 | case 0: case 1: t = op; break; 78 | case 4: _.label++; return { value: op[1], done: false }; 79 | case 5: _.label++; y = op[1]; op = [0]; continue; 80 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 81 | default: 82 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 83 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 84 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 85 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 86 | if (t[2]) _.ops.pop(); 87 | _.trys.pop(); continue; 88 | } 89 | op = body.call(thisArg, _); 90 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 91 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 92 | } 93 | } 94 | 95 | /** 96 | * Fail modes for the load balancer. 97 | */ 98 | (function (FailMode) { 99 | /** 100 | * Try single node only, failure throws exception. 101 | */ 102 | FailMode["single"] = "single"; 103 | /** 104 | * Try all nodes until one succeeds, on all failing throws combined exception. 105 | */ 106 | FailMode["all"] = "all"; 107 | })(exports.FailMode || (exports.FailMode = {})); 108 | 109 | /** 110 | * Success modes for the load balancer. 111 | */ 112 | (function (SuccessMode) { 113 | /** 114 | * Keep the node if it was successful. 115 | */ 116 | SuccessMode["keep"] = "keep"; 117 | /** 118 | * Move to the next node even if it was successful. 119 | */ 120 | SuccessMode["next"] = "next"; 121 | })(exports.SuccessMode || (exports.SuccessMode = {})); 122 | 123 | /** 124 | * Create a new instance of the API. 125 | * @param settings The load balancer settings. 126 | * @param updateProvider Update the provider in the calling context. 127 | * @param methodPromise The method to call. 128 | * @param validateResult Let the caller validate the result. 129 | * @returns The api. 130 | * @private 131 | */ 132 | function loadBalancer(settings, updateProvider, methodPromise, validateResult) { 133 | return __awaiter(this, void 0, Promise, function () { 134 | var res, tryNextNode, totalNodes, triedCount, errorList, node, timeout, validMessage, err_1; 135 | return __generator(this, function (_a) { 136 | switch (_a.label) { 137 | case 0: 138 | tryNextNode = false; 139 | totalNodes = settings.nodeWalkStrategy.totalUsable(); 140 | triedCount = 0; 141 | errorList = []; 142 | _a.label = 1; 143 | case 1: 144 | node = settings.nodeWalkStrategy.current(); 145 | updateProvider(node); 146 | if (settings.tryNodeCallback) { 147 | settings.tryNodeCallback(node); 148 | } 149 | _a.label = 2; 150 | case 2: 151 | _a.trys.push([2, 7, , 8]); 152 | timeout = node.timeoutMs || settings.timeoutMs; 153 | if (!timeout) return [3 /*break*/, 4]; 154 | return [4 /*yield*/, methodPromise(node).timeout(timeout, node.provider + " the request timed out")]; 155 | case 3: 156 | res = _a.sent(); 157 | return [3 /*break*/, 6]; 158 | case 4: return [4 /*yield*/, methodPromise(node)]; 159 | case 5: 160 | res = _a.sent(); 161 | _a.label = 6; 162 | case 6: 163 | if (validateResult) { 164 | validMessage = validateResult(res); 165 | if (validMessage) { 166 | throw new Error(validMessage); 167 | } 168 | } 169 | tryNextNode = false; 170 | if (settings.successMode === exports.SuccessMode.next) { 171 | // Walk to the next node in the strategy 172 | settings.nodeWalkStrategy.next(false); 173 | } 174 | return [3 /*break*/, 8]; 175 | case 7: 176 | err_1 = _a.sent(); 177 | settings.nodeWalkStrategy.blacklist(); 178 | if (settings.failNodeCallback) { 179 | settings.failNodeCallback(node, err_1); 180 | } 181 | if (settings.failMode === exports.FailMode.single) { 182 | // Single fail mode so just throw the error 183 | throw err_1; 184 | } 185 | else if (settings.failMode === exports.FailMode.all) { 186 | // Fail mode is try all until one succeeds 187 | errorList.push(err_1.message ? err_1 : { message: err_1 }); 188 | // Try to use the next node if the current one errored 189 | triedCount++; 190 | // But only if we have not already tried all the nodes 191 | tryNextNode = triedCount < totalNodes; 192 | if (!tryNextNode) { 193 | // No more nodes to try so throw the combined exceptions 194 | throw new Error("All nodes failed\n " + errorList.map(function (e) { return e.message; }).join("\n ")); 195 | } 196 | // Walk to the next node in the strategy 197 | settings.nodeWalkStrategy.next(true); 198 | } 199 | return [3 /*break*/, 8]; 200 | case 8: 201 | if (tryNextNode) return [3 /*break*/, 1]; 202 | _a.label = 9; 203 | case 9: return [2 /*return*/, res]; 204 | } 205 | }); 206 | }); 207 | } 208 | /** 209 | * Wrap a method and handle either callback or async result. 210 | * @param settings The load balancer settings. 211 | * @param api The composed api. 212 | * @param method The method to wrap. 213 | * @param methodName The name of the method. 214 | * @returns The wrapped method. 215 | * @private 216 | */ 217 | function wrapMethodCallbackOrAsync(settings, api, method, methodName) { 218 | var _this = this; 219 | return function () { 220 | var p = []; 221 | for (var _i = 0; _i < arguments.length; _i++) { 222 | p[_i] = arguments[_i]; 223 | } 224 | return __awaiter(_this, void 0, void 0, function () { 225 | var originalCallbackParam; 226 | return __generator(this, function (_a) { 227 | originalCallbackParam = p[method.length - 1]; 228 | // If the caller is using the callback parameter remove it and use the promise 229 | // method then restore on method completion. 230 | if (originalCallbackParam) { 231 | p[method.length - 1] = undefined; 232 | } 233 | return [2 /*return*/, loadBalancer(settings, function (node) { return api.setSettings({ 234 | provider: node.provider, 235 | attachToTangle: node.attachToTangle || settings.attachToTangle, 236 | user: node.user || settings.user, 237 | password: node.password || settings.password 238 | }); }, function (node) { 239 | // Apply the default depth and mwm to methods that use them if they have not been supplied 240 | if (methodName === "promoteTransaction" || 241 | methodName === "replayBundle" || 242 | methodName === "sendTrytes") { 243 | p[1] = p[1] || node.depth || settings.depth; 244 | p[2] = p[2] || node.mwm || settings.mwm; 245 | } 246 | return method.apply(void 0, p); 247 | }, function (res) { 248 | if (settings.snapshotAware && methodName === "getTrytes") { 249 | var trytes = res; 250 | if (trytes) { 251 | for (var i = 0; i < trytes.length; i++) { 252 | if (validators.isEmpty(trytes[i])) { 253 | return "Data has been removed by snapshot"; 254 | } 255 | } 256 | } 257 | } 258 | return ""; 259 | }) 260 | .then(function (res) { 261 | if (originalCallbackParam) { 262 | originalCallbackParam(null, res); 263 | return undefined; 264 | } 265 | else { 266 | return res; 267 | } 268 | }).catch(function (err) { 269 | if (originalCallbackParam) { 270 | originalCallbackParam(err); 271 | } 272 | else { 273 | throw err; 274 | } 275 | })]; 276 | }); 277 | }); 278 | }; 279 | } 280 | 281 | /** 282 | * Create a new instance of the API wrapped with load balancing support. 283 | * @param settings The load balancer settings. 284 | * @returns The api. 285 | */ 286 | function composeAPI(settings) { 287 | if (!settings) { 288 | throw new Error("You must provider settings"); 289 | } 290 | if (!settings.nodeWalkStrategy) { 291 | throw new Error("The nodeWalkStrategy field must be provided"); 292 | } 293 | settings.mwm = settings.mwm || 9; 294 | settings.depth = settings.depth || 3; 295 | settings.successMode = settings.successMode || exports.SuccessMode.next; 296 | settings.failMode = settings.failMode || exports.FailMode.all; 297 | var api = core.composeAPI({ attachToTangle: settings.attachToTangle }); 298 | // Wrap all the web methods with additional handling 299 | api.addNeighbors = wrapMethodCallbackOrAsync(settings, api, api.addNeighbors, "addNeighbors"); 300 | api.broadcastTransactions = wrapMethodCallbackOrAsync(settings, api, api.broadcastTransactions, "broadcastTransactions"); 301 | api.checkConsistency = wrapMethodCallbackOrAsync(settings, api, api.checkConsistency, "checkConsistency"); 302 | api.findTransactions = wrapMethodCallbackOrAsync(settings, api, api.findTransactions, "findTransactions"); 303 | api.getBalances = wrapMethodCallbackOrAsync(settings, api, api.getBalances, "getBalances"); 304 | api.getInclusionStates = wrapMethodCallbackOrAsync(settings, api, api.getInclusionStates, "getInclusionStates"); 305 | api.getNeighbors = wrapMethodCallbackOrAsync(settings, api, api.getNeighbors, "getNeighbors"); 306 | api.getNodeInfo = wrapMethodCallbackOrAsync(settings, api, api.getNodeInfo, "getNodeInfo"); 307 | api.getTransactionsToApprove = wrapMethodCallbackOrAsync(settings, api, api.getTransactionsToApprove, "getTransactionsToApprove"); 308 | api.getTrytes = wrapMethodCallbackOrAsync(settings, api, api.getTrytes, "getTrytes"); 309 | api.interruptAttachingToTangle = wrapMethodCallbackOrAsync(settings, api, api.interruptAttachingToTangle, "interruptAttachingToTangle"); 310 | api.removeNeighbors = wrapMethodCallbackOrAsync(settings, api, api.removeNeighbors, "removeNeighbors"); 311 | api.storeTransactions = wrapMethodCallbackOrAsync(settings, api, api.storeTransactions, "storeTransactions"); 312 | api.broadcastBundle = wrapMethodCallbackOrAsync(settings, api, api.broadcastBundle, "broadcastBundle"); 313 | api.getAccountData = wrapMethodCallbackOrAsync(settings, api, api.getAccountData, "getAccountData"); 314 | api.getBundle = wrapMethodCallbackOrAsync(settings, api, api.getBundle, "getBundle"); 315 | api.getBundlesFromAddresses = wrapMethodCallbackOrAsync(settings, api, api.getBundlesFromAddresses, "getBundlesFromAddresses"); 316 | api.getNewAddress = wrapMethodCallbackOrAsync(settings, api, api.getNewAddress, "getNewAddress"); 317 | api.getTransactionObjects = wrapMethodCallbackOrAsync(settings, api, api.getTransactionObjects, "getTransactionObjects"); 318 | api.findTransactionObjects = wrapMethodCallbackOrAsync(settings, api, api.findTransactionObjects, "findTransactionObjects"); 319 | api.getInputs = wrapMethodCallbackOrAsync(settings, api, api.getInputs, "getInputs"); 320 | api.getTransfers = wrapMethodCallbackOrAsync(settings, api, api.getTransfers, "getTransfers"); 321 | api.isPromotable = wrapMethodCallbackOrAsync(settings, api, api.isPromotable, "isPromotable"); 322 | api.isReattachable = wrapMethodCallbackOrAsync(settings, api, api.isReattachable, "isReattachable"); 323 | api.prepareTransfers = wrapMethodCallbackOrAsync(settings, api, api.prepareTransfers, "prepareTransfers"); 324 | api.promoteTransaction = wrapMethodCallbackOrAsync(settings, api, api.promoteTransaction, "promoteTransaction"); 325 | api.replayBundle = wrapMethodCallbackOrAsync(settings, api, api.replayBundle, "replayBundle"); 326 | api.sendTrytes = wrapMethodCallbackOrAsync(settings, api, api.sendTrytes, "sendTrytes"); 327 | api.storeAndBroadcast = wrapMethodCallbackOrAsync(settings, api, api.storeAndBroadcast, "storeAndBroadcast"); 328 | api.traverseBundle = wrapMethodCallbackOrAsync(settings, api, api.traverseBundle, "traverseBundle"); 329 | return api; 330 | } 331 | 332 | /** 333 | * Wrapper for Mam with load balancing 334 | */ 335 | var Mam = /** @class */ (function () { 336 | function Mam() { 337 | } 338 | /** 339 | * Initialisation function which returns a state object 340 | * @param settings Settings for the load balancer. 341 | * @param seed The seed to initialise with. 342 | * @param security The security level, defaults to 2. 343 | * @returns The mam state. 344 | */ 345 | Mam.init = function (settings, seed, security) { 346 | if (security === void 0) { security = 2; } 347 | if (!settings) { 348 | throw new Error("You must provider settings"); 349 | } 350 | if (!settings.nodeWalkStrategy) { 351 | throw new Error("The nodeWalkStrategy field must be provided"); 352 | } 353 | settings.mwm = settings.mwm || 9; 354 | settings.depth = settings.depth || 3; 355 | settings.successMode = settings.successMode || exports.SuccessMode.next; 356 | settings.failMode = settings.failMode || exports.FailMode.all; 357 | Mam.loadBalancerSettings = settings; 358 | return MamCore.init({ provider: "" }, seed, security); 359 | }; 360 | /** 361 | * Change the mode for the mam state. 362 | * @param state The current mam state. 363 | * @param mode [public/private/restricted]. 364 | * @param sidekey, required for restricted mode. 365 | * @returns Updated state object to be used with future actions. 366 | */ 367 | Mam.changeMode = function (state, mode, sidekey) { 368 | return MamCore.changeMode(state, mode, sidekey); 369 | }; 370 | /** 371 | * Get the root from the mam state. 372 | * @param state The mam state. 373 | * @returns The root. 374 | */ 375 | Mam.getRoot = function (state) { 376 | return MamCore.getRoot(state); 377 | }; 378 | /** 379 | * Add a subscription to your state object 380 | * @param state The state object to add the subscription to. 381 | * @param channelRoot The root of the channel to subscribe to. 382 | * @param channelMode Can be `public`, `private` or `restricted`. 383 | * @param channelKey Optional, the key of the channel to subscribe to. 384 | * @returns Updated state object to be used with future actions. 385 | */ 386 | Mam.subscribe = function (state, channelRoot, channelMode, channelKey) { 387 | return MamCore.subscribe(state, channelRoot, channelMode, channelKey); 388 | }; 389 | /** 390 | * Listen for new message on the channel. 391 | * @param channel The channel to listen on. 392 | * @param callback The callback to receive any messages, 393 | */ 394 | Mam.listen = function (channel, callback) { 395 | return MamCore.listen(channel, callback); 396 | }; 397 | /** 398 | * Creates a MAM message payload from a state object. 399 | * @param state The current mam state. 400 | * @param message Tryte encoded string. 401 | * @returns An object containing the payload and updated state. 402 | */ 403 | Mam.create = function (state, message) { 404 | return MamCore.create(state, message); 405 | }; 406 | /** 407 | * Decode a message. 408 | * @param payload The payload of the message. 409 | * @param sideKey The sideKey used in the message. 410 | * @param root The root used for the message. 411 | * @returns The decoded payload. 412 | */ 413 | Mam.decode = function (payload, sideKey, root) { 414 | return MamCore.decode(payload, sideKey, root); 415 | }; 416 | /** 417 | * Fetch the messages asynchronously. 418 | * @param root The root key to use. 419 | * @param mode The mode of the channel. 420 | * @param sideKey The sideKey used in the messages, only required for restricted. 421 | * @param callback Optional callback to receive each payload. 422 | * @param limit Limit the number of messages that are fetched. 423 | * @returns The nextRoot and the messages if no callback was supplied, or an Error. 424 | */ 425 | Mam.fetch = function (root, mode, sideKey, callback, limit) { 426 | return __awaiter(this, void 0, Promise, function () { 427 | return __generator(this, function (_a) { 428 | return [2 /*return*/, loadBalancer(Mam.loadBalancerSettings, function (node) { 429 | MamCore.setIOTA(node.provider); 430 | MamCore.setAttachToTangle(node.attachToTangle || Mam.loadBalancerSettings.attachToTangle); 431 | }, function () { return new Bluebird__namespace(function (resolve, reject) { 432 | MamCore.fetch(root, mode, sideKey, callback, limit) 433 | .then(resolve) 434 | .catch(reject); 435 | }); })]; 436 | }); 437 | }); 438 | }; 439 | /** 440 | * Fetch a single message asynchronously. 441 | * @param root The root key to use. 442 | * @param mode The mode of the channel. 443 | * @param sideKey The sideKey used in the messages. 444 | * @returns The nextRoot and the payload, or an Error. 445 | */ 446 | Mam.fetchSingle = function (root, mode, sideKey) { 447 | return __awaiter(this, void 0, Promise, function () { 448 | var response; 449 | return __generator(this, function (_a) { 450 | switch (_a.label) { 451 | case 0: return [4 /*yield*/, Mam.fetch(root, mode, sideKey, undefined, 1)]; 452 | case 1: 453 | response = _a.sent(); 454 | return [2 /*return*/, response instanceof Error ? response : { 455 | payload: response.messages && response.messages.length === 1 ? response.messages[0] : undefined, 456 | nextRoot: response.nextRoot 457 | }]; 458 | } 459 | }); 460 | }); 461 | }; 462 | /** 463 | * Attach the mam trytes to the tangle. 464 | * @param trytes The trytes to attach. 465 | * @param root The root to attach them to. 466 | * @param depth The depth to attach them with, defaults to 3. 467 | * @param mwm The minimum weight magnitude to attach with, defaults to 9 for devnet, 14 required for mainnet. 468 | * @param tag Trytes to tag the message with. 469 | * @returns The transaction objects. 470 | */ 471 | Mam.attach = function (trytes, root, depth, mwm, tag) { 472 | return __awaiter(this, void 0, Promise, function () { 473 | var _a, prepareTransfers, sendTrytes, response; 474 | return __generator(this, function (_b) { 475 | switch (_b.label) { 476 | case 0: 477 | _a = composeAPI(Mam.loadBalancerSettings), prepareTransfers = _a.prepareTransfers, sendTrytes = _a.sendTrytes; 478 | return [4 /*yield*/, prepareTransfers("9".repeat(81), [ 479 | { 480 | address: root, 481 | value: 0, 482 | message: trytes, 483 | tag: tag 484 | } 485 | ])]; 486 | case 1: 487 | response = _b.sent(); 488 | return [2 /*return*/, sendTrytes(response, depth || 0, mwm || 0)]; 489 | } 490 | }); 491 | }); 492 | }; 493 | return Mam; 494 | }()); 495 | 496 | /** 497 | * Settings to use for the load balancer. 498 | */ 499 | var LoadBalancerSettings = /** @class */ (function () { 500 | function LoadBalancerSettings() { 501 | } 502 | return LoadBalancerSettings; 503 | }()); 504 | 505 | /** 506 | * The configuration for a single node. 507 | */ 508 | var NodeConfiguration = /** @class */ (function () { 509 | function NodeConfiguration() { 510 | } 511 | return NodeConfiguration; 512 | }()); 513 | 514 | /** 515 | * Common features for the node strategies. 516 | * @private 517 | */ 518 | var BaseWalkStrategy = /** @class */ (function () { 519 | /** 520 | * Create a new instance of BaseWalkStrategy. 521 | * @param nodes The nodes to iterate through. 522 | * @param blacklistLimit The number of failures before a node is blacklisted. 523 | */ 524 | function BaseWalkStrategy(nodes, blacklistLimit) { 525 | if (!nodes || nodes.length === 0) { 526 | throw new Error("You must supply at least one node to the strategy"); 527 | } 528 | this._allNodes = nodes; 529 | this._usableNodes = nodes.slice(); 530 | this._blacklistLimit = blacklistLimit; 531 | this._blacklistNodes = {}; 532 | } 533 | /** 534 | * The total number of nodes configured for the strategy. 535 | * @returns The total number of nodes. 536 | */ 537 | BaseWalkStrategy.prototype.totalUsable = function () { 538 | return this._usableNodes.length; 539 | }; 540 | /** 541 | * Blacklist the current node, so it doesn't get used again once limit is reached. 542 | */ 543 | BaseWalkStrategy.prototype.blacklist = function () { 544 | if (this._blacklistLimit) { 545 | var current = this.current(); 546 | if (current) { 547 | if (!this._blacklistNodes[current.provider]) { 548 | this._blacklistNodes[current.provider] = 1; 549 | } 550 | else { 551 | this._blacklistNodes[current.provider]++; 552 | } 553 | if (this._blacklistNodes[current.provider] >= this._blacklistLimit) { 554 | var idx = this._usableNodes.indexOf(current); 555 | if (idx >= 0) { 556 | this._usableNodes.splice(idx, 1); 557 | } 558 | } 559 | // If there are no usable nodes left then reset the blacklists 560 | if (this._usableNodes.length === 0) { 561 | this._blacklistNodes = {}; 562 | this._usableNodes = this._allNodes.slice(); 563 | } 564 | } 565 | } 566 | }; 567 | /** 568 | * Get the list of nodes that have not been blacklisted. 569 | * @returns The non blacklisted nodes. 570 | */ 571 | BaseWalkStrategy.prototype.getUsableNodes = function () { 572 | return this._usableNodes; 573 | }; 574 | return BaseWalkStrategy; 575 | }()); 576 | 577 | /** 578 | * Node choice strategy which just iterates through the list of nodes. 579 | */ 580 | var LinearWalkStrategy = /** @class */ (function (_super) { 581 | __extends(LinearWalkStrategy, _super); 582 | /** 583 | * Create a new instance of LinearWalkStrategy. 584 | * @param nodes The nodes to randomly pick from. 585 | * @param blacklistLimit The number of failures before a node is blacklisted. 586 | */ 587 | function LinearWalkStrategy(nodes, blacklistLimit) { 588 | var _this = _super.call(this, nodes, blacklistLimit) || this; 589 | _this._currentIndex = 0; 590 | return _this; 591 | } 592 | /** 593 | * Get the current node from the strategy. 594 | * @returns A node configuration from the strategy. 595 | */ 596 | LinearWalkStrategy.prototype.current = function () { 597 | return this.getUsableNodes()[this._currentIndex]; 598 | }; 599 | /** 600 | * Move to the next node in the strategy. 601 | * @param retainOrder Retain the ordering if resetting the list. 602 | */ 603 | LinearWalkStrategy.prototype.next = function (retainOrder) { 604 | this._currentIndex = (this._currentIndex + 1) % this.getUsableNodes().length; 605 | }; 606 | return LinearWalkStrategy; 607 | }(BaseWalkStrategy)); 608 | 609 | /** 610 | * Node choice strategy which randomly picks from the list of nodes. 611 | */ 612 | var RandomWalkStrategy = /** @class */ (function (_super) { 613 | __extends(RandomWalkStrategy, _super); 614 | /** 615 | * Create a new instance of RandomWalkStategy. 616 | * @param nodes The nodes to randomly pick from. 617 | * @param blacklistLimit The number of failures before a node is blacklisted. 618 | */ 619 | function RandomWalkStrategy(nodes, blacklistLimit) { 620 | var _this = _super.call(this, nodes, blacklistLimit) || this; 621 | _this._remainingNodes = []; 622 | _this._randomNodes = []; 623 | _this.populateRemaining(); 624 | return _this; 625 | } 626 | /** 627 | * Get the current node from the strategy. 628 | * @returns A node configuration from the strategy. 629 | */ 630 | RandomWalkStrategy.prototype.current = function () { 631 | return this._remainingNodes[0]; 632 | }; 633 | /** 634 | * Move to the next node in the strategy. 635 | * @param retainOrder Retain the ordering if resetting the list. 636 | */ 637 | RandomWalkStrategy.prototype.next = function (retainOrder) { 638 | this._remainingNodes.shift(); 639 | if (this._remainingNodes.length === 0) { 640 | if (retainOrder) { 641 | this._remainingNodes = this._randomNodes.slice(); 642 | } 643 | else { 644 | this.populateRemaining(); 645 | } 646 | } 647 | }; 648 | /** 649 | * Populate the remaining array by randomizing the nodes. 650 | * @internal 651 | * @private 652 | */ 653 | RandomWalkStrategy.prototype.populateRemaining = function () { 654 | var _a; 655 | this._remainingNodes = _super.prototype.getUsableNodes.call(this).slice(); 656 | for (var i = this._remainingNodes.length - 1; i > 0; i--) { 657 | // tslint:disable-next-line:insecure-random 658 | var j = Math.floor(Math.random() * (i + 1)); 659 | _a = [this._remainingNodes[j], this._remainingNodes[i]], this._remainingNodes[i] = _a[0], this._remainingNodes[j] = _a[1]; 660 | } 661 | this._randomNodes = this._remainingNodes.slice(); 662 | }; 663 | return RandomWalkStrategy; 664 | }(BaseWalkStrategy)); 665 | 666 | exports.LinearWalkStrategy = LinearWalkStrategy; 667 | exports.LoadBalancerSettings = LoadBalancerSettings; 668 | exports.Mam = Mam; 669 | exports.NodeConfiguration = NodeConfiguration; 670 | exports.RandomWalkStrategy = RandomWalkStrategy; 671 | exports.composeAPI = composeAPI; 672 | 673 | Object.defineProperty(exports, '__esModule', { value: true }); 674 | 675 | }))); 676 | -------------------------------------------------------------------------------- /dist/iota-client-load-balancer.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@iota/core"),require("@iota/validators"),require("@iota/mam"),require("bluebird")):"function"==typeof define&&define.amd?define(["exports","@iota/core","@iota/validators","@iota/mam","bluebird"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).IotaClientLoadBalancer={},e.core,e.validators,e.MamCore,e.Bluebird)}(this,(function(e,t,n,r,o){"use strict";function s(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach((function(n){if("default"!==n){var r=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,r.get?r:{enumerable:!0,get:function(){return e[n]}})}})),t.default=e,Object.freeze(t)}var a,i,c=s(o),l=function(e,t){return(l=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])})(e,t)}; 2 | /*! ***************************************************************************** 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 5 | this file except in compliance with the License. You may obtain a copy of the 6 | License at http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 9 | KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 10 | WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 11 | MERCHANTABLITY OR NON-INFRINGEMENT. 12 | 13 | See the Apache Version 2.0 License for specific language governing permissions 14 | and limitations under the License. 15 | ***************************************************************************** */function u(e,t){function n(){this.constructor=e}l(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}function d(e,t,n,r){return new(n||(n=Promise))((function(o,s){function a(e){try{c(r.next(e))}catch(e){s(e)}}function i(e){try{c(r.throw(e))}catch(e){s(e)}}function c(e){e.done?o(e.value):new n((function(t){t(e.value)})).then(a,i)}c((r=r.apply(e,t||[])).next())}))}function f(e,t){var n,r,o,s,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:i(0),throw:i(1),return:i(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function i(s){return function(i){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return a.label++,{value:s[1],done:!1};case 5:a.label++,r=s[1],s=[0];continue;case 7:s=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]=this._blacklistLimit){var t=this._usableNodes.indexOf(e);t>=0&&this._usableNodes.splice(t,1)}0===this._usableNodes.length&&(this._blacklistNodes={},this._usableNodes=this._allNodes.slice())}}},e.prototype.getUsableNodes=function(){return this._usableNodes},e}(),v=function(e){function t(t,n){var r=e.call(this,t,n)||this;return r._currentIndex=0,r}return u(t,e),t.prototype.current=function(){return this.getUsableNodes()[this._currentIndex]},t.prototype.next=function(e){this._currentIndex=(this._currentIndex+1)%this.getUsableNodes().length},t}(T),N=function(e){function t(t,n){var r=e.call(this,t,n)||this;return r._remainingNodes=[],r._randomNodes=[],r.populateRemaining(),r}return u(t,e),t.prototype.current=function(){return this._remainingNodes[0]},t.prototype.next=function(e){this._remainingNodes.shift(),0===this._remainingNodes.length&&(e?this._remainingNodes=this._randomNodes.slice():this.populateRemaining())},t.prototype.populateRemaining=function(){var t;this._remainingNodes=e.prototype.getUsableNodes.call(this).slice();for(var n=this._remainingNodes.length-1;n>0;n--){var r=Math.floor(Math.random()*(n+1));t=[this._remainingNodes[r],this._remainingNodes[n]],this._remainingNodes[n]=t[0],this._remainingNodes[r]=t[1]}this._randomNodes=this._remainingNodes.slice()},t}(T);e.LinearWalkStrategy=v,e.LoadBalancerSettings=m,e.Mam=b,e.NodeConfiguration=y,e.RandomWalkStrategy=N,e.composeAPI=p,Object.defineProperty(e,"__esModule",{value:!0})})); -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | > **[@iota/client-load-balancer](README.md)** 2 | 3 | ### Index 4 | 5 | #### Enumerations 6 | 7 | * [FailMode](enums/failmode.md) 8 | * [SuccessMode](enums/successmode.md) 9 | 10 | #### Classes 11 | 12 | * [BaseWalkStrategy](classes/basewalkstrategy.md) 13 | * [LinearWalkStrategy](classes/linearwalkstrategy.md) 14 | * [LoadBalancerSettings](classes/loadbalancersettings.md) 15 | * [Mam](classes/mam.md) 16 | * [NodeConfiguration](classes/nodeconfiguration.md) 17 | * [RandomWalkStrategy](classes/randomwalkstrategy.md) 18 | 19 | #### Interfaces 20 | 21 | * [NodeWalkStrategy](interfaces/nodewalkstrategy.md) 22 | 23 | #### Functions 24 | 25 | * [composeAPI](README.md#composeapi) 26 | 27 | ## Functions 28 | 29 | ### composeAPI 30 | 31 | ▸ **composeAPI**(`settings`: [LoadBalancerSettings](classes/loadbalancersettings.md)): *`API`* 32 | 33 | Create a new instance of the API wrapped with load balancing support. 34 | 35 | **Parameters:** 36 | 37 | Name | Type | Description | 38 | ------ | ------ | ------ | 39 | `settings` | [LoadBalancerSettings](classes/loadbalancersettings.md) | The load balancer settings. | 40 | 41 | **Returns:** *`API`* 42 | 43 | The api. -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | ## Classes 2 | 3 |
4 |
Mam
5 |

Wrapper for Mam with load balancing

6 |
7 |
LoadBalancerSettings
8 |

Settings to use for the load balancer.

9 |
10 |
NodeConfiguration
11 |

The configuration for a single node.

12 |
13 |
LinearWalkStrategy
14 |

Node choice strategy which just iterates through the list of nodes.

15 |
16 |
RandomWalkStrategy
17 |

Node choice strategy which randomly picks from the list of nodes.

18 |
19 |
20 | 21 | ## Members 22 | 23 |
24 |
FailMode
25 |

Fail modes for the load balancer.

26 |
27 |
SuccessMode
28 |

Success modes for the load balancer.

29 |
30 |
31 | 32 | ## Functions 33 | 34 |
35 |
composeAPI(settings)
36 |

Create a new instance of the API wrapped with load balancing support.

37 |
38 |
39 | 40 | 41 | 42 | ## Mam 43 | Wrapper for Mam with load balancing 44 | 45 | **Kind**: global class 46 | 47 | * [Mam](#Mam) 48 | * [.init(settings, seed, security)](#Mam.init) ⇒ 49 | * [.changeMode(state, mode, sidekey,)](#Mam.changeMode) ⇒ 50 | * [.getRoot(state)](#Mam.getRoot) ⇒ 51 | * [.subscribe(state, channelRoot, channelMode, channelKey)](#Mam.subscribe) ⇒ 52 | * [.listen(channel, callback)](#Mam.listen) 53 | * [.create(state, message)](#Mam.create) ⇒ 54 | * [.decode(payload, sideKey, root)](#Mam.decode) ⇒ 55 | * [.fetch(root, mode, sideKey, callback, limit)](#Mam.fetch) ⇒ 56 | * [.fetchSingle(root, mode, sideKey)](#Mam.fetchSingle) ⇒ 57 | * [.attach(trytes, root, depth, mwm, tag)](#Mam.attach) ⇒ 58 | 59 | 60 | 61 | ### Mam.init(settings, seed, security) ⇒ 62 | Initialisation function which returns a state object 63 | 64 | **Kind**: static method of [Mam](#Mam) 65 | **Returns**: The mam state. 66 | 67 | | Param | Default | Description | 68 | | --- | --- | --- | 69 | | settings | | Settings for the load balancer. | 70 | | seed | | The seed to initialise with. | 71 | | security | 2 | The security level, defaults to 2. | 72 | 73 | 74 | 75 | ### Mam.changeMode(state, mode, sidekey,) ⇒ 76 | Change the mode for the mam state. 77 | 78 | **Kind**: static method of [Mam](#Mam) 79 | **Returns**: Updated state object to be used with future actions. 80 | 81 | | Param | Description | 82 | | --- | --- | 83 | | state | The current mam state. | 84 | | mode | [public/private/restricted]. | 85 | | sidekey, | required for restricted mode. | 86 | 87 | 88 | 89 | ### Mam.getRoot(state) ⇒ 90 | Get the root from the mam state. 91 | 92 | **Kind**: static method of [Mam](#Mam) 93 | **Returns**: The root. 94 | 95 | | Param | Description | 96 | | --- | --- | 97 | | state | The mam state. | 98 | 99 | 100 | 101 | ### Mam.subscribe(state, channelRoot, channelMode, channelKey) ⇒ 102 | Add a subscription to your state object 103 | 104 | **Kind**: static method of [Mam](#Mam) 105 | **Returns**: Updated state object to be used with future actions. 106 | 107 | | Param | Description | 108 | | --- | --- | 109 | | state | The state object to add the subscription to. | 110 | | channelRoot | The root of the channel to subscribe to. | 111 | | channelMode | Can be `public`, `private` or `restricted`. | 112 | | channelKey | Optional, the key of the channel to subscribe to. | 113 | 114 | 115 | 116 | ### Mam.listen(channel, callback) 117 | Listen for new message on the channel. 118 | 119 | **Kind**: static method of [Mam](#Mam) 120 | 121 | | Param | Description | 122 | | --- | --- | 123 | | channel | The channel to listen on. | 124 | | callback | The callback to receive any messages, | 125 | 126 | 127 | 128 | ### Mam.create(state, message) ⇒ 129 | Creates a MAM message payload from a state object. 130 | 131 | **Kind**: static method of [Mam](#Mam) 132 | **Returns**: An object containing the payload and updated state. 133 | 134 | | Param | Description | 135 | | --- | --- | 136 | | state | The current mam state. | 137 | | message | Tryte encoded string. | 138 | 139 | 140 | 141 | ### Mam.decode(payload, sideKey, root) ⇒ 142 | Decode a message. 143 | 144 | **Kind**: static method of [Mam](#Mam) 145 | **Returns**: The decoded payload. 146 | 147 | | Param | Description | 148 | | --- | --- | 149 | | payload | The payload of the message. | 150 | | sideKey | The sideKey used in the message. | 151 | | root | The root used for the message. | 152 | 153 | 154 | 155 | ### Mam.fetch(root, mode, sideKey, callback, limit) ⇒ 156 | Fetch the messages asynchronously. 157 | 158 | **Kind**: static method of [Mam](#Mam) 159 | **Returns**: The nextRoot and the messages if no callback was supplied, or an Error. 160 | 161 | | Param | Description | 162 | | --- | --- | 163 | | root | The root key to use. | 164 | | mode | The mode of the channel. | 165 | | sideKey | The sideKey used in the messages, only required for restricted. | 166 | | callback | Optional callback to receive each payload. | 167 | | limit | Limit the number of messages that are fetched. | 168 | 169 | 170 | 171 | ### Mam.fetchSingle(root, mode, sideKey) ⇒ 172 | Fetch a single message asynchronously. 173 | 174 | **Kind**: static method of [Mam](#Mam) 175 | **Returns**: The nextRoot and the payload, or an Error. 176 | 177 | | Param | Description | 178 | | --- | --- | 179 | | root | The root key to use. | 180 | | mode | The mode of the channel. | 181 | | sideKey | The sideKey used in the messages. | 182 | 183 | 184 | 185 | ### Mam.attach(trytes, root, depth, mwm, tag) ⇒ 186 | Attach the mam trytes to the tangle. 187 | 188 | **Kind**: static method of [Mam](#Mam) 189 | **Returns**: The transaction objects. 190 | 191 | | Param | Description | 192 | | --- | --- | 193 | | trytes | The trytes to attach. | 194 | | root | The root to attach them to. | 195 | | depth | The depth to attach them with, defaults to 3. | 196 | | mwm | The minimum weight magnitude to attach with, defaults to 9 for devnet, 14 required for mainnet. | 197 | | tag | Trytes to tag the message with. | 198 | 199 | 200 | 201 | ## LoadBalancerSettings 202 | Settings to use for the load balancer. 203 | 204 | **Kind**: global class 205 | 206 | 207 | ## NodeConfiguration 208 | The configuration for a single node. 209 | 210 | **Kind**: global class 211 | 212 | 213 | ## LinearWalkStrategy 214 | Node choice strategy which just iterates through the list of nodes. 215 | 216 | **Kind**: global class 217 | 218 | * [LinearWalkStrategy](#LinearWalkStrategy) 219 | * [new LinearWalkStrategy(nodes, blacklistLimit)](#new_LinearWalkStrategy_new) 220 | * [.current()](#LinearWalkStrategy+current) ⇒ 221 | * [.next(retainOrder)](#LinearWalkStrategy+next) 222 | 223 | 224 | 225 | ### new LinearWalkStrategy(nodes, blacklistLimit) 226 | Create a new instance of LinearWalkStrategy. 227 | 228 | 229 | | Param | Description | 230 | | --- | --- | 231 | | nodes | The nodes to randomly pick from. | 232 | | blacklistLimit | The number of failures before a node is blacklisted. | 233 | 234 | 235 | 236 | ### linearWalkStrategy.current() ⇒ 237 | Get the current node from the strategy. 238 | 239 | **Kind**: instance method of [LinearWalkStrategy](#LinearWalkStrategy) 240 | **Returns**: A node configuration from the strategy. 241 | 242 | 243 | ### linearWalkStrategy.next(retainOrder) 244 | Move to the next node in the strategy. 245 | 246 | **Kind**: instance method of [LinearWalkStrategy](#LinearWalkStrategy) 247 | 248 | | Param | Description | 249 | | --- | --- | 250 | | retainOrder | Retain the ordering if resetting the list. | 251 | 252 | 253 | 254 | ## RandomWalkStrategy 255 | Node choice strategy which randomly picks from the list of nodes. 256 | 257 | **Kind**: global class 258 | 259 | * [RandomWalkStrategy](#RandomWalkStrategy) 260 | * [new RandomWalkStrategy(nodes, blacklistLimit)](#new_RandomWalkStrategy_new) 261 | * [.current()](#RandomWalkStrategy+current) ⇒ 262 | * [.next(retainOrder)](#RandomWalkStrategy+next) 263 | 264 | 265 | 266 | ### new RandomWalkStrategy(nodes, blacklistLimit) 267 | Create a new instance of RandomWalkStategy. 268 | 269 | 270 | | Param | Description | 271 | | --- | --- | 272 | | nodes | The nodes to randomly pick from. | 273 | | blacklistLimit | The number of failures before a node is blacklisted. | 274 | 275 | 276 | 277 | ### randomWalkStrategy.current() ⇒ 278 | Get the current node from the strategy. 279 | 280 | **Kind**: instance method of [RandomWalkStrategy](#RandomWalkStrategy) 281 | **Returns**: A node configuration from the strategy. 282 | 283 | 284 | ### randomWalkStrategy.next(retainOrder) 285 | Move to the next node in the strategy. 286 | 287 | **Kind**: instance method of [RandomWalkStrategy](#RandomWalkStrategy) 288 | 289 | | Param | Description | 290 | | --- | --- | 291 | | retainOrder | Retain the ordering if resetting the list. | 292 | 293 | 294 | 295 | ## FailMode 296 | Fail modes for the load balancer. 297 | 298 | **Kind**: global variable 299 | 300 | * [FailMode](#FailMode) 301 | * [.single](#FailMode.single) 302 | * [.all](#FailMode.all) 303 | 304 | 305 | 306 | ### FailMode.single 307 | Try single node only, failure throws exception. 308 | 309 | **Kind**: static property of [FailMode](#FailMode) 310 | 311 | 312 | ### FailMode.all 313 | Try all nodes until one succeeds, on all failing throws combined exception. 314 | 315 | **Kind**: static property of [FailMode](#FailMode) 316 | 317 | 318 | ## SuccessMode 319 | Success modes for the load balancer. 320 | 321 | **Kind**: global variable 322 | 323 | * [SuccessMode](#SuccessMode) 324 | * [.keep](#SuccessMode.keep) 325 | * [.next](#SuccessMode.next) 326 | 327 | 328 | 329 | ### SuccessMode.keep 330 | Keep the node if it was successful. 331 | 332 | **Kind**: static property of [SuccessMode](#SuccessMode) 333 | 334 | 335 | ### SuccessMode.next 336 | Move to the next node even if it was successful. 337 | 338 | **Kind**: static property of [SuccessMode](#SuccessMode) 339 | 340 | 341 | ## composeAPI(settings) ⇒ 342 | Create a new instance of the API wrapped with load balancing support. 343 | 344 | **Kind**: global function 345 | **Returns**: The api. 346 | 347 | | Param | Description | 348 | | --- | --- | 349 | | settings | The load balancer settings. | 350 | 351 | -------------------------------------------------------------------------------- /es/composeAPI.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.composeAPI = void 0; 4 | const core_1 = require("@iota/core"); 5 | const internals_1 = require("./internals"); 6 | const failMode_1 = require("./models/failMode"); 7 | const successMode_1 = require("./models/successMode"); 8 | /** 9 | * Create a new instance of the API wrapped with load balancing support. 10 | * @param settings The load balancer settings. 11 | * @returns The api. 12 | */ 13 | function composeAPI(settings) { 14 | if (!settings) { 15 | throw new Error("You must provider settings"); 16 | } 17 | if (!settings.nodeWalkStrategy) { 18 | throw new Error("The nodeWalkStrategy field must be provided"); 19 | } 20 | settings.mwm = settings.mwm || 9; 21 | settings.depth = settings.depth || 3; 22 | settings.successMode = settings.successMode || successMode_1.SuccessMode.next; 23 | settings.failMode = settings.failMode || failMode_1.FailMode.all; 24 | const api = core_1.composeAPI({ attachToTangle: settings.attachToTangle }); 25 | // Wrap all the web methods with additional handling 26 | api.addNeighbors = internals_1.wrapMethodCallbackOrAsync(settings, api, api.addNeighbors, "addNeighbors"); 27 | api.broadcastTransactions = internals_1.wrapMethodCallbackOrAsync(settings, api, api.broadcastTransactions, "broadcastTransactions"); 28 | api.checkConsistency = internals_1.wrapMethodCallbackOrAsync(settings, api, api.checkConsistency, "checkConsistency"); 29 | api.findTransactions = internals_1.wrapMethodCallbackOrAsync(settings, api, api.findTransactions, "findTransactions"); 30 | api.getBalances = internals_1.wrapMethodCallbackOrAsync(settings, api, api.getBalances, "getBalances"); 31 | api.getInclusionStates = internals_1.wrapMethodCallbackOrAsync(settings, api, api.getInclusionStates, "getInclusionStates"); 32 | api.getNeighbors = internals_1.wrapMethodCallbackOrAsync(settings, api, api.getNeighbors, "getNeighbors"); 33 | api.getNodeInfo = internals_1.wrapMethodCallbackOrAsync(settings, api, api.getNodeInfo, "getNodeInfo"); 34 | api.getTransactionsToApprove = internals_1.wrapMethodCallbackOrAsync(settings, api, api.getTransactionsToApprove, "getTransactionsToApprove"); 35 | api.getTrytes = internals_1.wrapMethodCallbackOrAsync(settings, api, api.getTrytes, "getTrytes"); 36 | api.interruptAttachingToTangle = internals_1.wrapMethodCallbackOrAsync(settings, api, api.interruptAttachingToTangle, "interruptAttachingToTangle"); 37 | api.removeNeighbors = internals_1.wrapMethodCallbackOrAsync(settings, api, api.removeNeighbors, "removeNeighbors"); 38 | api.storeTransactions = internals_1.wrapMethodCallbackOrAsync(settings, api, api.storeTransactions, "storeTransactions"); 39 | api.broadcastBundle = internals_1.wrapMethodCallbackOrAsync(settings, api, api.broadcastBundle, "broadcastBundle"); 40 | api.getAccountData = internals_1.wrapMethodCallbackOrAsync(settings, api, api.getAccountData, "getAccountData"); 41 | api.getBundle = internals_1.wrapMethodCallbackOrAsync(settings, api, api.getBundle, "getBundle"); 42 | api.getBundlesFromAddresses = internals_1.wrapMethodCallbackOrAsync(settings, api, api.getBundlesFromAddresses, "getBundlesFromAddresses"); 43 | api.getNewAddress = internals_1.wrapMethodCallbackOrAsync(settings, api, api.getNewAddress, "getNewAddress"); 44 | api.getTransactionObjects = internals_1.wrapMethodCallbackOrAsync(settings, api, api.getTransactionObjects, "getTransactionObjects"); 45 | api.findTransactionObjects = internals_1.wrapMethodCallbackOrAsync(settings, api, api.findTransactionObjects, "findTransactionObjects"); 46 | api.getInputs = internals_1.wrapMethodCallbackOrAsync(settings, api, api.getInputs, "getInputs"); 47 | api.getTransfers = internals_1.wrapMethodCallbackOrAsync(settings, api, api.getTransfers, "getTransfers"); 48 | api.isPromotable = internals_1.wrapMethodCallbackOrAsync(settings, api, api.isPromotable, "isPromotable"); 49 | api.isReattachable = internals_1.wrapMethodCallbackOrAsync(settings, api, api.isReattachable, "isReattachable"); 50 | api.prepareTransfers = internals_1.wrapMethodCallbackOrAsync(settings, api, api.prepareTransfers, "prepareTransfers"); 51 | api.promoteTransaction = internals_1.wrapMethodCallbackOrAsync(settings, api, api.promoteTransaction, "promoteTransaction"); 52 | api.replayBundle = internals_1.wrapMethodCallbackOrAsync(settings, api, api.replayBundle, "replayBundle"); 53 | api.sendTrytes = internals_1.wrapMethodCallbackOrAsync(settings, api, api.sendTrytes, "sendTrytes"); 54 | api.storeAndBroadcast = internals_1.wrapMethodCallbackOrAsync(settings, api, api.storeAndBroadcast, "storeAndBroadcast"); 55 | api.traverseBundle = internals_1.wrapMethodCallbackOrAsync(settings, api, api.traverseBundle, "traverseBundle"); 56 | return api; 57 | } 58 | exports.composeAPI = composeAPI; 59 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcG9zZUFQSS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9jb21wb3NlQVBJLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHFDQUErRDtBQUMvRCwyQ0FBd0Q7QUFDeEQsZ0RBQTZDO0FBRTdDLHNEQUFtRDtBQUVuRDs7OztHQUlHO0FBQ0gsU0FBZ0IsVUFBVSxDQUFDLFFBQThCO0lBQ3JELElBQUksQ0FBQyxRQUFRLEVBQUU7UUFDWCxNQUFNLElBQUksS0FBSyxDQUFDLDRCQUE0QixDQUFDLENBQUM7S0FDakQ7SUFDRCxJQUFJLENBQUMsUUFBUSxDQUFDLGdCQUFnQixFQUFFO1FBQzVCLE1BQU0sSUFBSSxLQUFLLENBQUMsNkNBQTZDLENBQUMsQ0FBQztLQUNsRTtJQUNELFFBQVEsQ0FBQyxHQUFHLEdBQUcsUUFBUSxDQUFDLEdBQUcsSUFBSSxDQUFDLENBQUM7SUFDakMsUUFBUSxDQUFDLEtBQUssR0FBRyxRQUFRLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FBQztJQUNyQyxRQUFRLENBQUMsV0FBVyxHQUFHLFFBQVEsQ0FBQyxXQUFXLElBQUkseUJBQVcsQ0FBQyxJQUFJLENBQUM7SUFDaEUsUUFBUSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUMsUUFBUSxJQUFJLG1CQUFRLENBQUMsR0FBRyxDQUFDO0lBRXRELE1BQU0sR0FBRyxHQUFHLGlCQUFjLENBQUMsRUFBRSxjQUFjLEVBQUUsUUFBUSxDQUFDLGNBQWMsRUFBRSxDQUFDLENBQUM7SUFFeEUsb0RBQW9EO0lBQ3BELEdBQUcsQ0FBQyxZQUFZLEdBQUcscUNBQXlCLENBQUMsUUFBUSxFQUFFLEdBQUcsRUFBRSxHQUFHLENBQUMsWUFBWSxFQUFFLGNBQWMsQ0FBQyxDQUFDO0lBQzlGLEdBQUcsQ0FBQyxxQkFBcUIsR0FBRyxxQ0FBeUIsQ0FBQyxRQUFRLEVBQUUsR0FBRyxFQUFFLEdBQUcsQ0FBQyxxQkFBcUIsRUFBRSx1QkFBdUIsQ0FBQyxDQUFDO0lBQ3pILEdB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 -------------------------------------------------------------------------------- /es/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 10 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 11 | }; 12 | Object.defineProperty(exports, "__esModule", { value: true }); 13 | __exportStar(require("./composeAPI"), exports); 14 | __exportStar(require("./mam"), exports); 15 | __exportStar(require("./models/failMode"), exports); 16 | __exportStar(require("./models/loadBalancerSettings"), exports); 17 | __exportStar(require("./models/nodeConfiguration"), exports); 18 | __exportStar(require("./models/nodeWalkStrategy"), exports); 19 | __exportStar(require("./models/successMode"), exports); 20 | __exportStar(require("./walkStrategies/linearWalkStrategy"), exports); 21 | __exportStar(require("./walkStrategies/randomWalkStrategy"), exports); 22 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBQUEsK0NBQTZCO0FBQzdCLHdDQUFzQjtBQUN0QixvREFBa0M7QUFDbEMsZ0VBQThDO0FBQzlDLDZEQUEyQztBQUMzQyw0REFBMEM7QUFDMUMsdURBQXFDO0FBQ3JDLHNFQUFvRDtBQUNwRCxzRUFBb0QifQ== -------------------------------------------------------------------------------- /es/internals.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.wrapMethodCallbackOrAsync = exports.loadBalancer = void 0; 13 | const validators_1 = require("@iota/validators"); 14 | const failMode_1 = require("./models/failMode"); 15 | const successMode_1 = require("./models/successMode"); 16 | /** 17 | * Create a new instance of the API. 18 | * @param settings The load balancer settings. 19 | * @param updateProvider Update the provider in the calling context. 20 | * @param methodPromise The method to call. 21 | * @param validateResult Let the caller validate the result. 22 | * @returns The api. 23 | * @private 24 | */ 25 | function loadBalancer(settings, updateProvider, methodPromise, validateResult) { 26 | return __awaiter(this, void 0, void 0, function* () { 27 | let res; 28 | let tryNextNode = false; 29 | const totalNodes = settings.nodeWalkStrategy.totalUsable(); 30 | let triedCount = 0; 31 | const errorList = []; 32 | do { 33 | // Get the next node from the strategy 34 | const node = settings.nodeWalkStrategy.current(); 35 | updateProvider(node); 36 | if (settings.tryNodeCallback) { 37 | settings.tryNodeCallback(node); 38 | } 39 | try { 40 | const timeout = node.timeoutMs || settings.timeoutMs; 41 | if (timeout) { 42 | res = yield methodPromise(node).timeout(timeout, `${node.provider} the request timed out`); 43 | } 44 | else { 45 | res = yield methodPromise(node); 46 | } 47 | if (validateResult) { 48 | const validMessage = validateResult(res); 49 | if (validMessage) { 50 | throw new Error(validMessage); 51 | } 52 | } 53 | tryNextNode = false; 54 | if (settings.successMode === successMode_1.SuccessMode.next) { 55 | // Walk to the next node in the strategy 56 | settings.nodeWalkStrategy.next(false); 57 | } 58 | } 59 | catch (err) { 60 | settings.nodeWalkStrategy.blacklist(); 61 | if (settings.failNodeCallback) { 62 | settings.failNodeCallback(node, err); 63 | } 64 | if (settings.failMode === failMode_1.FailMode.single) { 65 | // Single fail mode so just throw the error 66 | throw err; 67 | } 68 | else if (settings.failMode === failMode_1.FailMode.all) { 69 | // Fail mode is try all until one succeeds 70 | errorList.push(err.message ? err : { message: err }); 71 | // Try to use the next node if the current one errored 72 | triedCount++; 73 | // But only if we have not already tried all the nodes 74 | tryNextNode = triedCount < totalNodes; 75 | if (!tryNextNode) { 76 | // No more nodes to try so throw the combined exceptions 77 | throw new Error(`All nodes failed\n ${errorList.map(e => e.message).join("\n ")}`); 78 | } 79 | // Walk to the next node in the strategy 80 | settings.nodeWalkStrategy.next(true); 81 | } 82 | } 83 | } while (tryNextNode); 84 | return res; 85 | }); 86 | } 87 | exports.loadBalancer = loadBalancer; 88 | /** 89 | * Wrap a method and handle either callback or async result. 90 | * @param settings The load balancer settings. 91 | * @param api The composed api. 92 | * @param method The method to wrap. 93 | * @param methodName The name of the method. 94 | * @returns The wrapped method. 95 | * @private 96 | */ 97 | function wrapMethodCallbackOrAsync(settings, api, method, methodName) { 98 | return (...p) => __awaiter(this, void 0, void 0, function* () { 99 | const originalCallbackParam = p[method.length - 1]; 100 | // If the caller is using the callback parameter remove it and use the promise 101 | // method then restore on method completion. 102 | if (originalCallbackParam) { 103 | p[method.length - 1] = undefined; 104 | } 105 | return loadBalancer(settings, (node) => api.setSettings({ 106 | provider: node.provider, 107 | attachToTangle: node.attachToTangle || settings.attachToTangle, 108 | user: node.user || settings.user, 109 | password: node.password || settings.password 110 | }), (node) => { 111 | // Apply the default depth and mwm to methods that use them if they have not been supplied 112 | if (methodName === "promoteTransaction" || 113 | methodName === "replayBundle" || 114 | methodName === "sendTrytes") { 115 | p[1] = p[1] || node.depth || settings.depth; 116 | p[2] = p[2] || node.mwm || settings.mwm; 117 | } 118 | return method(...p); 119 | }, (res) => { 120 | if (settings.snapshotAware && methodName === "getTrytes") { 121 | const trytes = res; 122 | if (trytes) { 123 | for (let i = 0; i < trytes.length; i++) { 124 | if (validators_1.isEmpty(trytes[i])) { 125 | return "Data has been removed by snapshot"; 126 | } 127 | } 128 | } 129 | } 130 | return ""; 131 | }) 132 | .then((res) => { 133 | if (originalCallbackParam) { 134 | originalCallbackParam(null, res); 135 | return undefined; 136 | } 137 | else { 138 | return res; 139 | } 140 | }).catch((err) => { 141 | if (originalCallbackParam) { 142 | originalCallbackParam(err); 143 | } 144 | else { 145 | throw err; 146 | } 147 | }); 148 | }); 149 | } 150 | exports.wrapMethodCallbackOrAsync = wrapMethodCallbackOrAsync; 151 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJuYWxzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ludGVybmFscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7QUFDQSxpREFBMkM7QUFFM0MsZ0RBQTZDO0FBRzdDLHNEQUFtRDtBQUVuRDs7Ozs7Ozs7R0FRRztBQUNILFNBQXNCLFlBQVksQ0FDOUIsUUFBOEIsRUFDOUIsY0FBaUQsRUFDakQsYUFBeUQsRUFDekQsY0FBcUM7O1FBQ3JDLElBQUksR0FBRyxDQUFDO1FBQ1IsSUFBSSxXQUFXLEdBQUcsS0FBSyxDQUFDO1FBQ3hCLE1BQU0sVUFBVSxHQUFHLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUMzRCxJQUFJLFVBQVUsR0FBRyxDQUFDLENBQUM7UUFDbkIsTUFBTSxTQUFTLEdBQVksRUFBRSxDQUFDO1FBRTlCLEdBQUc7WUFDQyxzQ0FBc0M7WUFDdEMsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLGdCQUFnQixDQUFDLE9BQU8sRUFBRSxDQUFDO1lBRWpELGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUVyQixJQUFJLFFBQVEsQ0FBQyxlQUFlLEVBQUU7Z0JBQzFCLFFBQVEsQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLENBQUM7YUFDbEM7WUFFRCxJQUFJO2dCQUNBLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxTQUFTLElBQUksUUFBUSxDQUFDLFNBQVMsQ0FBQztnQkFDckQsSUFBSSxPQUFPLEVBQUU7b0JBQ1QsR0FBRyxHQUFHLE1BQU0sYUFBYSxDQUFDLElBQUksQ0FBQyxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsR0FBRyxJQUFJLENBQUMsUUFBUSx3QkFBd0IsQ0FBQyxDQUFDO2lCQUM5RjtxQkFBTTtvQkFDSCxHQUFHLEdBQUcsTUFBTSxhQUFhLENBQUMsSUFBSSxDQUFDLENBQUM7aUJBQ25DO2dCQUVELElBQUksY0FBYyxFQUFFO29CQUNoQixNQUFNLFlBQVksR0FBRyxjQUFjLENBQUMsR0FBRyxDQUFDLENBQUM7b0JBQ3pDLElBQUksWUFBWSxFQUFFO3dCQUNkLE1BQU0sSUFBSSxLQUFLLENBQUMsWUFBWSxDQUFDLENBQUM7cUJBQ2pDO2lCQUNKO2dCQUNELFdBQVcsR0FBRyxLQUFLLENBQUM7Z0JBQ3BCLElBQUksUUFBUSxDQUFDLFdBQVcsS0FBSyx5QkFBVyxDQUFDLElBQUksRUFBRTtvQkFDM0Msd0NBQXdDO29CQUN4QyxRQUFRLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO2lCQUN6QzthQUNKO1lBQUMsT0FBTyxHQUFHLEVBQUU7Z0JBQ1YsUUFBUSxDQUFDLGdCQUFnQixDQUFDLFNBQVMsRUFBRSxDQUFDO2dCQUV0QyxJQUFJLFFBQVEsQ0FBQyxnQkFBZ0IsRUFBRTtvQkFDM0IsUUFBUSxDQUFDLGdCQUFnQixDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQztpQkFDeEM7Z0JBRUQsSUFBSSxRQUFRLENBQUMsUUFBUSxLQUFLLG1CQUFRLENBQUMsTUFBTSxFQUFFO29CQUN2QywyQ0FBMkM7b0JBQzNDLE1BQU0sR0FBRyxDQUFDO2lCQUNiO3FCQUFNLElBQUksUUFBUSxDQUFDLFFBQVEsS0FBSyxtQkFBUSxDQUFDLEdBQUcsRUFBRTtvQkFDM0MsMENBQTBDO29CQUMxQyxTQUFTLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztvQkFFckQsc0RBQXNEO29CQUN0RCxVQUFVLEVBQUUsQ0FBQztvQkFDYixzREFBc0Q7b0JBQ3RELFdBQVcsR0FBRyxVQUFVLEdBQUcsVUFBVSxDQUFDO29CQUV0QyxJQUFJLENBQUMsV0FBVyxFQUFFO3dCQUNkLHdEQUF3RDt3QkFDeEQsTUFBTSxJQUFJLEtBQUssQ0FBQyx3QkFBd0IsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQUFDO3FCQUMxRjtvQkFFRCx3Q0FBd0M7b0JBQ3hDLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7aUJBQ3hDO2FBQ0o7U0FDSixRQUFRLFdBQVcsRUFBRTtRQUV0QixPQUFPLEdBQUcsQ0FBQztJQUNmLENBQUM7Q0FBQTtBQXZFRCxvQ0F1RUM7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILFNBQWdCLHlCQUF5QixDQUFDLFFBQThCLEVBQUUsR0FBUSxFQUFFLE1BQXlDLEVBQUUsVUFBa0I7SUFDN0ksT0FBTyxDQUFPLEdBQUcsQ0FBTSxFQUFFLEVBQUU7UUFDdkIsTUFBTSxxQkFBcUIsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQztRQUVuRCw4RUFBOEU7UUFDOUUsNENBQTRDO1FBQzVDLElBQUkscUJBQXFCLEVBQUU7WUFDdkIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLEdBQUcsU0FBUyxDQUFDO1NBQ3BDO1FBRUQsT0FBTyxZQUFZLENBQ2YsUUFBUSxFQUNSLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDO1lBQ3RCLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUTtZQUN2QixjQUFjLEVBQUUsSUFBSSxDQUFDLGNBQWMsSUFBSSxRQUFRLENBQUMsY0FBYztZQUM5RCxJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUksSUFBSSxRQUFRLENBQUMsSUFBSTtZQUNoQyxRQUFRLEVBQUUsSUFBSSxDQUFDLFFBQVEsSUFBSSxRQUFRLENBQUMsUUFBUTtTQUMvQyxDQUFDLEVBQ0YsQ0FBQyxJQUFJLEVBQUUsRUFBRTtZQUNMLDBGQUEwRjtZQUMxRixJQUFJLFVBQVUsS0FBSyxvQkFBb0I7Z0JBQ25DLFVBQVUsS0FBSyxjQUFjO2dCQUM3QixVQUFVLEtBQUssWUFBWSxFQUFFO2dCQUM3QixDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLElBQUksQ0FBQyxLQUFLLElBQUksUUFBUSxDQUFDLEtBQUssQ0FBQztnQkFDNUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUMsR0FBRyxJQUFJLFFBQVEsQ0FBQyxHQUFHLENBQUM7YUFDM0M7WUFDRCxPQUFPLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQ3hCLENBQUMsRUFDRCxDQUFDLEdBQUcsRUFBRSxFQUFFO1lBQ0osSUFBSSxRQUFRLENBQUMsYUFBYSxJQUFJLFVBQVUsS0FBSyxXQUFXLEVBQUU7Z0JBQ3RELE1BQU0sTUFBTSxHQUFpRCxHQUFHLENBQUM7Z0JBQ2pFLElBQUksTUFBTSxFQUFFO29CQUNSLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFO3dCQUNwQyxJQUFJLG9CQUFPLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUU7NEJBQ3BCLE9BQU8sbUNBQW1DLENBQUM7eUJBQzlDO3FCQUNKO2lCQUNKO2FBQ0o7WUFDRCxPQUFPLEVBQUUsQ0FBQztRQUNkLENBQUMsQ0FBQzthQUNELElBQUksQ0FBQyxDQUFDLEdBQVEsRUFBRSxFQUFFO1lBQ2YsSUFBSSxxQkFBcUIsRUFBRTtnQkFDdkIscUJBQXFCLENBQUMsSUFBSSxFQUFFLEdBQUcsQ0FBQyxDQUFDO2dCQUNqQyxPQUFPLFNBQVMsQ0FBQzthQUNwQjtpQkFBTTtnQkFDSCxPQUFPLEdBQUcsQ0FBQzthQUNkO1FBQ0wsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUU7WUFDYixJQUFJLHFCQUFxQixFQUFFO2dCQUN2QixxQkFBcUIsQ0FBQyxHQUFHLENBQUMsQ0FBQzthQUM5QjtpQkFBTTtnQkFDSCxNQUFNLEdBQUcsQ0FBQzthQUNiO1FBQ0wsQ0FBQyxDQUFDLENBQUM7SUFDWCxDQUFDLENBQUEsQ0FBQztBQUNOLENBQUM7QUF4REQsOERBd0RDIn0= -------------------------------------------------------------------------------- /es/mam.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.Mam = void 0; 13 | const MamCore = require("@iota/mam"); 14 | const Bluebird = require("bluebird"); 15 | const composeAPI_1 = require("./composeAPI"); 16 | const internals_1 = require("./internals"); 17 | const failMode_1 = require("./models/failMode"); 18 | const successMode_1 = require("./models/successMode"); 19 | /** 20 | * Wrapper for Mam with load balancing 21 | */ 22 | class Mam { 23 | /** 24 | * Initialisation function which returns a state object 25 | * @param settings Settings for the load balancer. 26 | * @param seed The seed to initialise with. 27 | * @param security The security level, defaults to 2. 28 | * @returns The mam state. 29 | */ 30 | static init(settings, seed, security = 2) { 31 | if (!settings) { 32 | throw new Error("You must provider settings"); 33 | } 34 | if (!settings.nodeWalkStrategy) { 35 | throw new Error("The nodeWalkStrategy field must be provided"); 36 | } 37 | settings.mwm = settings.mwm || 9; 38 | settings.depth = settings.depth || 3; 39 | settings.successMode = settings.successMode || successMode_1.SuccessMode.next; 40 | settings.failMode = settings.failMode || failMode_1.FailMode.all; 41 | Mam.loadBalancerSettings = settings; 42 | return MamCore.init({ provider: "" }, seed, security); 43 | } 44 | /** 45 | * Change the mode for the mam state. 46 | * @param state The current mam state. 47 | * @param mode [public/private/restricted]. 48 | * @param sidekey, required for restricted mode. 49 | * @returns Updated state object to be used with future actions. 50 | */ 51 | static changeMode(state, mode, sidekey) { 52 | return MamCore.changeMode(state, mode, sidekey); 53 | } 54 | /** 55 | * Get the root from the mam state. 56 | * @param state The mam state. 57 | * @returns The root. 58 | */ 59 | static getRoot(state) { 60 | return MamCore.getRoot(state); 61 | } 62 | /** 63 | * Add a subscription to your state object 64 | * @param state The state object to add the subscription to. 65 | * @param channelRoot The root of the channel to subscribe to. 66 | * @param channelMode Can be `public`, `private` or `restricted`. 67 | * @param channelKey Optional, the key of the channel to subscribe to. 68 | * @returns Updated state object to be used with future actions. 69 | */ 70 | static subscribe(state, channelRoot, channelMode, channelKey) { 71 | return MamCore.subscribe(state, channelRoot, channelMode, channelKey); 72 | } 73 | /** 74 | * Listen for new message on the channel. 75 | * @param channel The channel to listen on. 76 | * @param callback The callback to receive any messages, 77 | */ 78 | static listen(channel, callback) { 79 | return MamCore.listen(channel, callback); 80 | } 81 | /** 82 | * Creates a MAM message payload from a state object. 83 | * @param state The current mam state. 84 | * @param message Tryte encoded string. 85 | * @returns An object containing the payload and updated state. 86 | */ 87 | static create(state, message) { 88 | return MamCore.create(state, message); 89 | } 90 | /** 91 | * Decode a message. 92 | * @param payload The payload of the message. 93 | * @param sideKey The sideKey used in the message. 94 | * @param root The root used for the message. 95 | * @returns The decoded payload. 96 | */ 97 | static decode(payload, sideKey, root) { 98 | return MamCore.decode(payload, sideKey, root); 99 | } 100 | /** 101 | * Fetch the messages asynchronously. 102 | * @param root The root key to use. 103 | * @param mode The mode of the channel. 104 | * @param sideKey The sideKey used in the messages, only required for restricted. 105 | * @param callback Optional callback to receive each payload. 106 | * @param limit Limit the number of messages that are fetched. 107 | * @returns The nextRoot and the messages if no callback was supplied, or an Error. 108 | */ 109 | static fetch(root, mode, sideKey, callback, limit) { 110 | return __awaiter(this, void 0, void 0, function* () { 111 | return internals_1.loadBalancer(Mam.loadBalancerSettings, (node) => { 112 | MamCore.setIOTA(node.provider); 113 | MamCore.setAttachToTangle(node.attachToTangle || Mam.loadBalancerSettings.attachToTangle); 114 | }, () => new Bluebird((resolve, reject) => { 115 | MamCore.fetch(root, mode, sideKey, callback, limit) 116 | .then(resolve) 117 | .catch(reject); 118 | })); 119 | }); 120 | } 121 | /** 122 | * Fetch a single message asynchronously. 123 | * @param root The root key to use. 124 | * @param mode The mode of the channel. 125 | * @param sideKey The sideKey used in the messages. 126 | * @returns The nextRoot and the payload, or an Error. 127 | */ 128 | static fetchSingle(root, mode, sideKey) { 129 | return __awaiter(this, void 0, void 0, function* () { 130 | const response = yield Mam.fetch(root, mode, sideKey, undefined, 1); 131 | return response instanceof Error ? response : { 132 | payload: response.messages && response.messages.length === 1 ? response.messages[0] : undefined, 133 | nextRoot: response.nextRoot 134 | }; 135 | }); 136 | } 137 | /** 138 | * Attach the mam trytes to the tangle. 139 | * @param trytes The trytes to attach. 140 | * @param root The root to attach them to. 141 | * @param depth The depth to attach them with, defaults to 3. 142 | * @param mwm The minimum weight magnitude to attach with, defaults to 9 for devnet, 14 required for mainnet. 143 | * @param tag Trytes to tag the message with. 144 | * @returns The transaction objects. 145 | */ 146 | static attach(trytes, root, depth, mwm, tag) { 147 | return __awaiter(this, void 0, void 0, function* () { 148 | const { prepareTransfers, sendTrytes } = composeAPI_1.composeAPI(Mam.loadBalancerSettings); 149 | const response = yield prepareTransfers("9".repeat(81), [ 150 | { 151 | address: root, 152 | value: 0, 153 | message: trytes, 154 | tag: tag 155 | } 156 | ]); 157 | return sendTrytes(response, depth || 0, mwm || 0); 158 | }); 159 | } 160 | } 161 | exports.Mam = Mam; 162 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL21hbS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7QUFDQSxxQ0FBcUM7QUFDckMscUNBQXFDO0FBQ3JDLDZDQUEwQztBQUMxQywyQ0FBMkM7QUFDM0MsZ0RBQTZDO0FBRTdDLHNEQUFtRDtBQUVuRDs7R0FFRztBQUNILE1BQWEsR0FBRztJQU1aOzs7Ozs7T0FNRztJQUNJLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBOEIsRUFBRSxJQUFhLEVBQUUsV0FBbUIsQ0FBQztRQUNsRixJQUFJLENBQUMsUUFBUSxFQUFFO1lBQ1gsTUFBTSxJQUFJLEtBQUssQ0FBQyw0QkFBNEIsQ0FBQyxDQUFDO1NBQ2pEO1FBQ0QsSUFBSSxDQUFDLFFBQVEsQ0FBQyxnQkFBZ0IsRUFBRTtZQUM1QixNQUFNLElBQUksS0FBSyxDQUFDLDZDQUE2QyxDQUFDLENBQUM7U0FDbEU7UUFDRCxRQUFRLENBQUMsR0FBRyxHQUFHLFFBQVEsQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDO1FBQ2pDLFFBQVEsQ0FBQyxLQUFLLEdBQUcsUUFBUSxDQUFDLEtBQUssSUFBSSxDQUFDLENBQUM7UUFDckMsUUFBUSxDQUFDLFdBQVcsR0FBRyxRQUFRLENBQUMsV0FBVyxJQUFJLHlCQUFXLENBQUMsSUFBSSxDQUFDO1FBQ2hFLFFBQVEsQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDLFFBQVEsSUFBSSxtQkFBUSxDQUFDLEdBQUcsQ0FBQztRQUN0RCxHQUFHLENBQUMsb0JBQW9CLEdBQUcsUUFBUSxDQUFDO1FBQ3BDLE9BQU8sT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFLFFBQVEsRUFBRSxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDMUQsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNJLE1BQU0sQ0FBQyxVQUFVLENBQUMsS0FBdUIsRUFBRSxJQUFxQixFQUFFLE9BQWdCO1FBQ3JGLE9B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 -------------------------------------------------------------------------------- /es/models/failMode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.FailMode = void 0; 4 | /** 5 | * Fail modes for the load balancer. 6 | */ 7 | var FailMode; 8 | (function (FailMode) { 9 | /** 10 | * Try single node only, failure throws exception. 11 | */ 12 | FailMode["single"] = "single"; 13 | /** 14 | * Try all nodes until one succeeds, on all failing throws combined exception. 15 | */ 16 | FailMode["all"] = "all"; 17 | })(FailMode = exports.FailMode || (exports.FailMode = {})); 18 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFpbE1vZGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbW9kZWxzL2ZhaWxNb2RlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBOztHQUVHO0FBQ0gsSUFBWSxRQVNYO0FBVEQsV0FBWSxRQUFRO0lBQ2hCOztPQUVHO0lBQ0gsNkJBQWlCLENBQUE7SUFDakI7O09BRUc7SUFDSCx1QkFBVyxDQUFBO0FBQ2YsQ0FBQyxFQVRXLFFBQVEsR0FBUixnQkFBUSxLQUFSLGdCQUFRLFFBU25CIn0= -------------------------------------------------------------------------------- /es/models/loadBalancerSettings.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.LoadBalancerSettings = void 0; 4 | /** 5 | * Settings to use for the load balancer. 6 | */ 7 | class LoadBalancerSettings { 8 | } 9 | exports.LoadBalancerSettings = LoadBalancerSettings; 10 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9hZEJhbGFuY2VyU2V0dGluZ3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbW9kZWxzL2xvYWRCYWxhbmNlclNldHRpbmdzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQU1BOztHQUVHO0FBQ0gsTUFBYSxvQkFBb0I7Q0ErRGhDO0FBL0RELG9EQStEQyJ9 -------------------------------------------------------------------------------- /es/models/nodeConfiguration.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.NodeConfiguration = void 0; 4 | /** 5 | * The configuration for a single node. 6 | */ 7 | class NodeConfiguration { 8 | } 9 | exports.NodeConfiguration = NodeConfiguration; 10 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZUNvbmZpZ3VyYXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbW9kZWxzL25vZGVDb25maWd1cmF0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUVBOztHQUVHO0FBQ0gsTUFBYSxpQkFBaUI7Q0FtQzdCO0FBbkNELDhDQW1DQyJ9 -------------------------------------------------------------------------------- /es/models/nodeWalkStrategy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZVdhbGtTdHJhdGVneS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9tb2RlbHMvbm9kZVdhbGtTdHJhdGVneS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0= -------------------------------------------------------------------------------- /es/models/successMode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.SuccessMode = void 0; 4 | /** 5 | * Success modes for the load balancer. 6 | */ 7 | var SuccessMode; 8 | (function (SuccessMode) { 9 | /** 10 | * Keep the node if it was successful. 11 | */ 12 | SuccessMode["keep"] = "keep"; 13 | /** 14 | * Move to the next node even if it was successful. 15 | */ 16 | SuccessMode["next"] = "next"; 17 | })(SuccessMode = exports.SuccessMode || (exports.SuccessMode = {})); 18 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3VjY2Vzc01vZGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbW9kZWxzL3N1Y2Nlc3NNb2RlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBOztHQUVHO0FBQ0gsSUFBWSxXQVNYO0FBVEQsV0FBWSxXQUFXO0lBQ25COztPQUVHO0lBQ0gsNEJBQWEsQ0FBQTtJQUNiOztPQUVHO0lBQ0gsNEJBQWEsQ0FBQTtBQUNqQixDQUFDLEVBVFcsV0FBVyxHQUFYLG1CQUFXLEtBQVgsbUJBQVcsUUFTdEIifQ== -------------------------------------------------------------------------------- /es/walkStrategies/baseWalkStrategy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.BaseWalkStrategy = void 0; 4 | /** 5 | * Common features for the node strategies. 6 | * @private 7 | */ 8 | class BaseWalkStrategy { 9 | /** 10 | * Create a new instance of BaseWalkStrategy. 11 | * @param nodes The nodes to iterate through. 12 | * @param blacklistLimit The number of failures before a node is blacklisted. 13 | */ 14 | constructor(nodes, blacklistLimit) { 15 | if (!nodes || nodes.length === 0) { 16 | throw new Error("You must supply at least one node to the strategy"); 17 | } 18 | this._allNodes = nodes; 19 | this._usableNodes = nodes.slice(); 20 | this._blacklistLimit = blacklistLimit; 21 | this._blacklistNodes = {}; 22 | } 23 | /** 24 | * The total number of nodes configured for the strategy. 25 | * @returns The total number of nodes. 26 | */ 27 | totalUsable() { 28 | return this._usableNodes.length; 29 | } 30 | /** 31 | * Blacklist the current node, so it doesn't get used again once limit is reached. 32 | */ 33 | blacklist() { 34 | if (this._blacklistLimit) { 35 | const current = this.current(); 36 | if (current) { 37 | if (!this._blacklistNodes[current.provider]) { 38 | this._blacklistNodes[current.provider] = 1; 39 | } 40 | else { 41 | this._blacklistNodes[current.provider]++; 42 | } 43 | if (this._blacklistNodes[current.provider] >= this._blacklistLimit) { 44 | const idx = this._usableNodes.indexOf(current); 45 | if (idx >= 0) { 46 | this._usableNodes.splice(idx, 1); 47 | } 48 | } 49 | // If there are no usable nodes left then reset the blacklists 50 | if (this._usableNodes.length === 0) { 51 | this._blacklistNodes = {}; 52 | this._usableNodes = this._allNodes.slice(); 53 | } 54 | } 55 | } 56 | } 57 | /** 58 | * Get the list of nodes that have not been blacklisted. 59 | * @returns The non blacklisted nodes. 60 | */ 61 | getUsableNodes() { 62 | return this._usableNodes; 63 | } 64 | } 65 | exports.BaseWalkStrategy = BaseWalkStrategy; 66 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFzZVdhbGtTdHJhdGVneS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy93YWxrU3RyYXRlZ2llcy9iYXNlV2Fsa1N0cmF0ZWd5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUdBOzs7R0FHRztBQUNILE1BQXNCLGdCQUFnQjtJQXFCbEM7Ozs7T0FJRztJQUNILFlBQVksS0FBMEIsRUFBRSxjQUF1QjtRQUMzRCxJQUFJLENBQUMsS0FBSyxJQUFJLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO1lBQzlCLE1BQU0sSUFBSSxLQUFLLENBQUMsbURBQW1ELENBQUMsQ0FBQztTQUN4RTtRQUNELElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxZQUFZLEdBQUcsS0FBSyxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ2xDLElBQUksQ0FBQyxlQUFlLEdBQUcsY0FBYyxDQUFDO1FBQ3RDLElBQUksQ0FBQyxlQUFlLEdBQUcsRUFBRSxDQUFDO0lBQzlCLENBQUM7SUFFRDs7O09BR0c7SUFDSSxXQUFXO1FBQ2QsT0FBTyxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQztJQUNwQyxDQUFDO0lBY0Q7O09BRUc7SUFDSSxTQUFTO1FBQ1osSUFBSSxJQUFJLENBQUMsZUFBZSxFQUFFO1lBQ3RCLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUMvQixJQUFJLE9BQU8sRUFBRTtnQkFDVCxJQUFJLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLEVBQUU7b0JBQ3pDLElBQUksQ0FBQyxlQUFlLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQztpQkFDOUM7cUJBQU07b0JBQ0gsSUFBSSxDQUFDLGVBQWUsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztpQkFDNUM7Z0JBQ0QsSUFBSSxJQUFJLENBQUMsZUFBZSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsSUFBSSxJQUFJLENBQUMsZUFBZSxFQUFFO29CQUNoRSxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQztvQkFDL0MsSUFBSSxHQUFHLElBQUksQ0FBQyxFQUFFO3dCQUNWLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQztxQkFDcEM7aUJBQ0o7Z0JBRUQsOERBQThEO2dCQUM5RCxJQUFJLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtvQkFDaEMsSUFBSSxDQUFDLGVBQWUsR0FBRyxFQUFFLENBQUM7b0JBQzFCLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEVBQUUsQ0FBQztpQkFDOUM7YUFDSjtTQUNKO0lBQ0wsQ0FBQztJQUVEOzs7T0FHRztJQUNPLGNBQWM7UUFDcEIsT0FBTyxJQUFJLENBQUMsWUFBWSxDQUFDO0lBQzdCLENBQUM7Q0FDSjtBQTNGRCw0Q0EyRkMifQ== -------------------------------------------------------------------------------- /es/walkStrategies/linearWalkStrategy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.LinearWalkStrategy = void 0; 4 | const baseWalkStrategy_1 = require("./baseWalkStrategy"); 5 | /** 6 | * Node choice strategy which just iterates through the list of nodes. 7 | */ 8 | class LinearWalkStrategy extends baseWalkStrategy_1.BaseWalkStrategy { 9 | /** 10 | * Create a new instance of LinearWalkStrategy. 11 | * @param nodes The nodes to randomly pick from. 12 | * @param blacklistLimit The number of failures before a node is blacklisted. 13 | */ 14 | constructor(nodes, blacklistLimit) { 15 | super(nodes, blacklistLimit); 16 | this._currentIndex = 0; 17 | } 18 | /** 19 | * Get the current node from the strategy. 20 | * @returns A node configuration from the strategy. 21 | */ 22 | current() { 23 | return this.getUsableNodes()[this._currentIndex]; 24 | } 25 | /** 26 | * Move to the next node in the strategy. 27 | * @param retainOrder Retain the ordering if resetting the list. 28 | */ 29 | next(retainOrder) { 30 | this._currentIndex = (this._currentIndex + 1) % this.getUsableNodes().length; 31 | } 32 | } 33 | exports.LinearWalkStrategy = LinearWalkStrategy; 34 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGluZWFyV2Fsa1N0cmF0ZWd5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3dhbGtTdHJhdGVnaWVzL2xpbmVhcldhbGtTdHJhdGVneS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFDQSx5REFBc0Q7QUFFdEQ7O0dBRUc7QUFDSCxNQUFhLGtCQUFtQixTQUFRLG1DQUFnQjtJQU1wRDs7OztPQUlHO0lBQ0gsWUFBWSxLQUEwQixFQUFFLGNBQXVCO1FBQzNELEtBQUssQ0FBQyxLQUFLLEVBQUUsY0FBYyxDQUFDLENBQUM7UUFDN0IsSUFBSSxDQUFDLGFBQWEsR0FBRyxDQUFDLENBQUM7SUFDM0IsQ0FBQztJQUVEOzs7T0FHRztJQUNJLE9BQU87UUFDVixPQUFPLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUM7SUFDckQsQ0FBQztJQUVEOzs7T0FHRztJQUNJLElBQUksQ0FBQyxXQUFvQjtRQUM1QixJQUFJLENBQUMsYUFBYSxHQUFHLENBQUMsSUFBSSxDQUFDLGFBQWEsR0FBRyxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUMsTUFBTSxDQUFDO0lBQ2pGLENBQUM7Q0FDSjtBQS9CRCxnREErQkMifQ== -------------------------------------------------------------------------------- /es/walkStrategies/randomWalkStrategy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.RandomWalkStrategy = void 0; 4 | const baseWalkStrategy_1 = require("./baseWalkStrategy"); 5 | /** 6 | * Node choice strategy which randomly picks from the list of nodes. 7 | */ 8 | class RandomWalkStrategy extends baseWalkStrategy_1.BaseWalkStrategy { 9 | /** 10 | * Create a new instance of RandomWalkStategy. 11 | * @param nodes The nodes to randomly pick from. 12 | * @param blacklistLimit The number of failures before a node is blacklisted. 13 | */ 14 | constructor(nodes, blacklistLimit) { 15 | super(nodes, blacklistLimit); 16 | this._remainingNodes = []; 17 | this._randomNodes = []; 18 | this.populateRemaining(); 19 | } 20 | /** 21 | * Get the current node from the strategy. 22 | * @returns A node configuration from the strategy. 23 | */ 24 | current() { 25 | return this._remainingNodes[0]; 26 | } 27 | /** 28 | * Move to the next node in the strategy. 29 | * @param retainOrder Retain the ordering if resetting the list. 30 | */ 31 | next(retainOrder) { 32 | this._remainingNodes.shift(); 33 | if (this._remainingNodes.length === 0) { 34 | if (retainOrder) { 35 | this._remainingNodes = this._randomNodes.slice(); 36 | } 37 | else { 38 | this.populateRemaining(); 39 | } 40 | } 41 | } 42 | /** 43 | * Populate the remaining array by randomizing the nodes. 44 | * @internal 45 | * @private 46 | */ 47 | populateRemaining() { 48 | this._remainingNodes = super.getUsableNodes().slice(); 49 | for (let i = this._remainingNodes.length - 1; i > 0; i--) { 50 | // tslint:disable-next-line:insecure-random 51 | const j = Math.floor(Math.random() * (i + 1)); 52 | [this._remainingNodes[i], this._remainingNodes[j]] = [this._remainingNodes[j], this._remainingNodes[i]]; 53 | } 54 | this._randomNodes = this._remainingNodes.slice(); 55 | } 56 | } 57 | exports.RandomWalkStrategy = RandomWalkStrategy; 58 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmFuZG9tV2Fsa1N0cmF0ZWd5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3dhbGtTdHJhdGVnaWVzL3JhbmRvbVdhbGtTdHJhdGVneS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFDQSx5REFBc0Q7QUFFdEQ7O0dBRUc7QUFDSCxNQUFhLGtCQUFtQixTQUFRLG1DQUFnQjtJQWFwRDs7OztPQUlHO0lBQ0gsWUFBWSxLQUEwQixFQUFFLGNBQXVCO1FBQzNELEtBQUssQ0FBQyxLQUFLLEVBQUUsY0FBYyxDQUFDLENBQUM7UUFDN0IsSUFBSSxDQUFDLGVBQWUsR0FBRyxFQUFFLENBQUM7UUFDMUIsSUFBSSxDQUFDLFlBQVksR0FBRyxFQUFFLENBQUM7UUFDdkIsSUFBSSxDQUFDLGlCQUFpQixFQUFFLENBQUM7SUFDN0IsQ0FBQztJQUVEOzs7T0FHRztJQUNJLE9BQU87UUFDVixPQUFPLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDbkMsQ0FBQztJQUVEOzs7T0FHRztJQUNJLElBQUksQ0FBQyxXQUFvQjtRQUM1QixJQUFJLENBQUMsZUFBZSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQzdCLElBQUksSUFBSSxDQUFDLGVBQWUsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO1lBQ25DLElBQUksV0FBVyxFQUFFO2dCQUNiLElBQUksQ0FBQyxlQUFlLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxLQUFLLEVBQUUsQ0FBQzthQUNwRDtpQkFBTTtnQkFDSCxJQUFJLENBQUMsaUJBQWlCLEVBQUUsQ0FBQzthQUM1QjtTQUNKO0lBQ0wsQ0FBQztJQUVEOzs7O09BSUc7SUFDSyxpQkFBaUI7UUFDckIsSUFBSSxDQUFDLGVBQWUsR0FBRyxLQUFLLENBQUMsY0FBYyxFQUFFLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDdEQsS0FBSyxJQUFJLENBQUMsR0FBRyxJQUFJLENBQUMsZUFBZSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUN0RCwyQ0FBMkM7WUFDM0MsTUFBTSxDQUFDLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUM5QyxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLEVBQUUsSUFBSSxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUMsRUFBRSxJQUFJLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDM0c7UUFDRCxJQUFJLENBQUMsWUFBWSxHQUFHLElBQUksQ0FBQyxlQUFlLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDckQsQ0FBQztDQUNKO0FBOURELGdEQThEQyJ9 -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # IOTA JavaScript Client Load Balancer Examples 2 | 3 | The examples contain the following items: 4 | 5 | ## Simple 6 | 7 | A simple usage of the load balancer with composeAPI. 8 | 9 | See [./simple](./simple) for more details. 10 | 11 | ## MAM 12 | 13 | Demonstrates publishing and fetching messages using MAM. 14 | 15 | See [./mam](./mam) for more details. 16 | -------------------------------------------------------------------------------- /examples/mam/index.js: -------------------------------------------------------------------------------- 1 | const { FailMode, LinearWalkStrategy, Mam, SuccessMode } = require('@iota/client-load-balancer'); 2 | const { asciiToTrytes, trytesToAscii } = require('@iota/converter'); 3 | 4 | // Set the channel mode 5 | const channelMode = 'public' // 'private' 'restricted' 6 | const retrictedSideKeyTrytes = channelMode === 'restricted' ? 'THIS9IS9A9RESTRICTED9KEY' : undefined; 7 | const explorer = 'https://devnet.thetangle.org'; 8 | 9 | // Publish a new message 10 | async function publishMessage(mamState, trytesMessage) { 11 | console.log(`Publishing Message: ${trytesMessage}`); 12 | 13 | // Create MAM Payload 14 | const message = Mam.create(mamState, trytesMessage); 15 | 16 | console.log(`Root: ${message.root}`); 17 | console.log(`Address: ${message.address}`); 18 | 19 | // Attach the payload 20 | console.log('Attaching payload, please wait...'); 21 | 22 | try { 23 | await Mam.attach(message.payload, message.address); 24 | return message; 25 | } catch (err) { 26 | console.error('There was an error attaching the message', err.message); 27 | } 28 | } 29 | 30 | // Fetch message beginning at the specific root. 31 | async function fetchMessages(messageRoot) { 32 | console.log(`Fetching Messages from Root: ${messageRoot}`); 33 | 34 | try { 35 | const response = await Mam.fetch(messageRoot, channelMode, retrictedSideKeyTrytes); 36 | 37 | if (response) { 38 | if (!response.messages || response.messages.length === 0) { 39 | console.log('There are no messages.') 40 | } else { 41 | response.messages.forEach(messageTrytes => { 42 | console.log(`Fetched Message: ${trytesToAscii(messageTrytes)}`); 43 | }); 44 | } 45 | console.log(`Next Root: ${response.nextRoot}`); 46 | } 47 | return response; 48 | } catch (err) { 49 | console.error('There was an error fetching messages', err); 50 | } 51 | } 52 | 53 | (async function () { 54 | try { 55 | const nodeWalkStrategy = new LinearWalkStrategy( 56 | [ 57 | { 58 | 'provider': 'https://thiswillfailbigly.eu', 59 | 'depth': 3, 60 | 'mwm': 9 61 | }, 62 | { 63 | 'provider': 'https://nodes.devnet.iota.org:443', 64 | 'depth': 3, 65 | 'mwm': 9 66 | } 67 | ], 68 | 1 // Blacklist count, will stop using the initial node once it has failed 69 | ); 70 | 71 | let mamState = Mam.init({ 72 | nodeWalkStrategy, 73 | successMode: SuccessMode.next, 74 | failMode: FailMode.all, 75 | timeoutMs: 5000, 76 | tryNodeCallback: (node) => { 77 | console.log(`Trying node ${node.provider}`); 78 | }, 79 | failNodeCallback: (node, err) => { 80 | console.log(`Failed node ${node.provider}, ${err.message}`); 81 | } 82 | }); 83 | 84 | const initialRoot = Mam.getRoot(mamState); 85 | console.log('Root', initialRoot); 86 | 87 | console.log(`Channel Mode: ${channelMode}`); 88 | mamState = Mam.changeMode(mamState, channelMode, retrictedSideKeyTrytes); 89 | 90 | const messageResponse = await fetchMessages(initialRoot); 91 | 92 | if (messageResponse) { 93 | mamState.channel.start = messageResponse.messages.length; 94 | 95 | const message = await publishMessage(mamState, asciiToTrytes(`This is my message ${messageResponse.messages.length + 1}`)); 96 | 97 | if (message) { 98 | console.log('Message Published'); 99 | if (channelMode === 'public') { 100 | console.log(`You can view the message chain on the tangle:`); 101 | console.log(`${explorer}/mam/${initialRoot}`); 102 | console.log(`or just for this message at:`); 103 | console.log(`${explorer}/mam/${message.address}`); 104 | } else { 105 | console.log(`You can view the transactions for this this message at:`); 106 | console.log(`${explorer}/address/${message.address}`); 107 | } 108 | await fetchMessages(message.root); 109 | } 110 | } 111 | 112 | } catch (err) { 113 | console.error(`Error: ${err.message}`); 114 | } 115 | })(); -------------------------------------------------------------------------------- /examples/mam/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mam", 3 | "description": "Mam example for IOTA Client Load Balancer", 4 | "version": "1.0.0", 5 | "author": "Martyn Janes ", 6 | "dependencies": { 7 | "@iota/client-load-balancer": "file:../../" 8 | }, 9 | "scripts": { 10 | "start": "node ./index.js" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/simple/index.js: -------------------------------------------------------------------------------- 1 | const { composeAPI, FailMode, RandomWalkStrategy, SuccessMode } = require('@iota/client-load-balancer'); 2 | 3 | (async function () { 4 | try { 5 | const nodeWalkStrategy = new RandomWalkStrategy( 6 | [ 7 | { 8 | 'provider': 'https://altnodes.devnet.iota.org:443', 9 | 'depth': 3, 10 | 'mwm': 9 11 | }, 12 | { 13 | 'provider': 'https://nodes.devnet.iota.org:443', 14 | 'depth': 3, 15 | 'mwm': 9 16 | } 17 | ] 18 | ); 19 | 20 | const api = composeAPI({ 21 | nodeWalkStrategy, 22 | successMode: SuccessMode.next, 23 | failMode: FailMode.all, 24 | timeoutMs: 5000, 25 | tryNodeCallback: (node) => { 26 | console.log(`Trying node ${node.provider}`); 27 | }, 28 | failNodeCallback: (node, err) => { 29 | console.log(`Failed node ${node.provider}, ${err.message}`); 30 | } 31 | }); 32 | 33 | const res = await api.getNodeInfo(); 34 | console.log('App Name:', res.appName); 35 | console.log('App Version:', res.appVersion); 36 | } catch (err) { 37 | console.error(`Error: ${err.message}`); 38 | } 39 | })(); -------------------------------------------------------------------------------- /examples/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple", 3 | "description": "Simple example for IOTA Client Load Balancer", 4 | "version": "1.0.0", 5 | "author": "Martyn Janes ", 6 | "dependencies": { 7 | "@iota/client-load-balancer": "file:../../" 8 | }, 9 | "scripts": { 10 | "start": "node ./index.js" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@iota/client-load-balancer", 3 | "description": "IOTA JavaScript Client Load Balancer", 4 | "version": "1.0.2", 5 | "keywords": [ 6 | "iota", 7 | "client", 8 | "load", 9 | "balancer" 10 | ], 11 | "authors": [ 12 | "Martyn Janes " 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/iotaledger/iota-client-load-balancer.git" 17 | }, 18 | "license": "MIT", 19 | "dependencies": { 20 | "@iota/core": "^1.0.0-beta.30", 21 | "@iota/mam": "github:iotaledger/mam.client.js", 22 | "@iota/validators": "^1.0.0-beta.30", 23 | "bluebird": "^3.7.2" 24 | }, 25 | "scripts": { 26 | "build-clean": "rimraf ./es/*", 27 | "build-lint": "tslint --project ./tsconfig.json", 28 | "build-compile": "tsc", 29 | "build-watch": "tsc --watch", 30 | "build": "run-s build-clean build-lint build-compile", 31 | "test": "jest", 32 | "umd": "rollup --config rollup.config.js", 33 | "umd-min": "rollup --config rollup.config.js --environment MINIFY:true", 34 | "dist-clean": "rimraf ./dist/* ./typings/*", 35 | "typings": "tsc --emitDeclarationOnly --declaration true --declarationDir typings", 36 | "docs": "jsdoc2md --no-cache --files ./es/**/*.js > ./docs/api.md", 37 | "dist": "run-s dist-clean build test umd umd-min typings docs" 38 | }, 39 | "browserslist": [ 40 | ">0.2%", 41 | "not dead", 42 | "not ie <= 11", 43 | "not op_mini all" 44 | ], 45 | "devDependencies": { 46 | "@types/bluebird": "^3.5.33", 47 | "@types/jest": "^26.0.15", 48 | "@types/node": "^14.14.9", 49 | "cross-env": "^7.0.2", 50 | "jest": "^26.6.3", 51 | "jsdoc-to-markdown": "^6.0.1", 52 | "npm-run-all": "^4.1.5", 53 | "rimraf": "^3.0.2", 54 | "rollup": "^2.33.3", 55 | "rollup-plugin-commonjs": "^10.1.0", 56 | "rollup-plugin-node-resolve": "^5.1.0", 57 | "rollup-plugin-terser": "^7.0.2", 58 | "rollup-plugin-typescript": "^1.0.0", 59 | "rollup-plugin-uglify": "^6.0.4", 60 | "ts-jest": "^26.4.4", 61 | "ts-node": "^9.0.0", 62 | "tslint": "^6.1.3", 63 | "tslint-eslint-rules": "^5.4.0", 64 | "tslint-microsoft-contrib": "^6.2.0", 65 | "typescript": "^4.1.2" 66 | }, 67 | "jest": { 68 | "transform": { 69 | "^.+\\.ts?$": "ts-jest" 70 | }, 71 | "testRegex": "./test/.*.spec.ts$", 72 | "moduleFileExtensions": [ 73 | "ts", 74 | "js" 75 | ], 76 | "collectCoverage": true 77 | }, 78 | "main": "dist/iota-client-load-balancer.js", 79 | "module": "es/index.js", 80 | "typings": "typings/index.d.ts", 81 | "files": [ 82 | "dist", 83 | "lib", 84 | "es", 85 | "src", 86 | "typings" 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from 'rollup-plugin-commonjs'; 2 | import resolve from 'rollup-plugin-node-resolve'; 3 | import { terser } from "rollup-plugin-terser"; 4 | import typescript from 'rollup-plugin-typescript'; 5 | 6 | const plugins = [ 7 | commonjs(), 8 | resolve(), 9 | typescript({ 10 | target: "es5", 11 | module: "es2015" 12 | }) 13 | ]; 14 | 15 | if (process.env.MINIFY) { 16 | plugins.push(terser()); 17 | } 18 | 19 | export default { 20 | input: './src/index.ts', 21 | external: [ 22 | "@iota/core", 23 | "@iota/validators", 24 | "@iota/mam", 25 | "bluebird" 26 | ], 27 | output: { 28 | file: `dist/iota-client-load-balancer${process.env.MINIFY ? '.min' : ''}.js`, 29 | format: 'umd', 30 | name: 'IotaClientLoadBalancer', 31 | compact: process.env.MINIFY, 32 | globals: { 33 | "@iota/core": 'core', 34 | "@iota/validators": 'validators', 35 | "@iota/mam": 'MamCore', 36 | "bluebird": 'Bluebird' 37 | } 38 | }, 39 | plugins 40 | } -------------------------------------------------------------------------------- /src/composeAPI.ts: -------------------------------------------------------------------------------- 1 | import { API, composeAPI as composeAPICore } from "@iota/core"; 2 | import { wrapMethodCallbackOrAsync } from "./internals"; 3 | import { FailMode } from "./models/failMode"; 4 | import { LoadBalancerSettings } from "./models/loadBalancerSettings"; 5 | import { SuccessMode } from "./models/successMode"; 6 | 7 | /** 8 | * Create a new instance of the API wrapped with load balancing support. 9 | * @param settings The load balancer settings. 10 | * @returns The api. 11 | */ 12 | export function composeAPI(settings: LoadBalancerSettings): API { 13 | if (!settings) { 14 | throw new Error("You must provider settings"); 15 | } 16 | if (!settings.nodeWalkStrategy) { 17 | throw new Error("The nodeWalkStrategy field must be provided"); 18 | } 19 | settings.mwm = settings.mwm || 9; 20 | settings.depth = settings.depth || 3; 21 | settings.successMode = settings.successMode || SuccessMode.next; 22 | settings.failMode = settings.failMode || FailMode.all; 23 | 24 | const api = composeAPICore({ attachToTangle: settings.attachToTangle }); 25 | 26 | // Wrap all the web methods with additional handling 27 | api.addNeighbors = wrapMethodCallbackOrAsync(settings, api, api.addNeighbors, "addNeighbors"); 28 | api.broadcastTransactions = wrapMethodCallbackOrAsync(settings, api, api.broadcastTransactions, "broadcastTransactions"); 29 | api.checkConsistency = wrapMethodCallbackOrAsync(settings, api, api.checkConsistency, "checkConsistency"); 30 | api.findTransactions = wrapMethodCallbackOrAsync(settings, api, api.findTransactions, "findTransactions"); 31 | api.getBalances = wrapMethodCallbackOrAsync(settings, api, api.getBalances, "getBalances"); 32 | api.getInclusionStates = wrapMethodCallbackOrAsync(settings, api, api.getInclusionStates, "getInclusionStates"); 33 | api.getNeighbors = wrapMethodCallbackOrAsync(settings, api, api.getNeighbors, "getNeighbors"); 34 | api.getNodeInfo = wrapMethodCallbackOrAsync(settings, api, api.getNodeInfo, "getNodeInfo"); 35 | api.getTransactionsToApprove = wrapMethodCallbackOrAsync(settings, api, api.getTransactionsToApprove, "getTransactionsToApprove"); 36 | api.getTrytes = wrapMethodCallbackOrAsync(settings, api, api.getTrytes, "getTrytes"); 37 | api.interruptAttachingToTangle = wrapMethodCallbackOrAsync(settings, api, api.interruptAttachingToTangle, "interruptAttachingToTangle"); 38 | api.removeNeighbors = wrapMethodCallbackOrAsync(settings, api, api.removeNeighbors, "removeNeighbors"); 39 | api.storeTransactions = wrapMethodCallbackOrAsync(settings, api, api.storeTransactions, "storeTransactions"); 40 | api.broadcastBundle = wrapMethodCallbackOrAsync(settings, api, api.broadcastBundle, "broadcastBundle"); 41 | api.getAccountData = wrapMethodCallbackOrAsync(settings, api, api.getAccountData, "getAccountData"); 42 | api.getBundle = wrapMethodCallbackOrAsync(settings, api, api.getBundle, "getBundle"); 43 | api.getBundlesFromAddresses = wrapMethodCallbackOrAsync(settings, api, api.getBundlesFromAddresses, "getBundlesFromAddresses"); 44 | api.getNewAddress = wrapMethodCallbackOrAsync(settings, api, api.getNewAddress, "getNewAddress"); 45 | api.getTransactionObjects = wrapMethodCallbackOrAsync(settings, api, api.getTransactionObjects, "getTransactionObjects"); 46 | api.findTransactionObjects = wrapMethodCallbackOrAsync(settings, api, api.findTransactionObjects, "findTransactionObjects"); 47 | api.getInputs = wrapMethodCallbackOrAsync(settings, api, api.getInputs, "getInputs"); 48 | api.getTransfers = wrapMethodCallbackOrAsync(settings, api, api.getTransfers, "getTransfers"); 49 | api.isPromotable = wrapMethodCallbackOrAsync(settings, api, api.isPromotable, "isPromotable"); 50 | api.isReattachable = wrapMethodCallbackOrAsync(settings, api, api.isReattachable, "isReattachable"); 51 | api.prepareTransfers = wrapMethodCallbackOrAsync(settings, api, api.prepareTransfers, "prepareTransfers"); 52 | api.promoteTransaction = wrapMethodCallbackOrAsync(settings, api, api.promoteTransaction, "promoteTransaction"); 53 | api.replayBundle = wrapMethodCallbackOrAsync(settings, api, api.replayBundle, "replayBundle"); 54 | api.sendTrytes = wrapMethodCallbackOrAsync(settings, api, api.sendTrytes, "sendTrytes"); 55 | api.storeAndBroadcast = wrapMethodCallbackOrAsync(settings, api, api.storeAndBroadcast, "storeAndBroadcast"); 56 | api.traverseBundle = wrapMethodCallbackOrAsync(settings, api, api.traverseBundle, "traverseBundle"); 57 | 58 | return api; 59 | } 60 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./composeAPI"; 2 | export * from "./mam"; 3 | export * from "./models/failMode"; 4 | export * from "./models/loadBalancerSettings"; 5 | export * from "./models/nodeConfiguration"; 6 | export * from "./models/nodeWalkStrategy"; 7 | export * from "./models/successMode"; 8 | export * from "./walkStrategies/linearWalkStrategy"; 9 | export * from "./walkStrategies/randomWalkStrategy"; 10 | -------------------------------------------------------------------------------- /src/internals.ts: -------------------------------------------------------------------------------- 1 | import { API } from "@iota/core"; 2 | import { isEmpty } from "@iota/validators"; 3 | import * as Bluebird from "bluebird"; 4 | import { FailMode } from "./models/failMode"; 5 | import { LoadBalancerSettings } from "./models/loadBalancerSettings"; 6 | import { NodeConfiguration } from "./models/nodeConfiguration"; 7 | import { SuccessMode } from "./models/successMode"; 8 | 9 | /** 10 | * Create a new instance of the API. 11 | * @param settings The load balancer settings. 12 | * @param updateProvider Update the provider in the calling context. 13 | * @param methodPromise The method to call. 14 | * @param validateResult Let the caller validate the result. 15 | * @returns The api. 16 | * @private 17 | */ 18 | export async function loadBalancer( 19 | settings: LoadBalancerSettings, 20 | updateProvider: (node: NodeConfiguration) => void, 21 | methodPromise: (node: NodeConfiguration) => Bluebird, 22 | validateResult?: (res: any) => string): Promise { 23 | let res; 24 | let tryNextNode = false; 25 | const totalNodes = settings.nodeWalkStrategy.totalUsable(); 26 | let triedCount = 0; 27 | const errorList: Error[] = []; 28 | 29 | do { 30 | // Get the next node from the strategy 31 | const node = settings.nodeWalkStrategy.current(); 32 | 33 | updateProvider(node); 34 | 35 | if (settings.tryNodeCallback) { 36 | settings.tryNodeCallback(node); 37 | } 38 | 39 | try { 40 | const timeout = node.timeoutMs || settings.timeoutMs; 41 | if (timeout) { 42 | res = await methodPromise(node).timeout(timeout, `${node.provider} the request timed out`); 43 | } else { 44 | res = await methodPromise(node); 45 | } 46 | 47 | if (validateResult) { 48 | const validMessage = validateResult(res); 49 | if (validMessage) { 50 | throw new Error(validMessage); 51 | } 52 | } 53 | tryNextNode = false; 54 | if (settings.successMode === SuccessMode.next) { 55 | // Walk to the next node in the strategy 56 | settings.nodeWalkStrategy.next(false); 57 | } 58 | } catch (err) { 59 | settings.nodeWalkStrategy.blacklist(); 60 | 61 | if (settings.failNodeCallback) { 62 | settings.failNodeCallback(node, err); 63 | } 64 | 65 | if (settings.failMode === FailMode.single) { 66 | // Single fail mode so just throw the error 67 | throw err; 68 | } else if (settings.failMode === FailMode.all) { 69 | // Fail mode is try all until one succeeds 70 | errorList.push(err.message ? err : { message: err }); 71 | 72 | // Try to use the next node if the current one errored 73 | triedCount++; 74 | // But only if we have not already tried all the nodes 75 | tryNextNode = triedCount < totalNodes; 76 | 77 | if (!tryNextNode) { 78 | // No more nodes to try so throw the combined exceptions 79 | throw new Error(`All nodes failed\n ${errorList.map(e => e.message).join("\n ")}`); 80 | } 81 | 82 | // Walk to the next node in the strategy 83 | settings.nodeWalkStrategy.next(true); 84 | } 85 | } 86 | } while (tryNextNode); 87 | 88 | return res; 89 | } 90 | 91 | /** 92 | * Wrap a method and handle either callback or async result. 93 | * @param settings The load balancer settings. 94 | * @param api The composed api. 95 | * @param method The method to wrap. 96 | * @param methodName The name of the method. 97 | * @returns The wrapped method. 98 | * @private 99 | */ 100 | export function wrapMethodCallbackOrAsync(settings: LoadBalancerSettings, api: API, method: (...params: any) => Bluebird, methodName: string): () => any { 101 | return async (...p: any) => { 102 | const originalCallbackParam = p[method.length - 1]; 103 | 104 | // If the caller is using the callback parameter remove it and use the promise 105 | // method then restore on method completion. 106 | if (originalCallbackParam) { 107 | p[method.length - 1] = undefined; 108 | } 109 | 110 | return loadBalancer( 111 | settings, 112 | (node) => api.setSettings({ 113 | provider: node.provider, 114 | attachToTangle: node.attachToTangle || settings.attachToTangle, 115 | user: node.user || settings.user, 116 | password: node.password || settings.password 117 | }), 118 | (node) => { 119 | // Apply the default depth and mwm to methods that use them if they have not been supplied 120 | if (methodName === "promoteTransaction" || 121 | methodName === "replayBundle" || 122 | methodName === "sendTrytes") { 123 | p[1] = p[1] || node.depth || settings.depth; 124 | p[2] = p[2] || node.mwm || settings.mwm; 125 | } 126 | return method(...p); 127 | }, 128 | (res) => { 129 | if (settings.snapshotAware && methodName === "getTrytes") { 130 | const trytes: ReadonlyArray = >res; 131 | if (trytes) { 132 | for (let i = 0; i < trytes.length; i++) { 133 | if (isEmpty(trytes[i])) { 134 | return "Data has been removed by snapshot"; 135 | } 136 | } 137 | } 138 | } 139 | return ""; 140 | }) 141 | .then((res: any) => { 142 | if (originalCallbackParam) { 143 | originalCallbackParam(null, res); 144 | return undefined; 145 | } else { 146 | return res; 147 | } 148 | }).catch((err) => { 149 | if (originalCallbackParam) { 150 | originalCallbackParam(err); 151 | } else { 152 | throw err; 153 | } 154 | }); 155 | }; 156 | } 157 | -------------------------------------------------------------------------------- /src/mam.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from "@iota/core/typings/types"; 2 | import * as MamCore from "@iota/mam"; 3 | import * as Bluebird from "bluebird"; 4 | import { composeAPI } from "./composeAPI"; 5 | import { loadBalancer } from "./internals"; 6 | import { FailMode } from "./models/failMode"; 7 | import { LoadBalancerSettings } from "./models/loadBalancerSettings"; 8 | import { SuccessMode } from "./models/successMode"; 9 | 10 | /** 11 | * Wrapper for Mam with load balancing 12 | */ 13 | export class Mam { 14 | /** 15 | * The load balancer settings to use. 16 | */ 17 | private static loadBalancerSettings: LoadBalancerSettings; 18 | 19 | /** 20 | * Initialisation function which returns a state object 21 | * @param settings Settings for the load balancer. 22 | * @param seed The seed to initialise with. 23 | * @param security The security level, defaults to 2. 24 | * @returns The mam state. 25 | */ 26 | public static init(settings: LoadBalancerSettings, seed?: string, security: number = 2): MamCore.MamState { 27 | if (!settings) { 28 | throw new Error("You must provider settings"); 29 | } 30 | if (!settings.nodeWalkStrategy) { 31 | throw new Error("The nodeWalkStrategy field must be provided"); 32 | } 33 | settings.mwm = settings.mwm || 9; 34 | settings.depth = settings.depth || 3; 35 | settings.successMode = settings.successMode || SuccessMode.next; 36 | settings.failMode = settings.failMode || FailMode.all; 37 | Mam.loadBalancerSettings = settings; 38 | return MamCore.init({ provider: "" }, seed, security); 39 | } 40 | 41 | /** 42 | * Change the mode for the mam state. 43 | * @param state The current mam state. 44 | * @param mode [public/private/restricted]. 45 | * @param sidekey, required for restricted mode. 46 | * @returns Updated state object to be used with future actions. 47 | */ 48 | public static changeMode(state: MamCore.MamState, mode: MamCore.MamMode, sidekey?: string): MamCore.MamState { 49 | return MamCore.changeMode(state, mode, sidekey); 50 | } 51 | 52 | /** 53 | * Get the root from the mam state. 54 | * @param state The mam state. 55 | * @returns The root. 56 | */ 57 | public static getRoot(state: MamCore.MamState): string { 58 | return MamCore.getRoot(state); 59 | } 60 | 61 | /** 62 | * Add a subscription to your state object 63 | * @param state The state object to add the subscription to. 64 | * @param channelRoot The root of the channel to subscribe to. 65 | * @param channelMode Can be `public`, `private` or `restricted`. 66 | * @param channelKey Optional, the key of the channel to subscribe to. 67 | * @returns Updated state object to be used with future actions. 68 | */ 69 | public static subscribe(state: MamCore.MamState, channelRoot: string, channelMode: MamCore.MamMode, channelKey?: string): MamCore.MamState { 70 | return MamCore.subscribe(state, channelRoot, channelMode, channelKey); 71 | } 72 | 73 | /** 74 | * Listen for new message on the channel. 75 | * @param channel The channel to listen on. 76 | * @param callback The callback to receive any messages, 77 | */ 78 | public static listen(channel: MamCore.MamSubscribedChannel, callback: (messages: string[]) => void): void { 79 | return MamCore.listen(channel, callback); 80 | } 81 | 82 | /** 83 | * Creates a MAM message payload from a state object. 84 | * @param state The current mam state. 85 | * @param message Tryte encoded string. 86 | * @returns An object containing the payload and updated state. 87 | */ 88 | public static create(state: MamCore.MamState, message: string): MamCore.MamMessage { 89 | return MamCore.create(state, message); 90 | } 91 | 92 | /** 93 | * Decode a message. 94 | * @param payload The payload of the message. 95 | * @param sideKey The sideKey used in the message. 96 | * @param root The root used for the message. 97 | * @returns The decoded payload. 98 | */ 99 | public static decode(payload: string, sideKey: string, root: string): string { 100 | return MamCore.decode(payload, sideKey, root); 101 | } 102 | 103 | /** 104 | * Fetch the messages asynchronously. 105 | * @param root The root key to use. 106 | * @param mode The mode of the channel. 107 | * @param sideKey The sideKey used in the messages, only required for restricted. 108 | * @param callback Optional callback to receive each payload. 109 | * @param limit Limit the number of messages that are fetched. 110 | * @returns The nextRoot and the messages if no callback was supplied, or an Error. 111 | */ 112 | public static async fetch(root: string, mode: MamCore.MamMode, sideKey?: string, callback?: (payload: string) => void, limit?: number): Promise<{ 113 | /** 114 | * The root for the next message. 115 | */ 116 | nextRoot: string; 117 | /** 118 | * All the message payloads. 119 | */ 120 | messages?: string[]; 121 | } | Error> { 122 | return loadBalancer( 123 | Mam.loadBalancerSettings, 124 | (node) => { 125 | MamCore.setIOTA(node.provider); 126 | MamCore.setAttachToTangle(node.attachToTangle || Mam.loadBalancerSettings.attachToTangle); 127 | }, 128 | () => new Bluebird((resolve, reject) => { 129 | MamCore.fetch(root, mode, sideKey, callback, limit) 130 | .then(resolve) 131 | .catch(reject); 132 | })); 133 | } 134 | 135 | /** 136 | * Fetch a single message asynchronously. 137 | * @param root The root key to use. 138 | * @param mode The mode of the channel. 139 | * @param sideKey The sideKey used in the messages. 140 | * @returns The nextRoot and the payload, or an Error. 141 | */ 142 | public static async fetchSingle(root: string, mode: MamCore.MamMode, sideKey?: string): Promise<{ 143 | /** 144 | * The root for the next message. 145 | */ 146 | nextRoot: string; 147 | /** 148 | * The payload for the message. 149 | */ 150 | payload?: string; 151 | } | Error> { 152 | const response = await Mam.fetch(root, mode, sideKey, undefined, 1); 153 | return response instanceof Error ? response : { 154 | payload: response.messages && response.messages.length === 1 ? response.messages[0] : undefined, 155 | nextRoot: response.nextRoot 156 | }; 157 | } 158 | 159 | /** 160 | * Attach the mam trytes to the tangle. 161 | * @param trytes The trytes to attach. 162 | * @param root The root to attach them to. 163 | * @param depth The depth to attach them with, defaults to 3. 164 | * @param mwm The minimum weight magnitude to attach with, defaults to 9 for devnet, 14 required for mainnet. 165 | * @param tag Trytes to tag the message with. 166 | * @returns The transaction objects. 167 | */ 168 | public static async attach(trytes: string, root: string, depth?: number, mwm?: number, tag?: string): Promise> { 169 | const { prepareTransfers, sendTrytes } = composeAPI(Mam.loadBalancerSettings); 170 | 171 | const response = await prepareTransfers( 172 | "9".repeat(81), [ 173 | { 174 | address: root, 175 | value: 0, 176 | message: trytes, 177 | tag: tag 178 | } 179 | ]); 180 | 181 | return sendTrytes(response, depth || 0, mwm || 0); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/models/failMode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Fail modes for the load balancer. 3 | */ 4 | export enum FailMode { 5 | /** 6 | * Try single node only, failure throws exception. 7 | */ 8 | single = "single", 9 | /** 10 | * Try all nodes until one succeeds, on all failing throws combined exception. 11 | */ 12 | all = "all" 13 | } 14 | -------------------------------------------------------------------------------- /src/models/loadBalancerSettings.ts: -------------------------------------------------------------------------------- 1 | import { AttachToTangle } from "@iota/core"; 2 | import { FailMode } from "./failMode"; 3 | import { NodeConfiguration } from "./nodeConfiguration"; 4 | import { NodeWalkStrategy } from "./nodeWalkStrategy"; 5 | import { SuccessMode } from "./successMode"; 6 | 7 | /** 8 | * Settings to use for the load balancer. 9 | */ 10 | export class LoadBalancerSettings { 11 | /** 12 | * The strategy to use for node selection. 13 | */ 14 | public nodeWalkStrategy!: NodeWalkStrategy; 15 | 16 | /** 17 | * How should we use the nodes on a successful request, defaults to next. 18 | */ 19 | public successMode?: SuccessMode; 20 | 21 | /** 22 | * How should we use the nodes on a failed request, defaults to all. 23 | */ 24 | public failMode?: FailMode; 25 | 26 | /** 27 | * Should be look for missing data from snapshots. 28 | */ 29 | public snapshotAware?: boolean; 30 | 31 | /** 32 | * The minimum weight magnitude used for attaching, defaults to 9. 33 | */ 34 | public mwm?: number; 35 | 36 | /** 37 | * The depth used for attaching, defaults to 3. 38 | */ 39 | public depth?: number; 40 | 41 | /** 42 | * A timeout to check if a node is non responsive, if not supplied will use default fetch timout. 43 | */ 44 | public timeoutMs?: number; 45 | 46 | /** 47 | * The attach to tangle method. 48 | */ 49 | public attachToTangle?: AttachToTangle; 50 | 51 | /** 52 | * The username for the provider. 53 | */ 54 | public user?: string; 55 | 56 | /** 57 | * The password for the provider. 58 | */ 59 | public password?: string; 60 | 61 | /** 62 | * Callback which is triggered when a new node is about to be used. 63 | * @param node The node configuration that was tried. 64 | */ 65 | public tryNodeCallback?(node: NodeConfiguration): void; 66 | 67 | /** 68 | * Callback which is triggered when a node fails. 69 | * @param node The node configuration that failed. 70 | * @param error The error the node failed with. 71 | */ 72 | public failNodeCallback?(node: NodeConfiguration, error: Error): void; 73 | } 74 | -------------------------------------------------------------------------------- /src/models/nodeConfiguration.ts: -------------------------------------------------------------------------------- 1 | import { AttachToTangle } from "@iota/core"; 2 | 3 | /** 4 | * The configuration for a single node. 5 | */ 6 | export class NodeConfiguration { 7 | /** 8 | * The provider url for the node. 9 | */ 10 | public provider!: string; 11 | 12 | /** 13 | * The minimum weight magnitude used for attaching. 14 | */ 15 | public mwm?: number; 16 | 17 | /** 18 | * The depth used for attaching. 19 | */ 20 | public depth?: number; 21 | 22 | /** 23 | * Timeout for this specific node, defaults to using the main load balancer setting. 24 | */ 25 | public timeoutMs?: number; 26 | 27 | /** 28 | * The attach to tangle method, defaults to using main load balancer setting. 29 | */ 30 | public attachToTangle?: AttachToTangle; 31 | 32 | /** 33 | * The username for the provider. 34 | */ 35 | public user?: string; 36 | 37 | /** 38 | * The password for the provider. 39 | */ 40 | public password?: string; 41 | } 42 | -------------------------------------------------------------------------------- /src/models/nodeWalkStrategy.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfiguration } from "./nodeConfiguration"; 2 | 3 | /** 4 | * A strategy for choosing nodes. 5 | */ 6 | export interface NodeWalkStrategy { 7 | /** 8 | * The total number of usable nodes for the strategy. 9 | * @returns The total number of usable nodes. 10 | */ 11 | totalUsable(): number; 12 | 13 | /** 14 | * Get the current node from the strategy. 15 | * @returns A node configuration from the strategy. 16 | */ 17 | current(): NodeConfiguration; 18 | 19 | /** 20 | * Move to the next node in the strategy. 21 | * @param retainOrder Retain the ordering if resetting the list. 22 | */ 23 | next(retainOrder: boolean): void; 24 | 25 | /** 26 | * Blacklist the current node, so it doesn't get used again. 27 | */ 28 | blacklist(): void; 29 | } 30 | -------------------------------------------------------------------------------- /src/models/successMode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Success modes for the load balancer. 3 | */ 4 | export enum SuccessMode { 5 | /** 6 | * Keep the node if it was successful. 7 | */ 8 | keep = "keep", 9 | /** 10 | * Move to the next node even if it was successful. 11 | */ 12 | next = "next" 13 | } 14 | -------------------------------------------------------------------------------- /src/walkStrategies/baseWalkStrategy.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfiguration } from "../models/nodeConfiguration"; 2 | import { NodeWalkStrategy } from "../models/nodeWalkStrategy"; 3 | 4 | /** 5 | * Common features for the node strategies. 6 | * @private 7 | */ 8 | export abstract class BaseWalkStrategy implements NodeWalkStrategy { 9 | /** 10 | * The list of all configured nodes. 11 | */ 12 | private readonly _allNodes: NodeConfiguration[]; 13 | 14 | /** 15 | * The number of failures before a node is blacklisted. 16 | */ 17 | private readonly _blacklistLimit?: number; 18 | 19 | /** 20 | * The list of usable nodes to iterate through. 21 | */ 22 | private _usableNodes: NodeConfiguration[]; 23 | 24 | /** 25 | * The nodes that have been blacklisted. 26 | */ 27 | private _blacklistNodes: { [id: string]: number }; 28 | 29 | /** 30 | * Create a new instance of BaseWalkStrategy. 31 | * @param nodes The nodes to iterate through. 32 | * @param blacklistLimit The number of failures before a node is blacklisted. 33 | */ 34 | constructor(nodes: NodeConfiguration[], blacklistLimit?: number) { 35 | if (!nodes || nodes.length === 0) { 36 | throw new Error("You must supply at least one node to the strategy"); 37 | } 38 | this._allNodes = nodes; 39 | this._usableNodes = nodes.slice(); 40 | this._blacklistLimit = blacklistLimit; 41 | this._blacklistNodes = {}; 42 | } 43 | 44 | /** 45 | * The total number of nodes configured for the strategy. 46 | * @returns The total number of nodes. 47 | */ 48 | public totalUsable(): number { 49 | return this._usableNodes.length; 50 | } 51 | 52 | /** 53 | * Get the current node from the strategy. 54 | * @returns A node configuration from the strategy. 55 | */ 56 | public abstract current(): NodeConfiguration; 57 | 58 | /** 59 | * Move to the next node in the strategy. 60 | * @param retainOrder Retain the ordering if resetting the list. 61 | */ 62 | public abstract next(retainOrder: boolean): void; 63 | 64 | /** 65 | * Blacklist the current node, so it doesn't get used again once limit is reached. 66 | */ 67 | public blacklist(): void { 68 | if (this._blacklistLimit) { 69 | const current = this.current(); 70 | if (current) { 71 | if (!this._blacklistNodes[current.provider]) { 72 | this._blacklistNodes[current.provider] = 1; 73 | } else { 74 | this._blacklistNodes[current.provider]++; 75 | } 76 | if (this._blacklistNodes[current.provider] >= this._blacklistLimit) { 77 | const idx = this._usableNodes.indexOf(current); 78 | if (idx >= 0) { 79 | this._usableNodes.splice(idx, 1); 80 | } 81 | } 82 | 83 | // If there are no usable nodes left then reset the blacklists 84 | if (this._usableNodes.length === 0) { 85 | this._blacklistNodes = {}; 86 | this._usableNodes = this._allNodes.slice(); 87 | } 88 | } 89 | } 90 | } 91 | 92 | /** 93 | * Get the list of nodes that have not been blacklisted. 94 | * @returns The non blacklisted nodes. 95 | */ 96 | protected getUsableNodes(): NodeConfiguration[] { 97 | return this._usableNodes; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/walkStrategies/linearWalkStrategy.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfiguration } from "../models/nodeConfiguration"; 2 | import { BaseWalkStrategy } from "./baseWalkStrategy"; 3 | 4 | /** 5 | * Node choice strategy which just iterates through the list of nodes. 6 | */ 7 | export class LinearWalkStrategy extends BaseWalkStrategy { 8 | /** 9 | * The current node to use. 10 | */ 11 | private _currentIndex: number; 12 | 13 | /** 14 | * Create a new instance of LinearWalkStrategy. 15 | * @param nodes The nodes to randomly pick from. 16 | * @param blacklistLimit The number of failures before a node is blacklisted. 17 | */ 18 | constructor(nodes: NodeConfiguration[], blacklistLimit?: number) { 19 | super(nodes, blacklistLimit); 20 | this._currentIndex = 0; 21 | } 22 | 23 | /** 24 | * Get the current node from the strategy. 25 | * @returns A node configuration from the strategy. 26 | */ 27 | public current(): NodeConfiguration { 28 | return this.getUsableNodes()[this._currentIndex]; 29 | } 30 | 31 | /** 32 | * Move to the next node in the strategy. 33 | * @param retainOrder Retain the ordering if resetting the list. 34 | */ 35 | public next(retainOrder: boolean): void { 36 | this._currentIndex = (this._currentIndex + 1) % this.getUsableNodes().length; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/walkStrategies/randomWalkStrategy.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfiguration } from "../models/nodeConfiguration"; 2 | import { BaseWalkStrategy } from "./baseWalkStrategy"; 3 | 4 | /** 5 | * Node choice strategy which randomly picks from the list of nodes. 6 | */ 7 | export class RandomWalkStrategy extends BaseWalkStrategy { 8 | /** 9 | * The remaining nodes to randomly pick from. 10 | * @internal 11 | */ 12 | private _remainingNodes: NodeConfiguration[]; 13 | 14 | /** 15 | * The current random ordering. 16 | * @internal 17 | */ 18 | private _randomNodes: NodeConfiguration[]; 19 | 20 | /** 21 | * Create a new instance of RandomWalkStategy. 22 | * @param nodes The nodes to randomly pick from. 23 | * @param blacklistLimit The number of failures before a node is blacklisted. 24 | */ 25 | constructor(nodes: NodeConfiguration[], blacklistLimit?: number) { 26 | super(nodes, blacklistLimit); 27 | this._remainingNodes = []; 28 | this._randomNodes = []; 29 | this.populateRemaining(); 30 | } 31 | 32 | /** 33 | * Get the current node from the strategy. 34 | * @returns A node configuration from the strategy. 35 | */ 36 | public current(): NodeConfiguration { 37 | return this._remainingNodes[0]; 38 | } 39 | 40 | /** 41 | * Move to the next node in the strategy. 42 | * @param retainOrder Retain the ordering if resetting the list. 43 | */ 44 | public next(retainOrder: boolean): void { 45 | this._remainingNodes.shift(); 46 | if (this._remainingNodes.length === 0) { 47 | if (retainOrder) { 48 | this._remainingNodes = this._randomNodes.slice(); 49 | } else { 50 | this.populateRemaining(); 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * Populate the remaining array by randomizing the nodes. 57 | * @internal 58 | * @private 59 | */ 60 | private populateRemaining(): void { 61 | this._remainingNodes = super.getUsableNodes().slice(); 62 | for (let i = this._remainingNodes.length - 1; i > 0; i--) { 63 | // tslint:disable-next-line:insecure-random 64 | const j = Math.floor(Math.random() * (i + 1)); 65 | [this._remainingNodes[i], this._remainingNodes[j]] = [this._remainingNodes[j], this._remainingNodes[i]]; 66 | } 67 | this._randomNodes = this._remainingNodes.slice(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/baseWalkStrategy.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfiguration } from "../src/models/nodeConfiguration"; 2 | import { BaseWalkStrategy } from "../src/walkStrategies/baseWalkStrategy"; 3 | 4 | /** 5 | * Dummy walk strategy to test abstract base. 6 | */ 7 | class DummyWalkStrategy extends BaseWalkStrategy { 8 | /** 9 | * Get the current node from the strategy. 10 | * @returns A node configuration from the strategy. 11 | */ 12 | public current(): NodeConfiguration { 13 | return this.getUsableNodes()[0]; 14 | } 15 | 16 | /** 17 | * Move to the next node in the strategy. 18 | * @param retainOrder Retain the ordering if resetting the list. 19 | */ 20 | public next(retainOrder: boolean): void { 21 | } 22 | } 23 | 24 | test("BaseWalkStrategy() fails with no nodes", () => { 25 | expect(() => { 26 | // tslint:disable-next-line:no-unused-expression 27 | new DummyWalkStrategy([]); 28 | }).toThrowError(/at least one node/); 29 | }); 30 | 31 | test("totalUsable() returns node count", () => { 32 | const obj = new DummyWalkStrategy([ 33 | { 34 | provider: "https://localhost", 35 | mwm: 14, 36 | depth: 3 37 | }, 38 | { 39 | provider: "https://localhost", 40 | mwm: 14, 41 | depth: 3 42 | } 43 | ]); 44 | 45 | expect(obj.totalUsable()).toBe(2); 46 | }); 47 | 48 | test("blacklist() can be ignored", () => { 49 | const obj = new DummyWalkStrategy( 50 | [ 51 | { 52 | provider: "https://localhost", 53 | mwm: 14, 54 | depth: 3 55 | }, 56 | { 57 | provider: "https://localhost", 58 | mwm: 14, 59 | depth: 3 60 | } 61 | ]); 62 | 63 | obj.blacklist(); 64 | obj.blacklist(); 65 | obj.blacklist(); 66 | obj.blacklist(); 67 | expect(obj.totalUsable()).toBe(2); 68 | }); 69 | 70 | test("blacklist() can continue using nodes", () => { 71 | const obj = new DummyWalkStrategy( 72 | [ 73 | { 74 | provider: "https://localhost", 75 | mwm: 14, 76 | depth: 3 77 | }, 78 | { 79 | provider: "https://localhost", 80 | mwm: 14, 81 | depth: 3 82 | } 83 | ], 84 | 5); 85 | 86 | obj.blacklist(); 87 | obj.blacklist(); 88 | obj.blacklist(); 89 | obj.blacklist(); 90 | expect(obj.totalUsable()).toBe(2); 91 | }); 92 | 93 | test("blacklist() can stop using single nodes", () => { 94 | const obj = new DummyWalkStrategy( 95 | [ 96 | { 97 | provider: "https://localhost", 98 | mwm: 14, 99 | depth: 3 100 | }, 101 | { 102 | provider: "https://localhost", 103 | mwm: 14, 104 | depth: 3 105 | } 106 | ], 107 | 5); 108 | 109 | obj.blacklist(); 110 | obj.blacklist(); 111 | obj.blacklist(); 112 | obj.blacklist(); 113 | obj.blacklist(); 114 | expect(obj.totalUsable()).toBe(1); 115 | }); 116 | 117 | test("blacklist() can reset", () => { 118 | const obj = new DummyWalkStrategy( 119 | [ 120 | { 121 | provider: "https://localhost", 122 | mwm: 14, 123 | depth: 3 124 | }, 125 | { 126 | provider: "https://localhost", 127 | mwm: 14, 128 | depth: 3 129 | } 130 | ], 131 | 3); 132 | 133 | obj.blacklist(); 134 | obj.blacklist(); 135 | obj.blacklist(); 136 | obj.blacklist(); 137 | obj.blacklist(); 138 | obj.blacklist(); 139 | expect(obj.totalUsable()).toBe(2); 140 | }); 141 | -------------------------------------------------------------------------------- /test/linearWalkStrategy.spec.ts: -------------------------------------------------------------------------------- 1 | import { LinearWalkStrategy } from "../src/walkStrategies/linearWalkStrategy"; 2 | 3 | test("LinearWalkStrategy() fails with no nodes", () => { 4 | expect(() => { 5 | // tslint:disable-next-line:no-unused-expression 6 | new LinearWalkStrategy([]); 7 | }).toThrowError(/at least one node/); 8 | }); 9 | 10 | test("current() and next() iterate through nodes", () => { 11 | const obj = new LinearWalkStrategy([ 12 | { 13 | provider: "https://localhost1", 14 | mwm: 14, 15 | depth: 3 16 | }, 17 | { 18 | provider: "https://localhost2", 19 | mwm: 14, 20 | depth: 3 21 | } 22 | ]); 23 | 24 | expect(obj.current().provider).toBe("https://localhost1"); 25 | obj.next(true); 26 | expect(obj.current().provider).toBe("https://localhost2"); 27 | }); 28 | -------------------------------------------------------------------------------- /test/randomWalkStrategy.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfiguration } from "../src/models/nodeConfiguration"; 2 | import { RandomWalkStrategy } from "../src/walkStrategies/randomWalkStrategy"; 3 | 4 | test("RandomWalkStrategy() fails with no nodes", () => { 5 | expect(() => { 6 | // tslint:disable-next-line:no-unused-expression 7 | new RandomWalkStrategy([]); 8 | }).toThrowError(/at least one node/); 9 | }); 10 | 11 | test("current() and next() randomly pick nodes", () => { 12 | const nodes: NodeConfiguration[] = []; 13 | for (let i = 0; i < 10; i++) { 14 | nodes.push({ 15 | provider: `${i}`, 16 | mwm: 14, 17 | depth: 3 18 | }); 19 | } 20 | const obj = new RandomWalkStrategy(nodes); 21 | 22 | const visited: { [id: string]: boolean } = {}; 23 | 24 | for (let i = 0; i < 200; i++) { 25 | visited[obj.current().provider] = true; 26 | obj.next(true); 27 | } 28 | 29 | for (let i = 0; i < nodes.length; i++) { 30 | expect(visited[nodes[i].provider]).toBe(true); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "strict": true, 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "outDir": "./es", 8 | "inlineSourceMap": true 9 | }, 10 | "include": [ 11 | "src" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-microsoft-contrib/recommended", 5 | "tslint-eslint-rules" 6 | ], 7 | "linterOptions": { 8 | "exclude": [ 9 | "node_modules", 10 | "**/*.d.ts", 11 | "**/*.json" 12 | ] 13 | }, 14 | "rules": { 15 | "insecure-random": true, 16 | "no-banned-terms": true, 17 | "no-cookies": true, 18 | "no-delete-expression": true, 19 | "no-disable-auto-sanitization": true, 20 | "no-document-domain": true, 21 | "no-document-write": true, 22 | "no-eval": true, 23 | "no-exec-script": true, 24 | "no-http-string": [ 25 | false 26 | ], 27 | "no-inner-html": true, 28 | "no-octal-literal": true, 29 | "no-reserved-keywords": false, 30 | "no-string-based-set-immediate": true, 31 | "no-string-based-set-interval": true, 32 | "no-string-based-set-timeout": true, 33 | "non-literal-require": true, 34 | "possible-timing-attack": true, 35 | "react-anchor-blank-noopener": true, 36 | "react-iframe-missing-sandbox": true, 37 | "react-no-dangerous-html": true, 38 | "await-promise": [true, "Bluebird"], 39 | "forin": false, 40 | "jquery-deferred-must-complete": true, 41 | "label-position": true, 42 | "match-default-export-name": false, 43 | "mocha-avoid-only": true, 44 | "mocha-no-side-effect-code": false, 45 | "no-any": false, 46 | "no-arg": true, 47 | "no-backbone-get-set-outside-model": false, 48 | "no-bitwise": false, 49 | "no-conditional-assignment": true, 50 | "no-console": [ 51 | true, 52 | "debug", 53 | "info", 54 | "log", 55 | "time", 56 | "timeEnd", 57 | "trace" 58 | ], 59 | "no-constant-condition": true, 60 | "no-control-regex": false, 61 | "no-debugger": true, 62 | "no-duplicate-case": false, 63 | "no-duplicate-switch-case": true, 64 | "no-duplicate-super": true, 65 | "no-duplicate-variable": true, 66 | "no-empty": false, 67 | "no-floating-promises": [true, "Bluebird"], 68 | "no-for-in-array": true, 69 | "no-import-side-effect": false, 70 | "no-increment-decrement": false, 71 | "no-invalid-regexp": true, 72 | "no-invalid-template-strings": true, 73 | "no-invalid-this": true, 74 | "no-jquery-raw-elements": true, 75 | "no-misused-new": true, 76 | "no-non-null-assertion": true, 77 | "no-reference-import": true, 78 | "no-regex-spaces": true, 79 | "no-sparse-arrays": true, 80 | "no-stateless-class": false, 81 | "no-string-literal": true, 82 | "no-string-throw": true, 83 | "no-unnecessary-callback-wrapper": true, 84 | "no-unnecessary-initializer": true, 85 | "no-unnecessary-override": true, 86 | "no-unsafe-any": false, 87 | "no-unsafe-finally": true, 88 | "no-unused-expression": true, 89 | "no-with-statement": true, 90 | "promise-function-async": true, 91 | "promise-must-complete": [true, "Bluebird"], 92 | "radix": true, 93 | "react-this-binding-issue": false, 94 | "react-unused-props-and-state": true, 95 | "restrict-plus-operands": true, 96 | "strict-boolean-expressions": false, 97 | "switch-default": false, 98 | "triple-equals": [ 99 | true, 100 | "allow-null-check" 101 | ], 102 | "use-isnan": true, 103 | "use-named-parameter": true, 104 | "adjacent-overload-signatures": true, 105 | "array-type": [ 106 | true, 107 | "array" 108 | ], 109 | "arrow-parens": false, 110 | "callable-types": true, 111 | "chai-prefer-contains-to-index-of": true, 112 | "chai-vague-errors": false, 113 | "class-name": true, 114 | "comment-format": [ 115 | true 116 | ], 117 | "completed-docs": true, 118 | "export-name": false, 119 | "function-name": [ 120 | true, 121 | { 122 | "method-regex": "^[a-z][\\w\\d]+$", 123 | "private-method-regex": "^[a-z][\\w\\d]+$", 124 | "protected-method-regex": "^[a-z][\\w\\d]+$", 125 | "static-method-regex": "^[a-z][\\w\\d]+$", 126 | "function-regex": "^[a-z][\\w\\d]+$" 127 | } 128 | ], 129 | "import-name": false, 130 | "interface-name": [ 131 | false 132 | ], 133 | "jsdoc-format": true, 134 | "max-classes-per-file": [ 135 | true, 136 | 3 137 | ], 138 | "max-file-line-count": [ 139 | true 140 | ], 141 | "max-func-body-length": [ 142 | false 143 | ], 144 | "max-line-length": [ 145 | true, 146 | 200 147 | ], 148 | "member-access": true, 149 | "member-ordering": [ 150 | true, 151 | { 152 | "order": "fields-first" 153 | } 154 | ], 155 | "mocha-unneeded-done": true, 156 | "newline-per-chained-call": false, 157 | "new-parens": true, 158 | "no-construct": true, 159 | "no-default-export": false, 160 | "no-empty-interface": true, 161 | "no-for-in": false, 162 | "no-function-expression": true, 163 | "no-inferrable-types": false, 164 | "no-multiline-string": false, 165 | "no-null-keyword": false, 166 | "no-parameter-properties": true, 167 | "no-relative-imports": false, 168 | "no-require-imports": true, 169 | "no-redundant-jsdoc": false, 170 | "no-shadowed-variable": true, 171 | "no-suspicious-comment": true, 172 | "no-typeof-undefined": false, 173 | "no-unnecessary-field-initialization": true, 174 | "no-unnecessary-local-variable": true, 175 | "no-unnecessary-qualifier": true, 176 | "no-unsupported-browser-code": true, 177 | "no-useless-files": true, 178 | "no-var-keyword": true, 179 | "no-var-requires": true, 180 | "no-void-expression": false, 181 | "object-literal-sort-keys": false, 182 | "one-variable-per-declaration": [ 183 | true 184 | ], 185 | "only-arrow-functions": [ 186 | false 187 | ], 188 | "ordered-imports": [ 189 | true 190 | ], 191 | "prefer-array-literal": false, 192 | "prefer-const": true, 193 | "prefer-for-of": false, 194 | "prefer-method-signature": true, 195 | "prefer-template": true, 196 | "return-undefined": false, 197 | "typedef": [ 198 | true, 199 | "call-signature", 200 | "parameter", 201 | "property-declaration", 202 | "member-variable-declaration" 203 | ], 204 | "underscore-consistent-invocation": true, 205 | "unified-signatures": true, 206 | "variable-name": [ 207 | true, 208 | "allow-leading-underscore" 209 | ], 210 | "align": [ 211 | true, 212 | "parameters", 213 | "arguments", 214 | "statements" 215 | ], 216 | "curly": true, 217 | "eofline": true, 218 | "import-spacing": true, 219 | "indent": [ 220 | true, 221 | "spaces" 222 | ], 223 | "linebreak-style": [ 224 | true 225 | ], 226 | "newline-before-return": false, 227 | "no-consecutive-blank-lines": true, 228 | "no-empty-line-after-opening-brace": false, 229 | "no-single-line-block-comment": false, 230 | "no-trailing-whitespace": true, 231 | "no-unnecessary-semicolons": true, 232 | "object-literal-key-quotes": [ 233 | true, 234 | "as-needed" 235 | ], 236 | "one-line": [ 237 | true, 238 | "check-open-brace", 239 | "check-catch", 240 | "check-else", 241 | "check-whitespace" 242 | ], 243 | "quotemark": [ 244 | true, 245 | "double" 246 | ], 247 | "react-tsx-curly-spacing": false, 248 | "semicolon": [ 249 | true, 250 | "always" 251 | ], 252 | "trailing-comma": [ 253 | true, 254 | { 255 | "singleline": "never", 256 | "multiline": "never" 257 | } 258 | ], 259 | "typedef-whitespace": [ 260 | true, 261 | { 262 | "call-signature": "nospace", 263 | "index-signature": "nospace", 264 | "parameter": "nospace", 265 | "property-declaration": "nospace", 266 | "variable-declaration": "nospace" 267 | }, 268 | { 269 | "call-signature": "onespace", 270 | "index-signature": "onespace", 271 | "parameter": "onespace", 272 | "property-declaration": "onespace", 273 | "variable-declaration": "onespace" 274 | } 275 | ], 276 | "whitespace": [ 277 | true, 278 | "check-branch", 279 | "check-decl", 280 | "check-operator", 281 | "check-separator", 282 | "check-type" 283 | ], 284 | "ban": false, 285 | "ban-types": true, 286 | "cyclomatic-complexity": [ 287 | false 288 | ], 289 | "file-header": [ 290 | false 291 | ], 292 | "import-blacklist": false, 293 | "interface-over-type-literal": false, 294 | "no-angle-bracket-type-assertion": false, 295 | "no-inferred-empty-object-type": false, 296 | "no-internal-module": false, 297 | "no-magic-numbers": false, 298 | "no-mergeable-namespace": false, 299 | "no-namespace": false, 300 | "no-reference": true, 301 | "no-unexternalized-strings": false, 302 | "object-literal-shorthand": false, 303 | "prefer-type-cast": true, 304 | "space-before-function-paren": false, 305 | "prefer-switch": false, 306 | "prefer-function-over-method": false, 307 | "prefer-conditional-expression": false, 308 | "strict-type-predicates": false, 309 | "no-submodule-imports": false, 310 | "no-object-literal-type-assertion": false, 311 | "no-unbound-method": false, 312 | "no-unnecessary-class": false, 313 | "no-implicit-dependencies": false, 314 | "missing-optional-annotation": false, 315 | "no-duplicate-parameter-names": false, 316 | "no-empty-interfaces": false, 317 | "no-missing-visibility-modifiers": false, 318 | "no-multiple-var-decl": false, 319 | "no-var-self": false, 320 | "valid-typeof": false, 321 | "no-switch-case-fall-through": false, 322 | "typeof-compare": false, 323 | "no-dynamic-delete": false, 324 | "binary-expression-operand-order": false, 325 | "valid-jsdoc": [ 326 | true, 327 | { 328 | "requireParamType": false, 329 | "prefer": { 330 | "return": "returns" 331 | }, 332 | "requireReturn": false, 333 | "requireReturnType": false, 334 | "requireParamDescription": true, 335 | "requireReturnDescription": true 336 | } 337 | ], 338 | "file-name-casing": false, 339 | "no-unused-variable": false, 340 | "jsx-no-multiline-js": false, 341 | "jsx-no-lambda": false, 342 | "prefer-readonly": true, 343 | "react-a11y-anchors": false, 344 | "react-a11y-role-has-required-aria-props": false, 345 | "use-simple-attributes": false, 346 | "react-a11y-no-onchange": false, 347 | "react-a11y-input-elements": false 348 | } 349 | } -------------------------------------------------------------------------------- /typings/composeAPI.d.ts: -------------------------------------------------------------------------------- 1 | import { API } from "@iota/core"; 2 | import { LoadBalancerSettings } from "./models/loadBalancerSettings"; 3 | /** 4 | * Create a new instance of the API wrapped with load balancing support. 5 | * @param settings The load balancer settings. 6 | * @returns The api. 7 | */ 8 | export declare function composeAPI(settings: LoadBalancerSettings): API; 9 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./composeAPI"; 2 | export * from "./mam"; 3 | export * from "./models/failMode"; 4 | export * from "./models/loadBalancerSettings"; 5 | export * from "./models/nodeConfiguration"; 6 | export * from "./models/nodeWalkStrategy"; 7 | export * from "./models/successMode"; 8 | export * from "./walkStrategies/linearWalkStrategy"; 9 | export * from "./walkStrategies/randomWalkStrategy"; 10 | -------------------------------------------------------------------------------- /typings/internals.d.ts: -------------------------------------------------------------------------------- 1 | import { API } from "@iota/core"; 2 | import * as Bluebird from "bluebird"; 3 | import { LoadBalancerSettings } from "./models/loadBalancerSettings"; 4 | import { NodeConfiguration } from "./models/nodeConfiguration"; 5 | /** 6 | * Create a new instance of the API. 7 | * @param settings The load balancer settings. 8 | * @param updateProvider Update the provider in the calling context. 9 | * @param methodPromise The method to call. 10 | * @param validateResult Let the caller validate the result. 11 | * @returns The api. 12 | * @private 13 | */ 14 | export declare function loadBalancer(settings: LoadBalancerSettings, updateProvider: (node: NodeConfiguration) => void, methodPromise: (node: NodeConfiguration) => Bluebird, validateResult?: (res: any) => string): Promise; 15 | /** 16 | * Wrap a method and handle either callback or async result. 17 | * @param settings The load balancer settings. 18 | * @param api The composed api. 19 | * @param method The method to wrap. 20 | * @param methodName The name of the method. 21 | * @returns The wrapped method. 22 | * @private 23 | */ 24 | export declare function wrapMethodCallbackOrAsync(settings: LoadBalancerSettings, api: API, method: (...params: any) => Bluebird, methodName: string): () => any; 25 | -------------------------------------------------------------------------------- /typings/mam.d.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from "@iota/core/typings/types"; 2 | import * as MamCore from "@iota/mam"; 3 | import { LoadBalancerSettings } from "./models/loadBalancerSettings"; 4 | /** 5 | * Wrapper for Mam with load balancing 6 | */ 7 | export declare class Mam { 8 | /** 9 | * The load balancer settings to use. 10 | */ 11 | private static loadBalancerSettings; 12 | /** 13 | * Initialisation function which returns a state object 14 | * @param settings Settings for the load balancer. 15 | * @param seed The seed to initialise with. 16 | * @param security The security level, defaults to 2. 17 | * @returns The mam state. 18 | */ 19 | static init(settings: LoadBalancerSettings, seed?: string, security?: number): MamCore.MamState; 20 | /** 21 | * Change the mode for the mam state. 22 | * @param state The current mam state. 23 | * @param mode [public/private/restricted]. 24 | * @param sidekey, required for restricted mode. 25 | * @returns Updated state object to be used with future actions. 26 | */ 27 | static changeMode(state: MamCore.MamState, mode: MamCore.MamMode, sidekey?: string): MamCore.MamState; 28 | /** 29 | * Get the root from the mam state. 30 | * @param state The mam state. 31 | * @returns The root. 32 | */ 33 | static getRoot(state: MamCore.MamState): string; 34 | /** 35 | * Add a subscription to your state object 36 | * @param state The state object to add the subscription to. 37 | * @param channelRoot The root of the channel to subscribe to. 38 | * @param channelMode Can be `public`, `private` or `restricted`. 39 | * @param channelKey Optional, the key of the channel to subscribe to. 40 | * @returns Updated state object to be used with future actions. 41 | */ 42 | static subscribe(state: MamCore.MamState, channelRoot: string, channelMode: MamCore.MamMode, channelKey?: string): MamCore.MamState; 43 | /** 44 | * Listen for new message on the channel. 45 | * @param channel The channel to listen on. 46 | * @param callback The callback to receive any messages, 47 | */ 48 | static listen(channel: MamCore.MamSubscribedChannel, callback: (messages: string[]) => void): void; 49 | /** 50 | * Creates a MAM message payload from a state object. 51 | * @param state The current mam state. 52 | * @param message Tryte encoded string. 53 | * @returns An object containing the payload and updated state. 54 | */ 55 | static create(state: MamCore.MamState, message: string): MamCore.MamMessage; 56 | /** 57 | * Decode a message. 58 | * @param payload The payload of the message. 59 | * @param sideKey The sideKey used in the message. 60 | * @param root The root used for the message. 61 | * @returns The decoded payload. 62 | */ 63 | static decode(payload: string, sideKey: string, root: string): string; 64 | /** 65 | * Fetch the messages asynchronously. 66 | * @param root The root key to use. 67 | * @param mode The mode of the channel. 68 | * @param sideKey The sideKey used in the messages, only required for restricted. 69 | * @param callback Optional callback to receive each payload. 70 | * @param limit Limit the number of messages that are fetched. 71 | * @returns The nextRoot and the messages if no callback was supplied, or an Error. 72 | */ 73 | static fetch(root: string, mode: MamCore.MamMode, sideKey?: string, callback?: (payload: string) => void, limit?: number): Promise<{ 74 | /** 75 | * The root for the next message. 76 | */ 77 | nextRoot: string; 78 | /** 79 | * All the message payloads. 80 | */ 81 | messages?: string[]; 82 | } | Error>; 83 | /** 84 | * Fetch a single message asynchronously. 85 | * @param root The root key to use. 86 | * @param mode The mode of the channel. 87 | * @param sideKey The sideKey used in the messages. 88 | * @returns The nextRoot and the payload, or an Error. 89 | */ 90 | static fetchSingle(root: string, mode: MamCore.MamMode, sideKey?: string): Promise<{ 91 | /** 92 | * The root for the next message. 93 | */ 94 | nextRoot: string; 95 | /** 96 | * The payload for the message. 97 | */ 98 | payload?: string; 99 | } | Error>; 100 | /** 101 | * Attach the mam trytes to the tangle. 102 | * @param trytes The trytes to attach. 103 | * @param root The root to attach them to. 104 | * @param depth The depth to attach them with, defaults to 3. 105 | * @param mwm The minimum weight magnitude to attach with, defaults to 9 for devnet, 14 required for mainnet. 106 | * @param tag Trytes to tag the message with. 107 | * @returns The transaction objects. 108 | */ 109 | static attach(trytes: string, root: string, depth?: number, mwm?: number, tag?: string): Promise>; 110 | } 111 | -------------------------------------------------------------------------------- /typings/models/failMode.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Fail modes for the load balancer. 3 | */ 4 | export declare enum FailMode { 5 | /** 6 | * Try single node only, failure throws exception. 7 | */ 8 | single = "single", 9 | /** 10 | * Try all nodes until one succeeds, on all failing throws combined exception. 11 | */ 12 | all = "all" 13 | } 14 | -------------------------------------------------------------------------------- /typings/models/loadBalancerSettings.d.ts: -------------------------------------------------------------------------------- 1 | import { AttachToTangle } from "@iota/core"; 2 | import { FailMode } from "./failMode"; 3 | import { NodeConfiguration } from "./nodeConfiguration"; 4 | import { NodeWalkStrategy } from "./nodeWalkStrategy"; 5 | import { SuccessMode } from "./successMode"; 6 | /** 7 | * Settings to use for the load balancer. 8 | */ 9 | export declare class LoadBalancerSettings { 10 | /** 11 | * The strategy to use for node selection. 12 | */ 13 | nodeWalkStrategy: NodeWalkStrategy; 14 | /** 15 | * How should we use the nodes on a successful request, defaults to next. 16 | */ 17 | successMode?: SuccessMode; 18 | /** 19 | * How should we use the nodes on a failed request, defaults to all. 20 | */ 21 | failMode?: FailMode; 22 | /** 23 | * Should be look for missing data from snapshots. 24 | */ 25 | snapshotAware?: boolean; 26 | /** 27 | * The minimum weight magnitude used for attaching, defaults to 9. 28 | */ 29 | mwm?: number; 30 | /** 31 | * The depth used for attaching, defaults to 3. 32 | */ 33 | depth?: number; 34 | /** 35 | * A timeout to check if a node is non responsive, if not supplied will use default fetch timout. 36 | */ 37 | timeoutMs?: number; 38 | /** 39 | * The attach to tangle method. 40 | */ 41 | attachToTangle?: AttachToTangle; 42 | /** 43 | * The username for the provider. 44 | */ 45 | user?: string; 46 | /** 47 | * The password for the provider. 48 | */ 49 | password?: string; 50 | /** 51 | * Callback which is triggered when a new node is about to be used. 52 | * @param node The node configuration that was tried. 53 | */ 54 | tryNodeCallback?(node: NodeConfiguration): void; 55 | /** 56 | * Callback which is triggered when a node fails. 57 | * @param node The node configuration that failed. 58 | * @param error The error the node failed with. 59 | */ 60 | failNodeCallback?(node: NodeConfiguration, error: Error): void; 61 | } 62 | -------------------------------------------------------------------------------- /typings/models/nodeConfiguration.d.ts: -------------------------------------------------------------------------------- 1 | import { AttachToTangle } from "@iota/core"; 2 | /** 3 | * The configuration for a single node. 4 | */ 5 | export declare class NodeConfiguration { 6 | /** 7 | * The provider url for the node. 8 | */ 9 | provider: string; 10 | /** 11 | * The minimum weight magnitude used for attaching. 12 | */ 13 | mwm?: number; 14 | /** 15 | * The depth used for attaching. 16 | */ 17 | depth?: number; 18 | /** 19 | * Timeout for this specific node, defaults to using the main load balancer setting. 20 | */ 21 | timeoutMs?: number; 22 | /** 23 | * The attach to tangle method, defaults to using main load balancer setting. 24 | */ 25 | attachToTangle?: AttachToTangle; 26 | /** 27 | * The username for the provider. 28 | */ 29 | user?: string; 30 | /** 31 | * The password for the provider. 32 | */ 33 | password?: string; 34 | } 35 | -------------------------------------------------------------------------------- /typings/models/nodeWalkStrategy.d.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfiguration } from "./nodeConfiguration"; 2 | /** 3 | * A strategy for choosing nodes. 4 | */ 5 | export interface NodeWalkStrategy { 6 | /** 7 | * The total number of usable nodes for the strategy. 8 | * @returns The total number of usable nodes. 9 | */ 10 | totalUsable(): number; 11 | /** 12 | * Get the current node from the strategy. 13 | * @returns A node configuration from the strategy. 14 | */ 15 | current(): NodeConfiguration; 16 | /** 17 | * Move to the next node in the strategy. 18 | * @param retainOrder Retain the ordering if resetting the list. 19 | */ 20 | next(retainOrder: boolean): void; 21 | /** 22 | * Blacklist the current node, so it doesn't get used again. 23 | */ 24 | blacklist(): void; 25 | } 26 | -------------------------------------------------------------------------------- /typings/models/successMode.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Success modes for the load balancer. 3 | */ 4 | export declare enum SuccessMode { 5 | /** 6 | * Keep the node if it was successful. 7 | */ 8 | keep = "keep", 9 | /** 10 | * Move to the next node even if it was successful. 11 | */ 12 | next = "next" 13 | } 14 | -------------------------------------------------------------------------------- /typings/walkStrategies/baseWalkStrategy.d.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfiguration } from "../models/nodeConfiguration"; 2 | import { NodeWalkStrategy } from "../models/nodeWalkStrategy"; 3 | /** 4 | * Common features for the node strategies. 5 | * @private 6 | */ 7 | export declare abstract class BaseWalkStrategy implements NodeWalkStrategy { 8 | /** 9 | * The list of all configured nodes. 10 | */ 11 | private readonly _allNodes; 12 | /** 13 | * The number of failures before a node is blacklisted. 14 | */ 15 | private readonly _blacklistLimit?; 16 | /** 17 | * The list of usable nodes to iterate through. 18 | */ 19 | private _usableNodes; 20 | /** 21 | * The nodes that have been blacklisted. 22 | */ 23 | private _blacklistNodes; 24 | /** 25 | * Create a new instance of BaseWalkStrategy. 26 | * @param nodes The nodes to iterate through. 27 | * @param blacklistLimit The number of failures before a node is blacklisted. 28 | */ 29 | constructor(nodes: NodeConfiguration[], blacklistLimit?: number); 30 | /** 31 | * The total number of nodes configured for the strategy. 32 | * @returns The total number of nodes. 33 | */ 34 | totalUsable(): number; 35 | /** 36 | * Get the current node from the strategy. 37 | * @returns A node configuration from the strategy. 38 | */ 39 | abstract current(): NodeConfiguration; 40 | /** 41 | * Move to the next node in the strategy. 42 | * @param retainOrder Retain the ordering if resetting the list. 43 | */ 44 | abstract next(retainOrder: boolean): void; 45 | /** 46 | * Blacklist the current node, so it doesn't get used again once limit is reached. 47 | */ 48 | blacklist(): void; 49 | /** 50 | * Get the list of nodes that have not been blacklisted. 51 | * @returns The non blacklisted nodes. 52 | */ 53 | protected getUsableNodes(): NodeConfiguration[]; 54 | } 55 | -------------------------------------------------------------------------------- /typings/walkStrategies/linearWalkStrategy.d.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfiguration } from "../models/nodeConfiguration"; 2 | import { BaseWalkStrategy } from "./baseWalkStrategy"; 3 | /** 4 | * Node choice strategy which just iterates through the list of nodes. 5 | */ 6 | export declare class LinearWalkStrategy extends BaseWalkStrategy { 7 | /** 8 | * The current node to use. 9 | */ 10 | private _currentIndex; 11 | /** 12 | * Create a new instance of LinearWalkStrategy. 13 | * @param nodes The nodes to randomly pick from. 14 | * @param blacklistLimit The number of failures before a node is blacklisted. 15 | */ 16 | constructor(nodes: NodeConfiguration[], blacklistLimit?: number); 17 | /** 18 | * Get the current node from the strategy. 19 | * @returns A node configuration from the strategy. 20 | */ 21 | current(): NodeConfiguration; 22 | /** 23 | * Move to the next node in the strategy. 24 | * @param retainOrder Retain the ordering if resetting the list. 25 | */ 26 | next(retainOrder: boolean): void; 27 | } 28 | -------------------------------------------------------------------------------- /typings/walkStrategies/randomWalkStrategy.d.ts: -------------------------------------------------------------------------------- 1 | import { NodeConfiguration } from "../models/nodeConfiguration"; 2 | import { BaseWalkStrategy } from "./baseWalkStrategy"; 3 | /** 4 | * Node choice strategy which randomly picks from the list of nodes. 5 | */ 6 | export declare class RandomWalkStrategy extends BaseWalkStrategy { 7 | /** 8 | * The remaining nodes to randomly pick from. 9 | * @internal 10 | */ 11 | private _remainingNodes; 12 | /** 13 | * The current random ordering. 14 | * @internal 15 | */ 16 | private _randomNodes; 17 | /** 18 | * Create a new instance of RandomWalkStategy. 19 | * @param nodes The nodes to randomly pick from. 20 | * @param blacklistLimit The number of failures before a node is blacklisted. 21 | */ 22 | constructor(nodes: NodeConfiguration[], blacklistLimit?: number); 23 | /** 24 | * Get the current node from the strategy. 25 | * @returns A node configuration from the strategy. 26 | */ 27 | current(): NodeConfiguration; 28 | /** 29 | * Move to the next node in the strategy. 30 | * @param retainOrder Retain the ordering if resetting the list. 31 | */ 32 | next(retainOrder: boolean): void; 33 | /** 34 | * Populate the remaining array by randomizing the nodes. 35 | * @internal 36 | * @private 37 | */ 38 | private populateRemaining; 39 | } 40 | --------------------------------------------------------------------------------