├── .all-contributorsrc ├── .editorconfig ├── .github ├── CODE_OF_CONDUCT.md ├── CODE_STANDARDS.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── IMAGES │ └── share-image.png ├── ISSUE_TEMPLATE │ ├── 1_bug_report.md │ ├── 2_feature_request.md │ └── 3_general.md ├── SECURITY.md ├── dependabot.yml ├── labels.yml ├── mergify.yml └── workflows │ ├── codeql-analysis.yml │ ├── release.yml │ └── sync-labels.yml ├── .gitignore ├── .goreleaser.yml ├── .make └── common.mk ├── .npmignore ├── LICENSE ├── Makefile ├── README.md ├── api-extractor.json ├── babel.config.js ├── jest.config.ts ├── jsdoc.json ├── package.json ├── rollup.config.js ├── setup-jest.js ├── src ├── index.ts ├── interface.ts ├── protobuf.ts └── subscription.ts ├── tsconfig.json └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "js-junglebus", 3 | "projectOwner": "GorillaPool", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": false, 11 | "commitConvention": "none", 12 | "contributorsPerLine": 7, 13 | "contributorsSortAlphabetically": false, 14 | "contributors": [ 15 | { 16 | "login": "icellan", 17 | "name": "Siggi", 18 | "avatar_url": "https://avatars.githubusercontent.com/u/4411176?v=4", 19 | "profile": "https://github.com/icellan", 20 | "contributions": [ 21 | "infra", 22 | "code", 23 | "security" 24 | ] 25 | }, 26 | { 27 | "login": "mrz1836", 28 | "name": "Mr. Z", 29 | "avatar_url": "https://avatars.githubusercontent.com/u/3743002?v=4", 30 | "profile": "https://mrz1818.com", 31 | "contributions": [ 32 | "infra", 33 | "code", 34 | "maintenance", 35 | "business" 36 | ] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Merit 2 | 3 | 1. The project creators, lead developers, core team, constitute 4 | the managing members of the project and have final say in every decision 5 | of the project, technical or otherwise, including overruling previous decisions. 6 | There are no limitations to this decisional power. 7 | 8 | 2. Contributions are an expected result of your membership on the project. 9 | Don't expect others to do your work or help you with your work forever. 10 | 11 | 3. All members have the same opportunities to seek any challenge they want 12 | within the project. 13 | 14 | 4. Authority or position in the project will be proportional 15 | to the accrued contribution. Seniority must be earned. 16 | 17 | 5. Software is evolutive: the better implementations must supersede lesser 18 | implementations. Technical advantage is the primary evaluation metric. 19 | 20 | 6. This is a space for technical prowess; topics outside of the project 21 | will not be tolerated. 22 | 23 | 7. Non technical conflicts will be discussed in a separate space. Disruption 24 | of the project will not be allowed. 25 | 26 | 8. Individual characteristics, including but not limited to, 27 | body, sex, sexual preference, race, language, religion, nationality, 28 | or political preferences are irrelevant in the scope of the project and 29 | will not be taken into account concerning your value or that of your contribution 30 | to the project. 31 | 32 | 9. Discuss or debate the idea, not the person. 33 | 34 | 10. There is no room for ambiguity: Ambiguity will be met with questioning; 35 | further ambiguity will be met with silence. It is the responsibility 36 | of the originator to provide requested context. 37 | 38 | 11. If something is illegal outside the scope of the project, it is illegal 39 | in the scope of the project. This Code of Merit does not take precedence over 40 | governing law. 41 | 42 | 12. This Code of Merit governs the technical procedures of the project not the 43 | activities outside of it. 44 | 45 | 13. Participation on the project equates to agreement of this Code of Merit. 46 | 47 | 14. No objectives beyond the stated objectives of this project are relevant 48 | to the project. Any intent to deviate the project from its original purpose 49 | of existence will constitute grounds for remedial action which may include 50 | expulsion from the project. 51 | -------------------------------------------------------------------------------- /.github/CODE_STANDARDS.md: -------------------------------------------------------------------------------- 1 | # Code Standards 2 | This project uses the following code standards and specifications from: 3 | - [docsify](https://docsify.js.org) - documentation 4 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to BUX 2 | 3 | See [README](../README.md) for more details 4 | 5 | Getting Started: 6 | 7 | * Visit the official site at [GetBux.io](https://GetBux.io) where you can read all the documentation. 8 | * For our license see the [LICENSE](../LICENSE) file. 9 | * How we work: _coming soon_ ;-) 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: GorillaPool 4 | custom: https://getbux.io/?utm_source=github&utm_medium=sponsor-link&utm_campaign=bux-website&utm_term=bux-website&utm_content=bux-website 5 | -------------------------------------------------------------------------------- /.github/IMAGES/share-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gorillapool/js-junglebus/df5a65b1e25445fe5fe8ec699aee4696a455b190/.github/IMAGES/share-image.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1_bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report a Bug 3 | about: Create a bug report to help us improve our project 4 | title: '' 5 | labels: bug-P3 6 | assignees: mrz1836 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Recordings** 27 | These help immensely! Use https://recordit.co 28 | 29 | **Desktop or Mobile Browser (please complete the following information):** 30 | - OS: [e.g. iOS] 31 | - Browser [e.g. chrome, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2_feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea or feature for this project 4 | title: '' 5 | labels: idea 6 | assignees: mrz1836 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Any supplemental graphics or designs*** 20 | If you have any sketches, designs, graphics, please attach them here. 21 | 22 | **Additional context** 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3_general.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Miscellaneous 3 | about: For all other miscellaneous issues 4 | title: '' 5 | labels: question 6 | assignees: mrz1836 7 | 8 | --- 9 | 10 | **Please describe your request/issue** 11 | A clear and concise description of what the issue is. 12 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported & Maintained Versions 4 | 5 | | Version | Supported | 6 | |---------|--------------------| 7 | | 0.x.x | :white_check_mark: | 8 | 9 | ## Reporting a Vulnerability 10 | 11 | Individuals or organizations that are experiencing a product security issue are strongly encouraged to contact the [project maintainers](mailto:security@getbux.io). 12 | We welcome reports from independent researchers, industry organizations, vendors, customers, and other sources concerned with our project security. 13 | The minimal data needed for reporting a security issue is a description of the potential vulnerability. 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Basic dependabot.yml to update npm and Github Actions 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: 'npm' 6 | target-branch: 'master' 7 | directory: '/' 8 | schedule: 9 | interval: 'daily' 10 | # Check for npm updates at 10am UTC (5am EST) 11 | time: '10:00' 12 | ignore: 13 | - dependency-name: "@types/bsv" 14 | - dependency-name: "bsv" 15 | reviewers: 16 | - 'mrz1836' 17 | assignees: 18 | - 'mrz1836' 19 | # Labels must be created first 20 | labels: 21 | - 'chore' 22 | 23 | # Maintain dependencies for GitHub Actions 24 | - package-ecosystem: "github-actions" 25 | target-branch: "master" 26 | directory: "/" 27 | schedule: 28 | interval: "weekly" 29 | day: "monday" 30 | reviewers: 31 | - "mrz1836" 32 | assignees: 33 | - "mrz1836" 34 | labels: 35 | - "chore" 36 | open-pull-requests-limit: 10 37 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | - color: 0075ca 2 | description: "Improvements or additions to documentation" 3 | name: "documentation" 4 | - color: b23128 5 | description: "Highest rated bug or issue, affects all" 6 | name: "bug-P1" 7 | - color: de3d32 8 | description: "Medium rated bug, affects a few" 9 | name: "bug-P2" 10 | - color: f44336 11 | description: "Lowest rated bug, affects nearly none or low-impact" 12 | name: "bug-P3" 13 | - color: 0e8a16 14 | description: "Any new significant addition" 15 | name: "feature" 16 | - color: b60205 17 | description: "Urgent or important fix/patch" 18 | name: "hot-fix" 19 | - color: cccccc 20 | description: "Any idea, suggestion or opinion" 21 | name: "idea" 22 | - color: d4c5f9 23 | description: "Experimental - can break!" 24 | name: "prototype" 25 | - color: cc317c 26 | description: "Any question or concern" 27 | name: "question" 28 | - color: c2e0c6 29 | description: "Unit tests, mocking, integration tests" 30 | name: "test" 31 | - color: fbca04 32 | description: "Anything GUI related" 33 | name: "ui-ux" 34 | - color: 006b75 35 | description: "Simple dependency updates or version bumps" 36 | name: "chore" 37 | - color: 006b75 38 | description: "General updates" 39 | name: "update" 40 | - color: FFA500 41 | description: "Any significant refactoring" 42 | name: "refactor" 43 | - color: FEF2C0 44 | description: "Used for automatic merging" 45 | name: "automerge" 46 | - color: FBCA04 47 | description: "Used for denoting a WIP, stops auto-merge" 48 | name: "work-in-progress" 49 | - color: c2e0c6 50 | description: "Old, unused, stale" 51 | name: "stale" 52 | -------------------------------------------------------------------------------- /.github/mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | 3 | # =============================================================================== 4 | # DEPENDABOT 5 | # =============================================================================== 6 | 7 | - name: Automatic Merge for Dependabot Minor Version Pull Requests 8 | conditions: 9 | - -draft 10 | - author~=^dependabot(|-preview)\[bot\]$ 11 | - check-success='Analyze (javascript)' 12 | - title~=^Bump [^\s]+ from ([\d]+)\..+ to \1\. 13 | actions: 14 | review: 15 | type: APPROVE 16 | message: Automatically approving dependabot pull request 17 | merge: 18 | method: merge 19 | - name: Alert on major version detection 20 | conditions: 21 | - author~=^dependabot(|-preview)\[bot\]$ 22 | - check-success='Analyze (javascript)' 23 | - -title~=^Bump [^\s]+ from ([\d]+)\..+ to \1\. 24 | actions: 25 | comment: 26 | message: "⚠️ @mrz1836: this is a major version bump and requires your attention" 27 | 28 | # =============================================================================== 29 | # AUTOMATIC MERGE (APPROVALS) 30 | # =============================================================================== 31 | 32 | - name: Automatic Merge ⬇️ on Approval ✔ 33 | conditions: 34 | - "#approved-reviews-by>=1" 35 | - "#review-requested=0" 36 | - "#changes-requested-reviews-by=0" 37 | - check-success='Analyze (javascript)' 38 | - -title~=(?i)wip 39 | - label!=work-in-progress 40 | - -draft 41 | actions: 42 | merge: 43 | method: merge 44 | 45 | # =============================================================================== 46 | # AUTHOR 47 | # =============================================================================== 48 | 49 | - name: Auto-Assign Author 50 | conditions: 51 | - "#assignee=0" 52 | actions: 53 | assign: 54 | users: [ "mrz1836" ] 55 | 56 | # =============================================================================== 57 | # ALERTS 58 | # =============================================================================== 59 | 60 | - name: Notify on merge 61 | conditions: 62 | - merged 63 | - label=automerge 64 | actions: 65 | comment: 66 | message: "✅ @{{author}}: **{{title}}** has been merged successfully." 67 | - name: Alert on merge conflict 68 | conditions: 69 | - conflict 70 | - label=automerge 71 | actions: 72 | comment: 73 | message: "🆘 @{{author}}: `{{head}}` has conflicts with `{{base}}` that must be resolved." 74 | - name: Alert on tests failure for automerge 75 | conditions: 76 | - label=automerge 77 | - status-failure=commit 78 | actions: 79 | comment: 80 | message: "🆘 @{{author}}: unable to merge due to CI failure." 81 | 82 | # =============================================================================== 83 | # LABELS 84 | # =============================================================================== 85 | # Automatically add labels when PRs match certain patterns 86 | # 87 | # NOTE: 88 | # - single quotes for regex to avoid accidental escapes 89 | # - Mergify leverages Python regular expressions to match rules. 90 | # 91 | # Semantic commit messages 92 | # - chore: updating grunt tasks etc.; no production code change 93 | # - docs: changes to the documentation 94 | # - feat: feature or story 95 | # - feature: new feature or story 96 | # - fix: bug fix for the user, not a fix to a build script 97 | # - idea: general idea or suggestion 98 | # - question: question regarding code 99 | # - test: test related changes 100 | # - wip: work in progress PR 101 | # =============================================================================== 102 | 103 | - name: Work in Progress 104 | conditions: 105 | - "head~=(?i)^wip" # if the PR branch starts with wip/ 106 | actions: 107 | label: 108 | add: ["work-in-progress"] 109 | - name: Hotfix label 110 | conditions: 111 | - "head~=(?i)^hotfix" # if the PR branch starts with hotfix/ 112 | actions: 113 | label: 114 | add: ["hot-fix"] 115 | - name: Bug / Fix label 116 | conditions: 117 | - "head~=(?i)^(bug)?fix" # if the PR branch starts with (bug)?fix/ 118 | actions: 119 | label: 120 | add: ["bug-P3"] 121 | - name: Documentation label 122 | conditions: 123 | - "head~=(?i)^docs" # if the PR branch starts with docs/ 124 | actions: 125 | label: 126 | add: ["documentation"] 127 | - name: Feature label 128 | conditions: 129 | - "head~=(?i)^feat(ure)?" # if the PR branch starts with feat(ure)?/ 130 | actions: 131 | label: 132 | add: ["feature"] 133 | - name: Chore label 134 | conditions: 135 | - "head~=(?i)^chore" # if the PR branch starts with chore/ 136 | actions: 137 | label: 138 | add: ["update"] 139 | - name: Question label 140 | conditions: 141 | - "head~=(?i)^question" # if the PR branch starts with question/ 142 | actions: 143 | label: 144 | add: ["question"] 145 | - name: Test label 146 | conditions: 147 | - "head~=(?i)^test" # if the PR branch starts with test/ 148 | actions: 149 | label: 150 | add: ["test"] 151 | - name: Idea label 152 | conditions: 153 | - "head~=(?i)^idea" # if the PR branch starts with idea/ 154 | actions: 155 | label: 156 | add: ["idea"] 157 | 158 | # =============================================================================== 159 | # CONTRIBUTORS 160 | # =============================================================================== 161 | 162 | - name: Welcome New Contributors 163 | conditions: 164 | - and: 165 | - author!=dependabot[bot] 166 | - author!=mergify[bot] 167 | - author!=icellan 168 | - author!=mrz1836 169 | actions: 170 | comment: 171 | message: "Welcome to our open-source project @{{author}}! 💘" 172 | 173 | # =============================================================================== 174 | # STALE BRANCHES 175 | # =============================================================================== 176 | 177 | - name: Close stale pull request 178 | conditions: 179 | - base=master 180 | - -closed 181 | - updated-at<21 days ago 182 | actions: 183 | close: 184 | message: | 185 | This pull request looks stale. Feel free to reopen it if you think it's a mistake. 186 | label: 187 | add: [ "stale" ] 188 | 189 | # =============================================================================== 190 | # BRANCHES 191 | # =============================================================================== 192 | 193 | - name: Delete head branch after merge 194 | conditions: 195 | - merged 196 | actions: 197 | delete_head_branch: 198 | 199 | # =============================================================================== 200 | # CONVENTION 201 | # =============================================================================== 202 | # https://www.conventionalcommits.org/en/v1.0.0/ 203 | # Premium feature only 204 | 205 | #- name: Conventional Commit 206 | # conditions: 207 | # - "title~=^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\\(.+\\))?:" 208 | # actions: 209 | # post_check: 210 | # title: | 211 | # {% if check_succeed %} 212 | # Title follows Conventional Commit 213 | # {% else %} 214 | # Title does not follow Conventional Commit 215 | # {% endif %} 216 | # summary: | 217 | # {% if not check_succeed %} 218 | # Your pull request title must follow [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/). 219 | # {% endif %} 220 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | 21 | jobs: 22 | analyze: 23 | name: Analyze 24 | runs-on: ubuntu-latest 25 | permissions: 26 | actions: read 27 | contents: read 28 | security-events: write 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | language: [ 'javascript' ] 34 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 35 | # Learn more: 36 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 37 | 38 | steps: 39 | - name: Checkout repository 40 | uses: actions/checkout@v3 41 | 42 | # Initializes the CodeQL tools for scanning. 43 | - name: Initialize CodeQL 44 | uses: github/codeql-action/init@v2 45 | with: 46 | languages: ${{ matrix.language }} 47 | # If you wish to specify custom queries, you can do so here or in a config file. 48 | # By default, queries listed here will override any specified in a config file. 49 | # Prefix the list here with "+" to use these queries and those in the config file. 50 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 51 | 52 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 53 | # If this step fails, then you should remove it and run the build manually (see below) 54 | - name: Autobuild 55 | uses: github/codeql-action/autobuild@v2 56 | 57 | # ℹ️ Command-line programs to run using the OS shell. 58 | # 📚 https://git.io/JvXDl 59 | 60 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 61 | # and modify them (or add more) to build your code if your project 62 | # uses a compiled language 63 | 64 | #- run: | 65 | # make bootstrap 66 | # make release 67 | 68 | - name: Perform CodeQL Analysis 69 | uses: github/codeql-action/analyze@v2 70 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # From: https://goreleaser.com/ci/actions/#usage 2 | name: release 3 | 4 | on: 5 | push: 6 | tags: 7 | - '*' 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | goreleaser: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | - name: Set up Go 21 | uses: actions/setup-go@v3 22 | with: 23 | go-version: 1.17 24 | - name: Setup Node 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: 16 28 | registry-url: https://registry.npmjs.org/ 29 | - name: Install all deps 30 | run: make install 31 | - name: Publish to NPM 32 | run: make publish 33 | env: 34 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 35 | - name: Run GoReleaser 36 | uses: goreleaser/goreleaser-action@v4.2.0 37 | with: 38 | distribution: goreleaser 39 | version: latest 40 | args: release --rm-dist --debug 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | # Workflow: https://github.com/micnncim/action-label-syncer 2 | # Export your labels: https://github.com/micnncim/label-exporter 3 | name: sync-labels 4 | on: 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - .github/labels.yml 10 | jobs: 11 | sync-labels: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: micnncim/action-label-syncer@v1.3.0 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | with: 19 | manifest: .github/labels.yml 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | firebase-debug.log* 8 | 9 | # Firebase cache 10 | .firebase 11 | .firebase/ 12 | .firebaserc 13 | firebase.json 14 | 15 | # Firebase config 16 | 17 | # Uncomment this if you'd like others to create their own Firebase project. 18 | # For a team working on the same Firebase project(s), it is recommended to leave 19 | # it commented so all members can deploy to the same project(s) in .firebaserc. 20 | # .firebaserc 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | 40 | # Optional npm cache directory 41 | .npm 42 | 43 | # Optional eslint cache 44 | .eslintcache 45 | 46 | # Optional REPL history 47 | .node_repl_history 48 | 49 | # Output of 'npm pack' 50 | *.tgz 51 | 52 | # Yarn Integrity file 53 | .yarn-integrity 54 | 55 | # dotenv environment variables file 56 | .env 57 | 58 | # WebStorm 59 | .idea 60 | .DS_Store 61 | todo.md 62 | 63 | # Deployment 64 | dist 65 | coverage 66 | module 67 | docs 68 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Make sure to check the documentation at http://goreleaser.com 2 | # --------------------------- 3 | # General 4 | # --------------------------- 5 | before: 6 | hooks: 7 | - make test 8 | snapshot: 9 | name_template: "{{ .Tag }}" 10 | changelog: 11 | sort: asc 12 | filters: 13 | exclude: 14 | - '^.github:' 15 | - '^test:' 16 | 17 | # --------------------------- 18 | # Builder 19 | # --------------------------- 20 | build: 21 | skip: true 22 | 23 | # --------------------------- 24 | # Publishers 25 | # --------------------------- 26 | # publishers: 27 | # - name: npm 28 | # cmd: make publish 29 | # dir: "." 30 | 31 | # --------------------------- 32 | # Github Release 33 | # --------------------------- 34 | release: 35 | prerelease: true 36 | name_template: "Release v{{.Version}}" 37 | 38 | # --------------------------- 39 | # Announce 40 | # --------------------------- 41 | announce: 42 | 43 | # See more at: https://goreleaser.com/customization/announce/#slack 44 | slack: 45 | enabled: false 46 | message_template: '{{ .ProjectName }} {{ .Tag }} is out! Changelog: https://github.com/GorillaPool/{{ .ProjectName }}/releases/tag/{{ .Tag }}' 47 | channel: '#releases' 48 | # username: '' 49 | # icon_emoji: '' 50 | # icon_url: '' 51 | 52 | # See more at: https://goreleaser.com/customization/announce/#twitter 53 | twitter: 54 | enabled: false 55 | message_template: '{{ .ProjectName }} {{ .Tag }} is out!' 56 | 57 | # See more at: https://goreleaser.com/customization/announce/#discord 58 | discord: 59 | enabled: false 60 | message_template: '{{ .ProjectName }} {{ .Tag }} is out! Changelog: https://github.com/GorillaPool/{{ .ProjectName }}/releases/tag/{{ .Tag }}' 61 | author: 'TonicBot' 62 | #color: '80200120' #50C878 63 | #icon_url: 'https://tonicpow.com/images/apple-touch-icon.png' 64 | 65 | # See more at: https://goreleaser.com/customization/announce/#reddit 66 | reddit: 67 | enabled: false 68 | # Application ID for Reddit Application 69 | application_id: "" 70 | # Username for your Reddit account 71 | username: "" 72 | # Defaults to `{{ .GitURL }}/releases/tag/{{ .Tag }}` 73 | # url_template: 'https://github.com/GorillaPool/{{ .ProjectName }}/releases/tag/{{ .Tag }}' 74 | # Defaults to `{{ .ProjectName }} {{ .Tag }} is out!` 75 | title_template: '{{ .ProjectName }} {{ .Tag }} is out!' 76 | -------------------------------------------------------------------------------- /.make/common.mk: -------------------------------------------------------------------------------- 1 | ## Default repository domain name 2 | ifndef GIT_DOMAIN 3 | override GIT_DOMAIN=github.com 4 | endif 5 | 6 | ## Set if defined (alias variable for ease of use) 7 | ifdef branch 8 | override REPO_BRANCH=$(branch) 9 | export REPO_BRANCH 10 | endif 11 | 12 | ## Do we have git available? 13 | HAS_GIT := $(shell command -v git 2> /dev/null) 14 | 15 | ifdef HAS_GIT 16 | ## Do we have a repo? 17 | HAS_REPO := $(shell git rev-parse --is-inside-work-tree 2> /dev/null) 18 | ifdef HAS_REPO 19 | ## Automatically detect the repo owner and repo name (for local use with Git) 20 | REPO_NAME=$(shell basename "$(shell git rev-parse --show-toplevel 2> /dev/null)") 21 | REPO_OWNER=$(shell git config --get remote.origin.url | sed 's/git@$(GIT_DOMAIN)://g' | sed 's/\/$(REPO_NAME).git//g') 22 | VERSION_SHORT=$(shell git describe --tags --always --abbrev=0) 23 | export REPO_NAME, REPO_OWNER, VERSION_SHORT 24 | endif 25 | endif 26 | 27 | ## Set the distribution folder 28 | ifndef DISTRIBUTIONS_DIR 29 | override DISTRIBUTIONS_DIR=./dist 30 | endif 31 | export DISTRIBUTIONS_DIR 32 | 33 | help: ## Show this help message 34 | @egrep -h '^(.+)\:\ ##\ (.+)' ${MAKEFILE_LIST} | column -t -c 2 -s ':#' 35 | 36 | release:: ## Full production release (creates release in Github) 37 | @test $(github_token) 38 | @export GITHUB_TOKEN=$(github_token) && goreleaser --rm-dist 39 | 40 | release-test: ## Full production test release (everything except deploy) 41 | @goreleaser --skip-publish --rm-dist 42 | 43 | release-snap: ## Test the full release (build binaries) 44 | @goreleaser --snapshot --skip-publish --rm-dist 45 | 46 | replace-version: ## Replaces the version in HTML/JS (pre-deploy) 47 | @test $(version) 48 | @test "$(path)" 49 | @find $(path) -name "*.html" -type f -exec sed -i '' -e "s/{{version}}/$(version)/g" {} \; 50 | @find $(path) -name "*.js" -type f -exec sed -i '' -e "s/{{version}}/$(version)/g" {} \; 51 | 52 | tag: ## Generate a new tag and push (tag version=0.0.0) 53 | @test $(version) 54 | @git tag -a v$(version) -m "Pending full release..." 55 | @git push origin v$(version) 56 | @git fetch --tags -f 57 | 58 | tag-remove: ## Remove a tag if found (tag-remove version=0.0.0) 59 | @test $(version) 60 | @git tag -d v$(version) 61 | @git push --delete origin v$(version) 62 | @git fetch --tags 63 | 64 | tag-update: ## Update an existing tag to current commit (tag-update version=0.0.0) 65 | @test $(version) 66 | @git push --force origin HEAD:refs/tags/v$(version) 67 | @git fetch --tags -f 68 | 69 | update-releaser: ## Update the goreleaser application 70 | @brew update 71 | @brew upgrade goreleaser 72 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | test 4 | *.log 5 | .DS_Store 6 | .idea 7 | examples/ 8 | .github 9 | .firebase 10 | .firebase/ 11 | .firebaserc 12 | firebase.json 13 | todo.md 14 | .goreleaser.yml 15 | Makefile 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Open BSV License 2 | Copyright (c) 2022 GorillaPool 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | 1 - The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 2 - The Software, and any software that is derived from the Software or parts thereof, 14 | can only be used on the Bitcoin SV blockchains. The Bitcoin SV blockchains are defined, 15 | for purposes of this license, as the Bitcoin blockchain containing block height #556767 16 | with the hash "000000000000000001d956714215d96ffc00e0afda4cd0a96c96f8d802b1662b" and 17 | the test blockchains that are supported by the un-modified Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Common makefile commands & variables between projects 2 | include .make/common.mk 3 | 4 | ## Not defined? Use default repo name which is the application 5 | ifeq ($(REPO_NAME),) 6 | REPO_NAME="js-junglebus" 7 | endif 8 | 9 | ## Not defined? Use default repo owner 10 | ifeq ($(REPO_OWNER),) 11 | REPO_OWNER="GorillaPool" 12 | endif 13 | 14 | .PHONY: clean publish release test 15 | 16 | audit: ## Checks for vulnerabilities in dependencies 17 | @yarn audit 18 | 19 | clean: ## Remove previous builds and any test cache data 20 | @if [ -d $(DISTRIBUTIONS_DIR) ]; then rm -r $(DISTRIBUTIONS_DIR); fi 21 | @if [ -d node_modules ]; then rm -r node_modules; fi 22 | 23 | install: ## Installs the dependencies for the package 24 | @yarn install 25 | 26 | install-all-contributors: ## Installs all contributors locally 27 | @echo "installing all-contributors cli tool..." 28 | @yarn global add all-contributors-cli 29 | 30 | outdated: ## Checks for outdated packages via npm 31 | @yarn outdated 32 | 33 | publish: ## Will publish the version to npm 34 | @npm run deploy 35 | 36 | release:: ## Run after releasing - deploy to npm 37 | @$(MAKE) publish 38 | 39 | test: ## Will run unit tests 40 | @yarn run test 41 | 42 | update-contributors: ## Regenerates the contributors html/list 43 | @echo "generating contributor html..." 44 | @all-contributors generate 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Gorilla Pool JungleBus: JS Client](https://www.npmjs.com/package/@GorillaPool/js-junglebus) 2 | 3 | [![last commit](https://img.shields.io/github/last-commit/GorillaPool/js-junglebus.svg?style=flat&v=2)](https://github.com/GorillaPool/js-junglebus/commits/master) 4 | [![version](https://img.shields.io/github/release-pre/GorillaPool/js-junglebus.svg?style=flat&v=2)](https://github.com/GorillaPool/js-junglebus/releases) 5 | [![Npm](https://img.shields.io/npm/v/@GorillaPool/js-junglebus?style=flat&v=2)](https://www.npmjs.com/package/@GorillaPool/js-junglebus) 6 | [![license](https://img.shields.io/badge/license-Open%20BSV-brightgreen.svg?style=flat&v=2)](/LICENSE) 7 | [![Mergify Status](https://img.shields.io/endpoint.svg?url=https://api.mergify.com/v1/badges/GorillaPool/js-junglebus&style=flat&v=2)](https://mergify.io) 8 | [![Sponsor](https://img.shields.io/badge/sponsor-GorillaPool-181717.svg?logo=github&style=flat&v=2)](https://github.com/sponsors/GorillaPool) 9 | 10 | ## Table of Contents 11 | - [Gorilla Pool JungleBus: JS Client](#gorilla-pool-junglebus-js-client) 12 | - [Table of Contents](#table-of-contents) 13 | - [What is JungleBus?](#what-is-junglebus) 14 | - [Installation](#installation) 15 | - [Usage](#usage) 16 | - [Lite Mode](#lite-mode) 17 | - [Documentation](#documentation) 18 | - [Code Standards](#code-standards) 19 | - [Contributing](#contributing) 20 | - [How can I help?](#how-can-i-help) 21 | - [Contributors ✨](#contributors-) 22 | - [License](#license) 23 | 24 |
25 | 26 | ## What is JungleBus? 27 | [Read more about JungleBus](https://junglebus.gorillapool.io) 28 | 29 |
30 | 31 | ## Installation 32 | 33 | Install the JungleBus library into your project: 34 | ```bash 35 | $ npm install @gorillapool/js-junglebus 36 | ``` 37 | 38 | or, with yarn 39 | ```bash 40 | $ yarn add @gorillapool/js-junglebus 41 | ``` 42 | 43 | ## Usage 44 | Here's the getting started with JungleBus 45 | 46 | ```javascript 47 | import { JungleBusClient } from '@gorillapool/js-junglebus'; 48 | 49 | const server = "junglebus.gorillapool.io"; 50 | const jungleBusClient = new JungleBusClient(server, { 51 | onConnected(ctx) { 52 | // add your own code here 53 | console.log(ctx); 54 | }, 55 | onConnecting(ctx) { 56 | // add your own code here 57 | console.log(ctx); 58 | }, 59 | onDisconnected(ctx) { 60 | // add your own code here 61 | console.log(ctx); 62 | }, 63 | onError(ctx) { 64 | // add your own code here 65 | console.error(ctx); 66 | } 67 | }); 68 | 69 | // create subscriptions in the dashboard of the JungleBus website 70 | const subId = "...."; 71 | const fromBlock = 750000; 72 | const subscription = jungleBusClient.Subscribe( 73 | subId, 74 | fromBlock, 75 | onPublish(tx) => { 76 | // add your own code here 77 | console.log(tx); 78 | 79 | }, 80 | onStatus(ctx) => { 81 | // add your own code here 82 | console.log(ctx); 83 | }, 84 | onError(ctx) => { 85 | // add your own code here 86 | console.log(ctx); 87 | }, 88 | onMempool(tx) => { 89 | // add your own code here 90 | console.log(tx); 91 | }); 92 | ``` 93 | 94 |
95 | 96 | ## Lite Mode 97 | JungleBus also supports a lite mode, which delivers only the transaction hash and block height. This is useful for applications that only need to know when a transaction is included in a block. 98 | 99 | To use lite mode, just pass true as a final argument to the Subscribe method. 100 | 101 | ```javascript 102 | await client.Subscribe("a5e2fa655c41753331539a2a86546bf9335ff6d9b7a512dc9acddb00ab9985c0", 1550000, onPublish, onStatus, onError, onMempool, true); 103 | ``` 104 | 105 | ## Documentation 106 | View more [JungleBus documentation](https://junglebus.gorillapool.io/docs). 107 | 108 | ## Code Standards 109 | Please read our [code standards document](.github/CODE_STANDARDS.md) 110 | 111 | ## Contributing 112 | View the [contributing guidelines](.github/CONTRIBUTING.md) and follow the [code of conduct](.github/CODE_OF_CONDUCT.md). 113 | 114 | ### How can I help? 115 | All kinds of contributions are welcome :raised_hands:! 116 | The most basic way to show your support is to star :star2: the project, or to raise issues :speech_balloon:. 117 | You can also support this project by [becoming a sponsor on GitHub](https://github.com/sponsors/GorillaPool) :clap: 118 | 119 | [![Stars](https://img.shields.io/github/stars/GorillaPool/js-junglebus?label=Please%20like%20us&style=social&v=2)](https://github.com/GorillaPool/js-junglebus/stargazers) 120 | 121 |
122 | 123 | ### Contributors ✨ 124 | Thank you to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 |

Siggi

🚇 💻 🛡️
134 | 135 | 136 | 137 | 138 | 139 | 140 | > This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. 141 | 142 | 143 |
144 | 145 | ## License 146 | [![License](https://img.shields.io/badge/license-Open%20BSV-brightgreen.svg?style=flat&v=2)](/LICENSE) 147 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectFolder": ".", 3 | "mainEntryPointFilePath": "./build/index.d.ts", 4 | "bundledPackages": [], 5 | "compiler": { 6 | "tsconfigFilePath": "./tsconfig.json", 7 | "overrideTsconfig": { 8 | "compilerOptions": { 9 | "outDir": "build" 10 | } 11 | } 12 | }, 13 | "dtsRollup": { 14 | "enabled": true, 15 | "untrimmedFilePath": "./dist/index.d.ts" 16 | }, 17 | "apiReport": { 18 | "enabled": false 19 | }, 20 | "docModel": { 21 | "enabled": false 22 | }, 23 | "tsdocMetadata": { 24 | "enabled": false 25 | }, 26 | "messages": { 27 | "compilerMessageReporting": { 28 | "default": { 29 | "logLevel": "none" 30 | } 31 | }, 32 | "extractorMessageReporting": { 33 | "default": { 34 | "logLevel": "none" 35 | } 36 | }, 37 | "tsdocMessageReporting": { 38 | "default": { 39 | "logLevel": "none" 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', {targets: {node: 'current'}}], 4 | '@babel/preset-typescript', 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | // All imported modules in your tests should be mocked automatically 8 | automock: false, 9 | 10 | // Stop running tests after `n` failures 11 | // bail: 0, 12 | 13 | // The directory where Jest should store its cached dependency information 14 | // cacheDirectory: "/private/var/folders/9k/tb80d9g96wddlz7zqg43nhwm0000gp/T/jest_dy", 15 | 16 | // Automatically clear mock calls, instances and results before every test 17 | clearMocks: true, 18 | 19 | // Indicates whether the coverage information should be collected while executing the test 20 | collectCoverage: true, 21 | 22 | // An array of glob patterns indicating a set of files for which coverage information should be collected 23 | // collectCoverageFrom: undefined, 24 | 25 | // The directory where Jest should output its coverage files 26 | coverageDirectory: "coverage", 27 | 28 | // An array of regexp pattern strings used to skip coverage collection 29 | // coveragePathIgnorePatterns: [ 30 | // "/node_modules/" 31 | // ], 32 | 33 | // Indicates which provider should be used to instrument code for coverage 34 | // coverageProvider: "babel", 35 | 36 | // A list of reporter names that Jest uses when writing coverage reports 37 | // coverageReporters: [ 38 | // "json", 39 | // "text", 40 | // "lcov", 41 | // "clover" 42 | // ], 43 | 44 | // An object that configures minimum threshold enforcement for coverage results 45 | // coverageThreshold: undefined, 46 | 47 | // A path to a custom dependency extractor 48 | // dependencyExtractor: undefined, 49 | 50 | // Make calling deprecated APIs throw helpful error messages 51 | // errorOnDeprecated: false, 52 | 53 | // Force coverage collection from ignored files using an array of glob patterns 54 | // forceCoverageMatch: [], 55 | 56 | // A path to a module which exports an async function that is triggered once before all test suites 57 | // globalSetup: undefined, 58 | 59 | // A path to a module which exports an async function that is triggered once after all test suites 60 | // globalTeardown: undefined, 61 | 62 | // A set of global variables that need to be available in all test environments 63 | // globals: {}, 64 | 65 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 66 | // maxWorkers: "50%", 67 | 68 | // An array of directory names to be searched recursively up from the requiring module's location 69 | // moduleDirectories: [ 70 | // "node_modules" 71 | // ], 72 | 73 | // An array of file extensions your modules use 74 | // moduleFileExtensions: [ 75 | // "js", 76 | // "jsx", 77 | // "ts", 78 | // "tsx", 79 | // "json", 80 | // "node" 81 | // ], 82 | 83 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 84 | // moduleNameMapper: {}, 85 | 86 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 87 | // modulePathIgnorePatterns: [], 88 | 89 | // Activates notifications for test results 90 | // notify: false, 91 | 92 | // An enum that specifies notification mode. Requires { notify: true } 93 | // notifyMode: "failure-change", 94 | 95 | // A preset that is used as a base for Jest's configuration 96 | // preset: undefined, 97 | 98 | // Run tests from one or more projects 99 | // projects: undefined, 100 | 101 | // Use this configuration option to add custom reporters to Jest 102 | // reporters: undefined, 103 | 104 | // Automatically reset mock state before every test 105 | // resetMocks: false, 106 | 107 | // Reset the module registry before running each individual test 108 | // resetModules: false, 109 | 110 | // A path to a custom resolver 111 | // resolver: undefined, 112 | 113 | // Automatically restore mock state and implementation before every test 114 | // restoreMocks: false, 115 | 116 | // The root directory that Jest should scan for tests and modules within 117 | // rootDir: undefined, 118 | 119 | // A list of paths to directories that Jest should use to search for files in 120 | // roots: [ 121 | // "" 122 | // ], 123 | 124 | // Allows you to use a custom runner instead of Jest's default test runner 125 | // runner: "jest-runner", 126 | 127 | // The paths to modules that run some code to configure or set up the testing environment before each test 128 | setupFiles: [ 129 | "./setup-jest.js", 130 | ], 131 | 132 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 133 | // setupFilesAfterEnv: [], 134 | 135 | // The number of seconds after which a test is considered as slow and reported as such in the results. 136 | // slowTestThreshold: 5, 137 | 138 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 139 | // snapshotSerializers: [], 140 | 141 | // The test environment that will be used for testing 142 | // testEnvironment: "jest-environment-node", 143 | 144 | // Options that will be passed to the testEnvironment 145 | // testEnvironmentOptions: {}, 146 | 147 | // Adds a location field to test results 148 | // testLocationInResults: false, 149 | 150 | // The glob patterns Jest uses to detect test files 151 | // testMatch: [ 152 | // "**/__tests__/**/*.[jt]s?(x)", 153 | // "**/?(*.)+(spec|test).[tj]s?(x)" 154 | // ], 155 | 156 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 157 | // testPathIgnorePatterns: [ 158 | // "/node_modules/" 159 | // ], 160 | 161 | // The regexp pattern or array of patterns that Jest uses to detect test files 162 | // testRegex: [], 163 | 164 | // This option allows the use of a custom results processor 165 | // testResultsProcessor: undefined, 166 | 167 | // This option allows use of a custom test runner 168 | // testRunner: "jest-circus/runner", 169 | 170 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 171 | // testURL: "http://localhost", 172 | 173 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 174 | // timers: "real", 175 | 176 | // A map from regular expressions to paths to transformers 177 | // transform: undefined, 178 | 179 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 180 | // transformIgnorePatterns: [ 181 | // "/node_modules/", 182 | // "\\.pnp\\.[^\\/]+$" 183 | // ], 184 | 185 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 186 | // unmockedModulePathPatterns: undefined, 187 | 188 | // Indicates whether each individual test should be reported during the run 189 | // verbose: undefined, 190 | 191 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 192 | // watchPathIgnorePatterns: [], 193 | 194 | // Whether to use watchman for file crawling 195 | // watchman: true, 196 | }; 197 | -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Bux Javascript client", 3 | "source": { 4 | "includePattern": ".+\\.ts(doc|x)?$", 5 | "excludePattern": ".+\\.(test|spec).ts" 6 | }, 7 | "plugins": [ 8 | "node_modules/better-docs/typescript" 9 | ], 10 | "tags": { 11 | "allowUnknownTags": ["optional"] 12 | }, 13 | "opts": { 14 | "template": "node_modules/better-docs" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gorillapool/js-junglebus", 3 | "version": "0.4.4", 4 | "description": "TypeScript library for connecting to a GorillaPool JungleBus server", 5 | "author": "Siggi ", 6 | "homepage": "https://junglebus.gorillapool.io", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/GorillaPool/js-junglebus" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/GorillaPool/js-junglebus/issues" 13 | }, 14 | "license": "MIT", 15 | "keywords": [ 16 | "gorillapool", 17 | "junglebus", 18 | "bitcoinsv", 19 | "bsv" 20 | ], 21 | "contributors": [ 22 | { 23 | "name": "Siggi", 24 | "url": "https://github.com/icellan/" 25 | } 26 | ], 27 | "main": "dist/typescript-npm-package.cjs.js", 28 | "module": "dist/typescript-npm-package.esm.js", 29 | "browser": "dist/typescript-npm-package.umd.js", 30 | "files": [ 31 | "dist" 32 | ], 33 | "scripts": { 34 | "build": "rimraf ./dist && rollup -c", 35 | "build:types": "tsc -p ./tsconfig.json --outDir build --declaration true && api-extractor run", 36 | "clean": "rimraf ./dist ./docs", 37 | "deploy": "yarn pub --access public", 38 | "dev": "rollup -c -w", 39 | "docs": "rimraf ./docs && jsdoc src -r -c jsdoc.json -d docs", 40 | "prebuild:types": "rimraf ./dist", 41 | "predocs": "rimraf ./docs", 42 | "pub": "yarn build && yarn publish", 43 | "test": "jest" 44 | }, 45 | "devDependencies": { 46 | "@babel/core": "^7.17.5", 47 | "@babel/preset-env": "^7.20.2", 48 | "@babel/preset-typescript": "^7.16.7", 49 | "@microsoft/api-extractor": "^7.19.4", 50 | "@rollup/plugin-babel": "^5.3.1", 51 | "@rollup/plugin-commonjs": "^22.0.0", 52 | "@rollup/plugin-json": "^4.1.0", 53 | "@rollup/plugin-node-resolve": "^15.0.1", 54 | "@rollup/plugin-typescript": "^8.3.1", 55 | "@types/bsv": "github:chainbow/bsv-types", 56 | "@types/jest": "^29.0.3", 57 | "babel-jest": "^29.0.3", 58 | "babel-plugin-import": "^1.13.3", 59 | "better-docs": "^2.7.2", 60 | "bsv": "^1.5.6", 61 | "eslint": "^8.8.0", 62 | "jest": "^29.0.3", 63 | "jest-fetch-mock": "^3.0.3", 64 | "jsdoc-babel": "^0.5.0", 65 | "prettier": "^2.5.1", 66 | "rimraf": "^3.0.2", 67 | "rollup": "^2.70.0", 68 | "rollup-plugin-dts": "^4.2.0", 69 | "rollup-plugin-exclude-dependencies-from-bundle": "^1.1.22", 70 | "rollup-plugin-polyfill-node": "^0.10.2", 71 | "ts-node": "^10.4.0", 72 | "tslib": "^2.3.1", 73 | "typedoc": "^0.23.26", 74 | "typescript": "^4.6.2" 75 | }, 76 | "dependencies": { 77 | "@types/better-queue": "^3.8.6", 78 | "better-queue": "^3.8.12", 79 | "buffer": "^6.0.3", 80 | "centrifuge": "^3.0.1", 81 | "cross-fetch": "^3.1.5", 82 | "protobufjs": "^7.2.2", 83 | "ws": "^8.8.1" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "@rollup/plugin-node-resolve"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import typescript from "@rollup/plugin-typescript"; 4 | import json from "@rollup/plugin-json"; 5 | import nodePolyfills from 'rollup-plugin-polyfill-node'; 6 | import excludeDependenciesFromBundle from "rollup-plugin-exclude-dependencies-from-bundle"; 7 | import dts from 'rollup-plugin-dts' 8 | import pkg from "./package.json"; 9 | 10 | export default [ 11 | // browser-friendly UMD build 12 | { 13 | input: "src/index.ts", 14 | output: { 15 | name: "typescriptNpmPackage", 16 | file: pkg.browser, 17 | format: "umd", 18 | sourcemap: true, 19 | }, 20 | external: ['bsv', 'cross-fetch', 'cross-fetch/polyfill'], 21 | plugins: [ 22 | resolve({ 23 | skip: ['bsv'] 24 | }), 25 | commonjs(), 26 | json(), 27 | typescript({ tsconfig: "./tsconfig.json", sourceMap: true }), 28 | nodePolyfills(), 29 | ], 30 | }, 31 | 32 | // CommonJS (for Node) and ES module (for bundlers) build. 33 | // (We could have three entries in the configuration array 34 | // instead of two, but it's quicker to generate multiple 35 | // builds from a single configuration where possible, using 36 | // an array for the `output` option, where we can specify 37 | // `file` and `format` for each target) 38 | { 39 | input: "src/index.ts", 40 | output: [ 41 | { file: pkg.main, format: "cjs", sourcemap: true }, 42 | { file: pkg.module, format: "es", sourcemap: true }, 43 | ], 44 | external: ['cross-fetch', 'cross-fetch/polyfill'], 45 | plugins: [ 46 | typescript({ tsconfig: "./tsconfig.json", sourceMap: true }), 47 | excludeDependenciesFromBundle( { peerDependencies: true } ), 48 | ], 49 | }, 50 | 51 | { 52 | // path to your declaration files root 53 | input: './dist/src/index.d.ts', 54 | output: [ 55 | { file: 'dist/typescript-npm-package.cjs.d.ts', format: 'es' }, 56 | { file: 'dist/typescript-npm-package.esm.d.ts', format: 'es' }, 57 | { file: 'dist/typescript-npm-package.umd.d.ts', format: 'es' } 58 | ], 59 | plugins: [dts()], 60 | }, 61 | ]; 62 | -------------------------------------------------------------------------------- /setup-jest.js: -------------------------------------------------------------------------------- 1 | require('jest-fetch-mock').enableMocks() 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | BlockHeader, 4 | Client, 5 | ClientOptions, 6 | ControlMessage, 7 | ControlMessageStatusCode, 8 | Transaction, 9 | TransactionMessage, 10 | } from "./interface"; 11 | import Centrifuge from 'centrifuge/build/protobuf'; 12 | import { JungleBusSubscription } from "./subscription"; 13 | import { SubscriptionErrorContext } from "centrifuge"; 14 | import "cross-fetch/polyfill"; 15 | 16 | let ws: any; 17 | if (typeof window === "undefined") { 18 | ws = require('ws'); 19 | } 20 | 21 | /** 22 | * JungleBusClient class 23 | * 24 | * @constructor 25 | * @example 26 | * const jungleBusClient = new JungleBusClient(, { 27 | * protocol: 'protobuf', 28 | * }) 29 | */ 30 | export class JungleBusClient { 31 | client: Client; 32 | 33 | constructor(serverUrl: string, options?: ClientOptions) { 34 | this.client = (options || {}) as Client; 35 | if (!this.client.protocol) { 36 | this.client.protocol = 'json'; 37 | } 38 | if (typeof options?.useSSL === "undefined") { 39 | this.client.useSSL = !(serverUrl.match(/^http:/) || serverUrl.match(/^ws:/)); 40 | } 41 | 42 | // remove https / wss from server url is defined 43 | this.client.serverUrl = serverUrl.replace(/^https?:\/\//, '').replace(/^wss?:\/\//, ''); 44 | } 45 | 46 | /** 47 | * Login to the JungleBus server and get a token 48 | * 49 | * @param username 50 | * @param password 51 | * @return error | null 52 | */ 53 | async Login(username: string, password: string): Promise { 54 | try { 55 | const response = await fetch(`${this.client.useSSL ? 'https' : 'http'}://${this.client.serverUrl}/v1/user/login`, { 56 | method: 'POST', 57 | headers: { 58 | "content-type": "application/json" 59 | }, 60 | body: JSON.stringify({ 61 | username, 62 | password, 63 | }) 64 | }); 65 | if (response.status !== 200) { 66 | this.client.error = new Error(response.statusText); 67 | return null; 68 | } 69 | 70 | const body = await response.json(); 71 | this.SetToken(body.token); 72 | 73 | return null; 74 | } catch(e: any) { 75 | this.client.error = e; 76 | return e; 77 | } 78 | } 79 | 80 | private getToken() { 81 | return this.client.token 82 | } 83 | 84 | /** 85 | * Set the JWT token to use in all calls 86 | * 87 | * @param token string 88 | * @constructor 89 | */ 90 | SetToken(token: string) { 91 | return this.client.token = token; 92 | } 93 | 94 | /** 95 | * Get an anonymous token based on a subscription ID to the JungleBus server 96 | * 97 | * @param subscriptionId 98 | * @return error | null 99 | */ 100 | async GetTokenFromSubscription(subscriptionId: string): Promise { 101 | try { 102 | const response = await fetch(`${this.client.useSSL ? 'https' : 'http'}://${this.client.serverUrl}/v1/user/subscription-token`, { 103 | method: 'POST', 104 | headers: { 105 | "content-type": "application/json" 106 | }, 107 | body: JSON.stringify({ 108 | id: subscriptionId, 109 | }) 110 | }); 111 | if (response.status !== 200) { 112 | this.client.error = new Error(response.statusText); 113 | return null; 114 | } 115 | 116 | const body = await response.json(); 117 | this.SetToken(body.token); 118 | 119 | return null; 120 | } catch(e: any) { 121 | this.client.error = e; 122 | return e; 123 | } 124 | } 125 | 126 | /** 127 | * Return the last error thrown 128 | * 129 | * @return Error 130 | */ 131 | GetLastError(): Error | undefined { 132 | return this.client?.error; 133 | } 134 | 135 | /** 136 | * Create the connection to the JungleBus server 137 | * 138 | * @return void 139 | */ 140 | Connect(): void { 141 | const self = this; 142 | const client = this.client; 143 | 144 | client.centrifuge = new Centrifuge(`${this.client.useSSL ? 'wss' : 'ws'}://${client.serverUrl}/connection/websocket${(client.protocol === "protobuf" ? '?format=protobuf' : '')}`, { 145 | protocol: client.protocol, 146 | debug: client.debug, 147 | websocket: typeof window !== "undefined" ? window.WebSocket : ws, 148 | timeout: 5000, 149 | maxServerPingDelay: 30000, 150 | getToken: function () { 151 | return new Promise((resolve, reject) => { 152 | fetch(`${client.useSSL ? 'https' : 'http'}://${client.serverUrl}/v1/user/refresh-token`, { 153 | headers: { 154 | token: self.getToken() || '', 155 | } 156 | }) 157 | .then(res => { 158 | if (!res.ok) { 159 | throw new Error(`Unexpected status code ${res.status}`); 160 | } 161 | return res.json(); 162 | }) 163 | .then(data => { 164 | // update the token 165 | self.SetToken(data.token); 166 | resolve(data.token); 167 | }) 168 | .catch(err => { 169 | reject(err); 170 | }); 171 | }); 172 | }, 173 | }); 174 | 175 | if (client.onConnected) { 176 | client.centrifuge.on('connected', client.onConnected); 177 | } 178 | if (client.onConnecting) { 179 | client.centrifuge.on('connecting', client.onConnecting); 180 | } 181 | if (client.onDisconnected) { 182 | client.centrifuge.on('disconnected', client.onDisconnected); 183 | } 184 | if (client.onError) { 185 | client.centrifuge.on('error', client.onError); 186 | } 187 | 188 | client.centrifuge.connect(); 189 | } 190 | 191 | /** 192 | * Disconnect the client from the server 193 | * 194 | * @return void 195 | */ 196 | Disconnect(): void { 197 | this.client.centrifuge.disconnect(); 198 | } 199 | 200 | /** 201 | * Subscribe to a channel on the JungleBus server 202 | * 203 | * @param subscriptionID 204 | * @param fromBlock 205 | * @param onPublish 206 | * @param onStatus 207 | * @param onError 208 | * @param onMempool 209 | * @return JungleBusSubscription 210 | */ 211 | async Subscribe( 212 | subscriptionID: string, 213 | fromBlock: number, 214 | onPublish?: (tx: Transaction) => void, 215 | onStatus?: (message: ControlMessage) => void, 216 | onError?: (error: SubscriptionErrorContext) => void, 217 | onMempool?: (tx: Transaction) => void, 218 | liteMode = false 219 | ): Promise { 220 | if (!this.client.token) { 221 | // we do not have a token yet, sign in anonymously with the subscription id 222 | await this.GetTokenFromSubscription(subscriptionID) 223 | } 224 | 225 | // Connect to the backend, if we do not have a connection yet 226 | if (!this.client.centrifuge) { 227 | this.Connect(); 228 | } 229 | 230 | return new JungleBusSubscription(this.client, subscriptionID, fromBlock, 231 | async (tx) => { 232 | if (onPublish) { 233 | if(!tx.transaction.length && !liteMode) { 234 | const url = `${this.client.useSSL ? 'https' : 'http'}://${this.client.serverUrl}/v1/transaction/get/${tx.id}/bin`; 235 | const resp = await fetch(url); 236 | tx.transaction = Buffer.from(await resp.arrayBuffer()).toString('hex') 237 | } 238 | onPublish(tx); 239 | } 240 | }, 241 | onStatus, 242 | onError, 243 | async (tx) => { 244 | if (onMempool) { 245 | if(!tx.transaction.length && !liteMode) { 246 | const url = `${this.client.useSSL ? 'https' : 'http'}://${this.client.serverUrl}/v1/transaction/get/${tx.id}/bin`; 247 | const resp = await fetch(url); 248 | tx.transaction = Buffer.from(await resp.arrayBuffer()).toString('hex') 249 | } 250 | onMempool(tx); 251 | } 252 | }, 253 | liteMode); 254 | } 255 | 256 | /** 257 | * Get a transaction from the JungleBus API 258 | * 259 | * @param txId Transaction ID in hex 260 | * @return Promise | null 261 | */ 262 | async GetTransaction(txId: string): Promise { 263 | const url = `${this.client.useSSL ? 'https' : 'http'}://${this.client.serverUrl}/v1/transaction/get/${txId}`; 264 | return await this.apiRequest(url); 265 | } 266 | 267 | 268 | /** 269 | * Get block header info from JungleBus 270 | * 271 | * @param block Block header height or hash 272 | * @return Promise | null 273 | */ 274 | async GetBlockHeader(block: string | number): Promise { 275 | const url = `${this.client.useSSL ? 'https' : 'http'}://${this.client.serverUrl}/v1/block_header/get/${block}`; 276 | return await this.apiRequest(url); 277 | } 278 | 279 | /** 280 | * Get a list of block headers from JungleBus 281 | * 282 | * @param fromBlock Block header height or hash 283 | * @param limit Limit the number of results to this number (max 10,000) 284 | * @return Promise | null 285 | */ 286 | async GetBlockHeaders(fromBlock: string | number, limit: number): Promise { 287 | const url = `${this.client.useSSL ? 'https' : 'http'}://${this.client.serverUrl}/v1/block_header/list/${fromBlock}?limit=${limit}`; 288 | return await this.apiRequest(url); 289 | } 290 | 291 | /** 292 | * Get all transaction references for the given address 293 | * 294 | * @param address Bitcoin address 295 | * @return Promise | null 296 | */ 297 | async GetAddressTransactions(address: string): Promise { 298 | const url = `${this.client.useSSL ? 'https' : 'http'}://${this.client.serverUrl}/v1/address/get/${address}`; 299 | return await this.apiRequest(url); 300 | } 301 | 302 | /** 303 | * Get all transactions, including the hex and merkle proof, for the given address 304 | * 305 | * This function is much slower than GetAddressTransactions 306 | * 307 | * @param address Bitcoin address 308 | * @return Promise | null 309 | */ 310 | async GetAddressTransactionDetails(address: string): Promise { 311 | const url = `${this.client.useSSL ? 'https' : 'http'}://${this.client.serverUrl}/v1/address/transactions/${address}`; 312 | return await this.apiRequest(url); 313 | } 314 | 315 | private async apiRequest(url: string) { 316 | try { 317 | const response = await fetch(url, { 318 | method: 'GET', 319 | headers: { 320 | "content-type": "application/json", 321 | token: this.getToken() || '', 322 | }, 323 | }); 324 | if (response.status !== 200) { 325 | this.client.error = new Error(response.statusText); 326 | return null; 327 | } 328 | 329 | return await response.json(); 330 | } catch (e: any) { 331 | this.client.error = e; 332 | throw e; 333 | } 334 | } 335 | } 336 | 337 | export { 338 | Client, 339 | ClientOptions, 340 | ControlMessage, 341 | ControlMessageStatusCode, 342 | JungleBusSubscription, 343 | Transaction, 344 | TransactionMessage, 345 | }; 346 | -------------------------------------------------------------------------------- /src/interface.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Centrifuge, 3 | ConnectedContext, 4 | ConnectingContext, 5 | DisconnectedContext, 6 | ErrorContext, 7 | } from "centrifuge"; 8 | import protobuf from "protobufjs"; 9 | 10 | export interface Client { 11 | centrifuge: Centrifuge; 12 | serverUrl: string; 13 | protocol: "protobuf" | "json" | undefined; 14 | useSSL?: boolean; 15 | token?: string; 16 | onConnecting?: (ctx: ConnectingContext) => void; 17 | onDisconnected?: (ctx: DisconnectedContext) => void; 18 | onConnected?: (ctx: ConnectedContext) => void; 19 | onError?: (ctx: ErrorContext) => void; 20 | debug?: boolean; 21 | error?: Error; 22 | } 23 | 24 | export interface PaymentRequest { 25 | tx: string; // transaction template 26 | } 27 | 28 | export interface ClientOptions { 29 | protocol: "protobuf" | "json" | undefined; 30 | useSSL?: boolean; 31 | token?: string; 32 | onConnecting?: (ctx: ConnectingContext) => void; 33 | onDisconnected?: (ctx: DisconnectedContext) => void; 34 | onConnected?: (ctx: ConnectedContext) => void; 35 | onError?: (ctx: ErrorContext) => void; 36 | onPayment?: (request: PaymentRequest) => void; 37 | debug?: boolean; 38 | } 39 | 40 | export interface Address { 41 | id: string; 42 | address: string; 43 | transaction_id: string; 44 | block_hash: string; 45 | transaction_num: number; 46 | } 47 | 48 | export interface BlockHeader { 49 | hash: string; 50 | coin: number; 51 | height: number; 52 | time: number; 53 | nonce: number; 54 | version: number; 55 | merkleroot: string; 56 | bits: string; 57 | synced: number; 58 | } 59 | 60 | export interface Transaction { 61 | id: string; 62 | block_hash: string; 63 | block_height: number; 64 | block_index: number; 65 | block_time: number; 66 | transaction: string; 67 | merkle_proof: string; 68 | } 69 | 70 | export interface TransactionMessage { 71 | id: string; 72 | block_hash: string; 73 | block_height: number; 74 | block_index: number; 75 | block_time: number; 76 | transaction: protobuf.Buffer; 77 | merkle_proof: protobuf.Buffer; 78 | } 79 | 80 | export enum ControlMessageStatusCode { 81 | WAITING = 100, 82 | ERROR = 101, 83 | PAUSED = 102, 84 | BLOCK_DONE = 200, 85 | REORG = 300, 86 | } 87 | 88 | export interface ControlMessage { 89 | statusCode: ControlMessageStatusCode; 90 | status: string; 91 | message: string; 92 | block: number; 93 | transactions: number; 94 | } 95 | -------------------------------------------------------------------------------- /src/protobuf.ts: -------------------------------------------------------------------------------- 1 | import protobuf from "protobufjs"; 2 | 3 | export const ProtobufDef = { 4 | nested: { 5 | Transaction: { 6 | fields: { 7 | id: { 8 | type: "string", 9 | id: 1, 10 | }, 11 | block_hash: { 12 | type: "string", 13 | id: 2, 14 | }, 15 | block_height: { 16 | type: "uint32", 17 | id: 3, 18 | }, 19 | block_index: { 20 | type: "uint64", 21 | id: 4, 22 | }, 23 | block_time: { 24 | type: "uint32", 25 | id: 5, 26 | }, 27 | transaction: { 28 | type: "bytes", 29 | id: 6, 30 | }, 31 | merkle_proof: { 32 | type: "bytes", 33 | id: 7, 34 | }, 35 | } 36 | }, 37 | ControlResponse: { 38 | fields: { 39 | statusCode: { 40 | type: "uint32", 41 | id: 1, 42 | }, 43 | status: { 44 | type: "string", 45 | id: 2, 46 | }, 47 | message: { 48 | type: "string", 49 | id: 3, 50 | }, 51 | block: { 52 | type: "uint32", 53 | id: 4, 54 | }, 55 | transactions: { 56 | type: "uint64", 57 | id: 5, 58 | } 59 | } 60 | } 61 | } 62 | }; 63 | export const ProtobufRoot = protobuf.Root.fromJSON(ProtobufDef); 64 | -------------------------------------------------------------------------------- /src/subscription.ts: -------------------------------------------------------------------------------- 1 | import { PublicationContext, Subscription, SubscriptionErrorContext } from "centrifuge"; 2 | import Queue from 'better-queue'; 3 | 4 | import { Client, ControlMessage, ControlMessageStatusCode, Transaction, TransactionMessage } from "./interface"; 5 | import { ProtobufRoot } from "./protobuf"; 6 | import BetterQueue from "better-queue"; 7 | 8 | /** 9 | * JungleBusSubscription class 10 | * 11 | * @constructor 12 | * @example 13 | * const jungleBusClient = new JungleBusSubscription(options: Subscription) 14 | */ 15 | export class JungleBusSubscription { 16 | // Set the max size of the internal queue, before pausing the subscription to be able to catch up 17 | // this doesn't use a lot of RAM 18 | MaxQueueSize: number = 20000; 19 | 20 | client: Client; 21 | subscription: Subscription | undefined; 22 | controlSubscription: Subscription | undefined; 23 | mempoolSubscription: Subscription | undefined; 24 | subscriptionID: string; 25 | currentBlock: number; 26 | onPublish?: (tx: Transaction) => void; 27 | onMempool?: (tx: Transaction) => void; 28 | onStatus?: (message: ControlMessage) => void; 29 | onError?: (error: SubscriptionErrorContext) => void; 30 | subscribed: boolean; 31 | controlSubscribed: boolean; 32 | mempoolSubscribed: boolean; 33 | paused: boolean = false; 34 | error: SubscriptionErrorContext | undefined; 35 | 36 | private subscriptionQueue: BetterQueue; 37 | private mempoolQueue: BetterQueue; 38 | 39 | constructor( 40 | client: Client, 41 | subscriptionID: string, 42 | fromBlock: number, 43 | onPublish?: (tx: Transaction) => void, 44 | onStatus?: (message: ControlMessage) => void, 45 | onError?: (error: SubscriptionErrorContext) => void, 46 | onMempool?: (tx: Transaction) => void, 47 | private liteMode = false 48 | ) { 49 | this.client = client; 50 | this.subscriptionID = subscriptionID; 51 | this.currentBlock = fromBlock; 52 | this.onPublish = onPublish; 53 | this.onMempool = onMempool; 54 | this.onStatus = onStatus; 55 | this.onError = onError; 56 | 57 | this.subscribed = false; 58 | this.controlSubscribed = false; 59 | this.mempoolSubscribed = false; 60 | 61 | this.subscriptionQueue = new Queue(async function (tx, cb) { 62 | if (tx.statusCode) { 63 | if (onStatus) { 64 | await onStatus(tx) 65 | } 66 | } else { 67 | if (onPublish) { 68 | await onPublish(tx) 69 | } 70 | } 71 | cb(null, true); 72 | }); 73 | this.mempoolQueue = new Queue(async function (tx, cb) { 74 | if (onMempool) { 75 | await onMempool(tx) 76 | } 77 | cb(null, true); 78 | }); 79 | 80 | this.Subscribe(); 81 | } 82 | 83 | /** 84 | * Start the subscription 85 | * 86 | * @return void 87 | */ 88 | Subscribe(): void { 89 | if (this.subscription || this.controlSubscription || this.mempoolSubscription) { 90 | // if the subscriptions are active, unsubscribe and then re-subscribe 91 | this.UnSubscribe(); 92 | } 93 | 94 | if (this.onMempool) { 95 | this.subscribeMempool(); 96 | } 97 | 98 | if (this.onPublish) { 99 | this.subscribeControlMessage(); 100 | this.subscribeTransactionBlocks(); 101 | } 102 | } 103 | 104 | private subscribeControlMessage() { 105 | const self = this; 106 | const controlChannel = `query:${self.subscriptionID}:control`; 107 | 108 | this.controlSubscription = this.client.centrifuge.newSubscription(controlChannel); 109 | this.controlSubscription.on('publication', (ctx) => { 110 | let message: ControlMessage; 111 | if (this.client.protocol === "protobuf") { 112 | const Message = ProtobufRoot.lookupType("ControlResponse"); 113 | message = Message.decode(ctx.data) as unknown as ControlMessage; 114 | } else { 115 | message = ctx.data; 116 | } 117 | 118 | 119 | if (message.statusCode === ControlMessageStatusCode.ERROR) { 120 | if (self.onError) { 121 | self.onError({ 122 | channel: ctx.channel, 123 | type: message.status, 124 | error: { 125 | code: message.statusCode, 126 | message: message.status 127 | }, 128 | }); 129 | } 130 | } else { 131 | if (message.statusCode === ControlMessageStatusCode.BLOCK_DONE) { 132 | this.currentBlock = message.block; 133 | } 134 | 135 | if (this.onStatus) { 136 | this.subscriptionQueue.push(message); 137 | } else { 138 | //console.log(message); 139 | } 140 | } 141 | }) 142 | .on("subscribed", function (ctx) { 143 | self.controlSubscribed = true; 144 | }) 145 | .on("error", function (error) { 146 | self.error = error; 147 | if (self.onError) { 148 | self.onError(error); 149 | } 150 | }); 151 | this.controlSubscription.subscribe(); 152 | } 153 | 154 | private subscribeMempool() { 155 | const self = this; 156 | const mempoolChannel = `${this.liteMode ? 'lite' : 'query'}:${self.subscriptionID}:mempool`; 157 | 158 | this.mempoolSubscription = self.client.centrifuge.newSubscription(mempoolChannel); 159 | this.mempoolSubscription.on('publication', (ctx) => { 160 | if (this.onMempool) { 161 | const tx = this.processTransaction(self, ctx); 162 | this.mempoolQueue.push(tx); 163 | } 164 | }) 165 | .on("subscribed", function (ctx) { 166 | self.mempoolSubscribed = true; 167 | }) 168 | .on("error", function (error) { 169 | self.error = error; 170 | if (self.onError) { 171 | self.onError(error); 172 | } 173 | }); 174 | this.mempoolSubscription.subscribe(); 175 | } 176 | 177 | private subscribeTransactionBlocks(): void { 178 | const channel = `${this.liteMode ? 'lite' : 'query'}:${this.subscriptionID}:${this.currentBlock}`; 179 | 180 | let pauseTimeOut: ReturnType; 181 | 182 | const self = this; 183 | this.subscription = self.client.centrifuge.newSubscription(channel); 184 | this.paused = false; 185 | 186 | function pauseProcessing() { 187 | return setTimeout(() => { 188 | // @ts-ignore 189 | const queueLength = self.subscriptionQueue.length; 190 | if (queueLength < self.MaxQueueSize / 2) { 191 | self.subscription?.publish({ cmd: 'start' }); 192 | self.paused = false; 193 | } else { 194 | pauseTimeOut = pauseProcessing(); 195 | } 196 | }, 2000); 197 | } 198 | 199 | this.subscription 200 | .on('publication', (ctx) => { 201 | if (this.onPublish) { 202 | const tx = self.processTransaction(self, ctx); 203 | this.subscriptionQueue.push(tx) 204 | // @ts-ignore 205 | const queueLength = self.subscriptionQueue.length; 206 | if (queueLength > self.MaxQueueSize) { 207 | if (!self.paused) { 208 | self.subscription?.publish({ cmd: 'pause' }); 209 | self.paused = true; 210 | if (self.onStatus) { 211 | self.onStatus({ 212 | statusCode: ControlMessageStatusCode.PAUSED, 213 | status: "paused subscription", 214 | message: "paused subscription to catch up", 215 | } as ControlMessage) 216 | } 217 | } 218 | if (pauseTimeOut) { 219 | clearTimeout(pauseTimeOut); 220 | } 221 | pauseTimeOut = pauseProcessing(); 222 | } 223 | } 224 | }) 225 | .on("subscribed", function (ctx) { 226 | self.subscribed = true; 227 | }) 228 | .on("state", function (ctx) { 229 | if (ctx.oldState === "subscribed" && ctx.newState === "subscribing") { 230 | // make sure we are subscribing to the correct channel 231 | const ch = ctx.channel.split(":"); 232 | if (ch[2]?.match(/^\d+$/) && Number(ch[2]) !== self.currentBlock) { 233 | // reset this subscription to set the correct block height 234 | self.unsubscribeTransactionBlocks(); 235 | self.subscribeTransactionBlocks(); 236 | } 237 | } 238 | }) 239 | .on("error", function (error) { 240 | self.error = error; 241 | if (self.onError) { 242 | self.onError(error); 243 | } 244 | }); 245 | 246 | this.subscription?.subscribe(); 247 | } 248 | 249 | protected processTransaction(self: this, ctx: PublicationContext) { 250 | if (self.client.protocol === "protobuf") { 251 | const Message = ProtobufRoot.lookupType("Transaction"); 252 | const message = Message.decode(ctx.data) as unknown as TransactionMessage; 253 | return { 254 | ...message, 255 | transaction: toHexString(message.transaction), 256 | // merkle proofs are missing from mempool transactions 257 | merkle_proof: message.merkle_proof ? toHexString(message.merkle_proof) : '', 258 | }; 259 | } else { 260 | return { 261 | ...ctx.data, 262 | // transactions can be missing, which means they are stored in S3 263 | transaction: ctx.data.transaction ? typeof Buffer !== "undefined" ? Buffer.from(ctx.data.transaction, 'base64').toString('hex') : base64ToHex(ctx.data.transaction) : '', 264 | // merkle proofs are missing from mempool transactions 265 | merkle_proof: ctx.data.merkle_proof ? (typeof Buffer !== "undefined" ? Buffer.from(ctx.data.merkle_proof, 'base64').toString('hex') : base64ToHex(ctx.data.merkle_proof)) : '', 266 | }; 267 | } 268 | } 269 | 270 | /** 271 | * Get the current last block that was processed completely 272 | * 273 | * @return number 274 | */ 275 | GetCurrentBlock(): number { 276 | return this.currentBlock; 277 | } 278 | 279 | /** 280 | * Unsubscribe from this subscription 281 | * 282 | * @return void 283 | */ 284 | UnSubscribe(): void { 285 | if (this.subscription) { 286 | this.unsubscribeTransactionBlocks(); 287 | } 288 | 289 | if (this.mempoolSubscription) { 290 | this.mempoolSubscription.unsubscribe(); 291 | this.mempoolSubscription.removeAllListeners(); 292 | this.client.centrifuge.removeSubscription(this.mempoolSubscription); 293 | this.mempoolSubscription = undefined; 294 | this.mempoolSubscribed = false; 295 | } 296 | 297 | if (this.controlSubscription) { 298 | this.controlSubscription.unsubscribe(); 299 | this.controlSubscription.removeAllListeners(); 300 | this.client.centrifuge.removeSubscription(this.controlSubscription); 301 | this.controlSubscription = undefined; 302 | this.controlSubscribed = false; 303 | } 304 | } 305 | 306 | private unsubscribeTransactionBlocks() { 307 | if (this.subscription) { 308 | this.subscription.unsubscribe(); 309 | this.subscription.removeAllListeners(); 310 | this.client.centrifuge.removeSubscription(this.subscription); 311 | this.subscription = undefined; 312 | this.subscribed = false; 313 | } 314 | } 315 | } 316 | 317 | function toHexString(byteArray: Uint8Array) { 318 | return Array.from(byteArray, function (byte) { 319 | return ('0' + (byte & 0xFF).toString(16)).slice(-2); 320 | }).join('').toLowerCase(); 321 | } 322 | 323 | function base64ToHex(str: string) { 324 | const raw = atob(str); 325 | let result = ''; 326 | for (let i = 0; i < raw.length; i++) { 327 | const hex = raw.charCodeAt(i).toString(16); 328 | result += (hex.length === 2 ? hex : '0' + hex); 329 | } 330 | return result.toLowerCase(); 331 | } 332 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "target": "es6", 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "strict": true, 12 | "esModuleInterop": true, 13 | "declaration": true, 14 | "declarationDir": "dist/dts/", 15 | "emitDeclarationOnly": true 16 | } 17 | } 18 | --------------------------------------------------------------------------------