[
  {
    "path": ".eslintignore",
    "content": "test/fixtures\ntest/benchmark\nnode_modules\ncoverage\nbenchmark\n_lib\nexample"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"extends\": \"eslint-config-egg\"\n}\n"
  },
  {
    "path": ".github/workflows/ci-actions.yml",
    "content": "name: ci-actions\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n  merge_group:\njobs:\n  test:\n    runs-on: ubuntu-latest\n    environment: 'ci'\n    strategy:\n      matrix:\n        node-version: [16, 18, 20, 22, 24]\n    env:\n      ALI_SDK_ONS_ID: ${{ secrets.ALI_SDK_ONS_ID }}\n      ALI_SDK_ONS_SECRET: ${{ secrets.ALI_SDK_ONS_SECRET }}\n    steps:\n      - name: Start RocketMQ Services\n        run: |\n          docker network create rocketmq\n          docker run -d --name rocketmq-namesrv --network rocketmq -p 9876:9876 apache/rocketmq:5.3.2 sh mqnamesrv\n          echo \"Waiting for RocketMQ services to be ready...\"\n          sleep 20\n          docker logs rocketmq-namesrv\n          echo \"brokerIP1=127.0.0.1\" > broker.conf\n          broker_ports=(10911 10912)\n          for port in ${broker_ports[@]}; do\n            docker run -d --name rocketmq-broker-${port} --network rocketmq -p ${port}:${port} -e \"NAMESRV_ADDR=rocketmq-namesrv:9876\" -v ./broker.conf:/home/rocketmq/conf/broker.conf apache/rocketmq:5.3.2 sh mqbroker --enable-proxy -c /home/rocketmq/conf/broker.conf\n            echo \"Waiting for RocketMQ broker to be ready...\"\n            sleep 20\n            docker logs rocketmq-broker-${port}\n            docker exec rocketmq-broker-${port} sh mqadmin updatetopic -t TP_alions_test_topic -c DefaultCluster\n          done\n\n      - uses: actions/checkout@v5\n\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n\n      - name: npm install, build, and test\n        run: |\n          npm install\n          npm run ci\n\n      - name: Codecov\n        uses: codecov/codecov-action@v2\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\non:\n  push:\n    branches: [ master ]\n\npermissions:\n  contents: write\n  deployments: write\n  issues: write\n  pull-requests: write\n  id-token: write\n\njobs:\n  release:\n    name: NPM\n    uses: ali-sdk/github-actions/.github/workflows/npm-release.yml@master\n    secrets:\n      GIT_TOKEN: ${{ secrets.GIT_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n\n# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directory\n# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git\nnode_modules\n\nexample/.config.js\n.nyc_output/\n.idea\n"
  },
  {
    "path": "AUTHORS",
    "content": "# Ordered by date of first contribution.\n# Auto-generated by 'contributors' on Fri, 29 Sep 2017 01:42:55 GMT.\n# https://github.com/xingrz/node-contributors\n\nGaoXiaochen <gxcsoccer@users.noreply.github.com>\n宗羽 <xiaochen.gaoxc@alibaba-inc.com>\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## <small>3.12.2 (2025-12-08)</small>\n\n* fix: make auto release work again ([5b43dda](https://github.com/ali-sdk/ali-ons/commit/5b43dda))\n\n## <small>3.12.1 (2025-12-08)</small>\n\n* fix: retry fetching nameservers 3 times until yielding error (#118) ([bdc44eb](https://github.com/ali-sdk/ali-ons/commit/bdc44eb)), closes [#118](https://github.com/ali-sdk/ali-ons/issues/118)\n* chore: enable auto release (#119) ([669abdc](https://github.com/ali-sdk/ali-ons/commit/669abdc)), closes [#119](https://github.com/ali-sdk/ali-ons/issues/119)\n"
  },
  {
    "path": "History.md",
    "content": "\n3.12.0 / 2023-07-31\n==================\n\n**features**\n  * [[`32d4d09`](http://github.com/ali-sdk/ali-ons/commit/32d4d0969fb3f10f691e38a1202f06359516593f)] - feat: 支持shardingKey 客户端实现 (#109) (Orange Mi <<orangemiwj@gmail.com>>)\n\n3.11.0 / 2023-01-12\n==================\n\n**fixes**\n  * [[`15846d4`](http://github.com/ali-sdk/ali-ons/commit/15846d4176bb2e4ef7c4d7399b02affdbcb66d58)] - fix: update name server by throttle (#108) (Gao Yang <<82163514@qq.com>>)\n\n3.10.0 / 2022-06-20\n==================\n\n**features**\n  * [[`6bc5a62`](http://github.com/ali-sdk/ali-ons/commit/6bc5a6271afe5c274db36c961cccc36643b1ad3f)] - feat: support consumer filter message by sql92 (#105) (#106) (Hongcai Deng <<admin@dhchouse.com>>)\n\n3.9.0 / 2021-05-08\n==================\n\n**features**\n  * [[`483b4fc`](http://github.com/ali-sdk/ali-ons/commit/483b4fc64457554331f5c0654a4aa642ab8a0ccf)] - feat: support set unitName (#101) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n3.8.2 / 2021-01-28\n==================\n\n**fixes**\n  * [[`b6ab961`](http://github.com/ali-sdk/ali-ons/commit/b6ab96159ab584ca27ab3390eb91eb79dddb95e8)] - fix: 🐛 repeat pull on same queue (#100) (Hongcai Deng <<admin@dhchouse.com>>)\n\n3.8.1 / 2020-04-21\n==================\n\n**others**\n  * [[`836d9e5`](http://github.com/ali-sdk/ali-ons/commit/836d9e5fae2b9cb0a79f0cc7874b26a7939af791)] - chore: add more debug log (#87) (Hongcai Deng <<admin@dhchouse.com>>)\n  * [[`6851d8e`](http://github.com/ali-sdk/ali-ons/commit/6851d8efe0a2f91cf598440b350060abafdcbff7)] - chore: reduce error (#64) (Hongcai Deng <<admin@dhchouse.com>>)\n\n3.8.0 / 2020-04-14\n==================\n\n**features**\n  * [[`207280f`](http://github.com/ali-sdk/ali-ons/commit/207280f8f00f82d98a2fb5bd32a528c3fdc1c68f)] - feat: support reconsume message later (#97) (Gaara <<hubiao1003hf@163.com>>)\n\n3.7.2 / 2020-03-17\n==================\n\n**fixes**\n  * [[`637aee4`](http://github.com/ali-sdk/ali-ons/commit/637aee4424e26ffce2bb1398e2a587b0ce1d6d94)] - fix: random-publish-queue-select-start-point (#36) (Hongcai Deng <<admin@dhchouse.com>>)\n\n3.7.1 / 2020-03-12\n==================\n\n**fixes**\n  * [[`cb244b7`](http://github.com/ali-sdk/ali-ons/commit/cb244b7c089f1d30fa1388b4334f27742ada196a)] - fix: use co-gather instead of p-gather (#95) (Yiyu He <<dead_horse@qq.com>>)\n\n3.7.0 / 2019-10-16\n==================\n\n**features**\n  * [[`3889a0e`](http://github.com/ali-sdk/ali-ons/commit/3889a0e03637fbb4ce8501abee3bd45d70000e1b)] - feat: support nameAddr as an array (#91) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n3.6.5 / 2019-05-10\n==================\n\n**others**\n  * [[`92892df`](http://github.com/ali-sdk/ali-ons/commit/92892df3ca1a4f33bec36ebd5fcfc8b3a3251a45)] - chore: adjust pulling log format (#82) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n3.6.4 / 2019-05-10\n==================\n\n**fixes**\n  * [[`456cd1f`](http://github.com/ali-sdk/ali-ons/commit/456cd1f70d22161f51749f48737aa038e0b69f6a)] - fix: producer should format topic first (#81) (Hongcai Deng <<admin@dhchouse.com>>)\n\n3.6.3 / 2019-05-10\n==================\n\n**fixes**\n  * [[`d477806`](http://github.com/ali-sdk/ali-ons/commit/d47780628ef18be8fda5922c63c71b0bacc4c75c)] - fix: close socket while pulling message timeout (#80) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n**others**\n  * [[`d1ca295`](http://github.com/ali-sdk/ali-ons/commit/d1ca295a9a600da2cb3bee99c206aa03c92025cf)] - update RocketMQ link to https://rocketmq.apache.org (#79) (小雷 <<863837949@qq.com>>)\n\n3.6.2 / 2019-04-16\n==================\n\n**fixes**\n  * [[`cb385e3`](http://github.com/ali-sdk/ali-ons/commit/cb385e31c486877b625eb6b27a69245c1d43e65d)] - fix: namespace logic (#78) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n3.6.1 / 2019-04-15\n==================\n\n**fixes**\n  * [[`7e5c6a4`](http://github.com/ali-sdk/ali-ons/commit/7e5c6a4ccf3f488df0f0e41a9a7bcfb0aadefb14)] - fix: not set nameSrv by default (#77) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n3.6.0 / 2019-04-15\n==================\n\n**features**\n  * [[`2cd16be`](http://github.com/ali-sdk/ali-ons/commit/2cd16bed4608ca4235ad9eb5c211b9aad090a411)] - feat: support namespace (#75) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n**fixes**\n  * [[`0176223`](http://github.com/ali-sdk/ali-ons/commit/0176223b3fdd3a2e5a219fb02b03552764951588)] - fix: retry should process by broker (#73) (Hongcai Deng <<admin@dhchouse.com>>)\n\n**others**\n  * [[`35f9341`](http://github.com/ali-sdk/ali-ons/commit/35f934174077e6835284ca5cbebb3e171122fd11)] - chore: update test config (#74) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n3.5.1 / 2019-04-12\n==================\n\n3.5.0 / 2019-04-12\n==================\n\n**features**\n  * [[`073058b`](http://github.com/ali-sdk/ali-ons/commit/073058b9d82415f7063758e815f0d31b2b9d5a2b)] - feat: support delay deliver message (#65) (MarvinWilliam <<la805779602@hotmail.com>>)\n\n**fixes**\n  * [[`8ef9502`](http://github.com/ali-sdk/ali-ons/commit/8ef95027eef89456b17531b89761fa375168bfd6)] - fix: drop message if reconsumeTimes > maxReconsumeTimes (#72) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n**others**\n  * [[`a44d4d0`](http://github.com/ali-sdk/ali-ons/commit/a44d4d05919d68200e012442e7831e5b2217f808)] - chore: upgrade deps (#71) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n3.4.0 / 2019-03-13\n==================\n\n**features**\n  * [[`7dfe610`](http://github.com/ali-sdk/ali-ons/commit/7dfe6105f41d7478d587732e3ed9c14e2b96e1bb)] - feat: support set namesrv (#61) (Hongcai Deng <<admin@dhchouse.com>>)\n\n3.3.0 / 2018-12-14\n==================\n\n**features**\n  * [[`9acff9b`](http://github.com/ali-sdk/ali-ons/commit/9acff9b91c0325ed88ad634ae15161f62dceb574)] - feat: support consume back (#56) (Hongcai Deng <<admin@dhchouse.com>>)\n\n3.2.2 / 2018-11-20\n==================\n\n**fixes**\n  * [[`39e3782`](http://github.com/ali-sdk/ali-ons/commit/39e37827c8eac0c16e1fb5ecd64792da331673f3)] - fix: parse date format (#53) (Hongcai Deng <<admin@dhchouse.com>>)\n\n**others**\n  * [[`e2dc377`](http://github.com/ali-sdk/ali-ons/commit/e2dc377babb3a420e2dfc99a33bf6118979825ff)] - test: fix ci (#54) (zōng yǔ <<gxcsoccer@users.noreply.github.com>>)\n\n3.2.1 / 2018-10-18\n==================\n\n**fixes**\n  * [[`d6b6fc6`](http://github.com/ali-sdk/ali-ons/commit/d6b6fc61d049c5925c65913c299d0b423573d4b6)] - fix: accessKeyID => accessKeyId (#50) (fengmk2 <<fengmk2@gmail.com>>)\n\n3.2.0 / 2018-10-17\n==================\n\n**features**\n  * [[`8ec58c8`](http://github.com/ali-sdk/ali-ons/commit/8ec58c8f773633a4f1bb0341306d89afda1972e5)] - feat: unify aliyun keys to accessKeyID and accessKeySecret (fengmk2 <<fengmk2@gmail.com>>)\n\n**others**\n  * [[`9ecd381`](http://github.com/ali-sdk/ali-ons/commit/9ecd381f7079d7be6f2551a6baabfc51cdefde46)] - f (fengmk2 <<fengmk2@gmail.com>>)\n  * [[`742f269`](http://github.com/ali-sdk/ali-ons/commit/742f269534c2e30f0772663ca3690ad000d9c75e)] - f (fengmk2 <<fengmk2@gmail.com>>)\n\n3.1.0 / 2018-09-14\n==================\n\n**others**\n  * [[`d1de082`](http://github.com/ali-sdk/ali-ons/commit/d1de0823fcc860ae12133a0026ddcfe2105db82d)] - name-server-fault-tolerance (wujia <<geoff.j.wu@gmail.com>>)\n\n3.0.0 / 2018-07-03\n==================\n\n**features**\n  * [[`5624319`](http://github.com/ali-sdk/ali-ons/commit/56243195ad838b88932be8b5e5c1f2f6a2eb3d4f)] - feat: suppory async (ngot <<zhuanghengfei@gmail.com>>)\n\n**others**\n  * [[`4b84644`](http://github.com/ali-sdk/ali-ons/commit/4b846446ceec7f175a363ef3dd011f67dc2dcaea)] - test: update ci command (Hongcai Deng <<admin@dhchouse.com>>)\n  * [[`ebb7942`](http://github.com/ali-sdk/ali-ons/commit/ebb7942269662843357ce4070a19628c6d51fe88)] - test: trigger ci (Hongcai Deng <<admin@dhchouse.com>>)\n  * [[`79b56c8`](http://github.com/ali-sdk/ali-ons/commit/79b56c86f03a957cad4442e52ddae8e49389ab57)] - breaking: async (Hongcai Deng <<admin@dhchouse.com>>)\n  * [[`1ee4785`](http://github.com/ali-sdk/ali-ons/commit/1ee47857f4846a4d70b33b60234972651d1c37d3)] - f (ngot <<zhuanghengfei@gmail.com>>)\n  * [[`e0a7a77`](http://github.com/ali-sdk/ali-ons/commit/e0a7a77e0830a04c020c7a2762f32a2f210ef426)] - f (ngot <<zhuanghengfei@gmail.com>>)\n  * [[`519e164`](http://github.com/ali-sdk/ali-ons/commit/519e1641e224deec08504ccf60ca4fa05e01788b)] - f (ngot <<zhuanghengfei@gmail.com>>)\n  * [[`1de2b1a`](http://github.com/ali-sdk/ali-ons/commit/1de2b1a6cdc46fd19faeceda50d89c2f2928144d)] - doc: updaet readme (ngot <<zhuanghengfei@gmail.com>>)\n\n2.0.4 / 2018-01-10\n==================\n\n**fixes**\n  * [[`6f78013`](http://github.com/ali-sdk/ali-ons/commit/6f780131b713465827588ac3ee866b9e2b2bd2ae)] - fix: fix invokeOneWay issue (gxcsoccer <<gxcsoccer@126.com>>)\n\n**others**\n  * [[`46d1bfe`](http://github.com/ali-sdk/ali-ons/commit/46d1bfe4cd5af83aa814d2a6dfd490efde5172bc)] - chore: release 2.0.3 (gxcsoccer <<gxcsoccer@126.com>>),\n\n2.0.3 / 2017-11-11\n==================\n\n  * fix: memory leak may occurred cause by Promise.race\n  * doc: fix consumer and producer initialize issue\n\n2.0.2 / 2017-10-09\n==================\n\n  * fix: persist consumer offset issue\n\n2.0.1 / 2017-09-29\n==================\n\n  * fix: support subscribe before ready\n\n2.0.0 / 2017-09-29\n==================\n\n  * feat: add local memory store\n  * refactor: new consumer api\n  * fix: consumer offset update issue\n\n1.0.0 / 2017-03-14\n==================\n\n  * feat: implement ali-ons base on rocketmq #1\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) ali-sdk\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# ali-ons\n\n[![NPM version][npm-image]][npm-url]\n[![ci-actions][ci-image]][ci-url]\n[![npm download][download-image]][download-url]\n[![Node.js Version](https://img.shields.io/node/v/ali-ons.svg?style=flat)](https://nodejs.org/en/download/)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)\n![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/ali-sdk/ali-ons)\n\n[npm-image]: https://img.shields.io/npm/v/ali-ons.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/ali-ons\n[ci-image]: https://github.com/ali-sdk/ali-ons/actions/workflows/ci-actions.yml/badge.svg\n[ci-url]: https://github.com/ali-sdk/ali-ons/actions/workflows/ci-actions.yml\n[download-image]: https://img.shields.io/npm/dm/ali-ons.svg?style=flat-square\n[download-url]: https://npmjs.org/package/ali-ons\n\nAliyun Open Notification Service Client (base on opensource project [RocketMQ](https://rocketmq.apache.org/))\n\nSub module of [ali-sdk](https://github.com/ali-sdk/ali-sdk).\n\n## Install\n\n```bash\nnpm install ali-ons --save\n```\n\n## Usage\n\nconsumer\n\n```js\n'use strict';\n\nconst httpclient = require('urllib');\nconst Consumer = require('ali-ons').Consumer;\nconst consumer = new Consumer({\n  httpclient,\n  accessKeyId: 'your-accessKeyId',\n  accessKeySecret: 'your-AccessKeySecret',\n  consumerGroup: 'your-consumer-group',\n  // namespace: '', // aliyun namespace support\n  // isBroadcast: true,\n});\n\nconsumer.subscribe(config.topic, '*', async msg => {\n  console.log(`receive message, msgId: ${msg.msgId}, body: ${msg.body.toString()}`)\n  // return Consumer.ACTION_RETRY; // you can return ACTION_RETRY, then this message will be directly retried\n});\n\nconsumer.on('error', err => console.log(err));\n```\n\nIf you want to use sql filter, you can subscribe a topic with a sql expression:\n```js\nconsumer.subscribe(\n  config.topic,\n  {\n    expressionType: 'SQL92',\n    subString: 'a is not null'\n  },\n  async msg => {\n    console.log(`receive message, msgId: ${msg.msgId}, body: ${msg.body.toString()}`)\n  }\n);\n```\nFor more information about sql filter, see: [Filter Messages By SQL92](https://rocketmq.apache.org/rocketmq/filter-messages-by-sql92-in-rocketmq/)\n\nproducer\n\n```js\n'use strict';\nconst httpclient = require('urllib');\nconst Producer = require('ali-ons').Producer;\nconst Message = require('ali-ons').Message;\n\nconst producer = new Producer({\n  httpclient,\n  accessKeyId: 'your-accessKeyId',\n  accessKeySecret: 'your-AccessKeySecret',\n  producerGroup: 'your-producer-group',\n  // namespace: '', // aliyun namespace support\n});\n\n(async () => {\n  const msg = new Message('your-topic', // topic\n    'TagA', // tag\n    'Hello ONS !!! ' // body\n  );\n\n  // set Message#keys\n  msg.keys = ['key1'];\n\n  // delay consume\n  // msg.setStartDeliverTime(Date.now() + 5000);\n\n  const sendResult = await producer.send(msg);\n  console.log(sendResult);\n})().catch(err => console.error(err))\n```\n\n## Secure Keys\n\nPlease contact to @gxcsoccer to give you accessKey\n\n- [ons secure data](https://sharelock.io/1/UM02CJiYyhXiZDOn1nhX0iqPqMIQtdwI_T5BY3F-tHs.d8-ycA/01veKH9kgAuFuKCqlVPzGsyPWJ8mQLaKPJjjcB9tpdbvi9L6XQ/IgqDvAdVDMzV9lK2gQzyAj7q-CNk8-1tWrLmdqMV0oJ5qgky40/HgpZyKKDfOGAcyqQ20RUdRgCLRWqF8LUUko0uDl_L-ATNOsi5z/W2bsvBc8tAoqSwNR7u2Sqe6XkNmD98s3UQOK-6T8--VwTbHzcG/dwkHwie3EkGB-TbiMnbRh7_5A-DaOTCtALP3xvl4G0XKxuOriC/2yfuPp7WRucTAoqx2STO5Hv3MZEhh3IXf7YiOQ8pWDDqjLuQSY/_irqzYyeseY9m106ksMUq3-yS_qkBRIuoyL-hHk9ZRhGppsdA5/Dw4Pjg.fmNP3aFkLnvPuhlPRwNcng)\n\n## License\n\n[MIT](LICENSE)\n\n## Contributors\n\n[![Contributors](https://contrib.rocks/image?repo=ali-sdk/ali-ons)](https://github.com/ali-sdk/ali-ons/graphs/contributors)\n\nMade with [contributors-img](https://contrib.rocks).\n"
  },
  {
    "path": "example/config.js",
    "content": "'use strict';\n\nconst env = process.env;\n\n// export ALI_SDK_ONS_ID=your-accesskey\n// export ALI_SDK_ONS_SECRET=your-secretkey\n\nmodule.exports = {\n  accessKeyId: env.ALI_SDK_ONS_ID,\n  accessKeySecret: env.ALI_SDK_ONS_SECRET,\n  producerGroup: 'PID_GXCSOCCER',\n  consumerGroup: 'GID_alions',\n  topic: 'TP_alions_test_topic',\n  // https://help.aliyun.com/document_detail/102895.html 阿里云产品更新，支持实例化\n  nameSrv: 'localhost:9876',\n  // onsAddr: 'http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet',\n};\n"
  },
  {
    "path": "example/consumer.js",
    "content": "'use strict';\n\nconst httpclient = require('urllib');\nconst logger = require('./logger');\nconst Consumer = require('../').Consumer;\nconst config = require('./config');\nconst consumer = new Consumer(Object.assign(config, {\n  httpclient,\n  logger,\n  maxReconsumeTimes: 1,\n  // isBroadcast: true,\n  // consumeFromWhere: 'CONSUME_FROM_LAST_OFFSET_AND_FROM_MIN_WHEN_BOOT_FIRST',\n}));\n\nconsumer.subscribe(config.topic, '*', async function(msg) {\n  console.log(`receive message, msgId: ${msg.msgId}, body: ${msg.body.toString()}`)\n  // throw new Error(123);\n});\n\nconsumer.on('error', err => console.log(err));\n"
  },
  {
    "path": "example/logger.js",
    "content": "'use strict';\n\nmodule.exports = {\n  info() {},\n  warn() {},\n  error(...args) {\n    console.error(...args);\n  },\n  debug() {},\n};\n"
  },
  {
    "path": "example/producer.js",
    "content": "'use strict';\n\nconst logger = require('./logger');\nconst config = require('./config');\nconst httpclient = require('urllib');\nconst Producer = require('../').Producer;\nconst Message = require('../').Message;\n\nconst producer = new Producer(Object.assign({ httpclient, logger }, config));\n(async () => {\n  try {\n    await producer.ready();\n    const msg = new Message(config.topic, // topic\n      'TagA', // tag\n      'Hello ONS !!! ' // body\n    );\n\n    const sendResult = await producer.send(msg);\n    console.log(sendResult);\n  } catch (err) {\n    console.error(err)\n  }\n})();\n"
  },
  {
    "path": "lib/channel.js",
    "content": "'use strict';\n\nconst is = require('is-type-of');\nconst crypto = require('crypto');\nconst Base = require('tcp-base');\nconst promisify = require('util').promisify;\nconst RemotingCommand = require('./protocol/command/remoting_command');\n\nclass Channel extends Base {\n  /**\n   * rocketmq tcp channel object\n   * @param {String} address - server address\n   * @param {Object} options\n   *   - {String} accessKey\n   *   - {String} secretKey\n   *   - {String} onsChannel\n   * @class\n   */\n  constructor(address, options = {}) {\n    // 10.18.214.201:8080\n    const arr = address.split(':');\n    // support alias: accessKeyId and accessKeySecret\n    options.accessKey = options.accessKey || options.accessKeyId;\n    options.secretKey = options.secretKey || options.accessKeySecret;\n    super(Object.assign({\n      host: arr[0],\n      port: arr[1],\n      headerLength: 4,\n      needHeartbeat: false,\n    }, options));\n    this.sendPromise = promisify(this.send);\n  }\n\n  get accessKey() {\n    return this.options.accessKey;\n  }\n\n  get secretKey() {\n    return this.options.secretKey;\n  }\n\n  get onsChannel() {\n    return 'ALIYUN';\n  }\n\n  /**\n   * Get packet length from header\n   * @param {Buffer} header - packet header\n   * @return {Number} bodyLength\n   */\n  getBodyLength(header) {\n    return header.readInt32BE(0);\n  }\n\n  decode(body, header) {\n    const command = RemotingCommand.decode(Buffer.concat([ header, body ]));\n    return {\n      id: command.opaque,\n      isResponse: command.isResponseType,\n      data: command,\n    };\n  }\n\n  beforeRequest(command) {\n    if (!this.accessKey || !this.secretKey) {\n      return;\n    }\n\n    const header = command.customHeader;\n    const map = new Map();\n    map.set('AccessKey', this.accessKey);\n    map.set('OnsChannel', this.onsChannel);\n    if (header) {\n      for (const field in header) {\n        if (!is.nullOrUndefined(header[field])) {\n          map.set(field, header[field].toString());\n        }\n      }\n    }\n\n    let val = '';\n    const fields = Array.from(map.keys()).sort();\n    for (const key of fields) {\n      if (key !== 'Signature') {\n        val += map.get(key);\n      }\n    }\n    let total = Buffer.from(val, 'utf8');\n    const bodyLength = command.body ? command.body.length : 0;\n    if (bodyLength) {\n      total = Buffer.concat([ total, command.body ], total.length + bodyLength);\n    }\n    const hmac = crypto.createHmac('sha1', this.secretKey);\n    const signature = hmac.update(total).digest('base64');\n    command.extFields.Signature = signature;\n    command.extFields.AccessKey = this.accessKey;\n    command.extFields.OnsChannel = this.onsChannel;\n  }\n\n  /**\n   * invoke rocketmq api\n   * @param {RemotingCommand} command - remoting command\n   * @param {Number} timeout - response timeout\n   * @return {Promise<Object>} response\n   */\n  invoke(command, timeout) {\n    this.beforeRequest(command);\n    return this.sendPromise({\n      id: command.opaque,\n      data: command.encode(),\n      timeout,\n    }).catch(err => {\n      // TODO: not sure whether is work ?\n      if (err.name === 'ResponseTimeoutError') {\n        this.close();\n      }\n      throw err;\n    });\n  }\n\n  /**\n   * invoke rocketmq api without need response\n   * @param {RemotingCommand} command - remoting command\n   * @return {Promise} Promise\n   */\n  invokeOneway(command) {\n    this.beforeRequest(command);\n    return this.sendPromise({\n      id: command.opaque,\n      data: command.encode(),\n      oneway: true,\n    });\n  }\n}\n\nmodule.exports = Channel;\n"
  },
  {
    "path": "lib/client_config.js",
    "content": "'use strict';\n\nconst Base = require('sdk-base');\nconst address = require('address');\nconst MixAll = require('./mix_all');\n\nconst defaultOptions = {\n  instanceName: 'DEFAULT',\n  pollNameServerInteval: 30 * 1000,\n  heartbeatBrokerInterval: 30 * 1000,\n  persistConsumerOffsetInterval: 5 * 1000,\n  rebalanceInterval: 10 * 1000,\n  clientIP: address.ip(),\n  unitMode: false,\n  unitName: null,\n  // 阿里云自创建实例，需要 ns 前缀\n  namespace: '',\n  // 公有云生产环境：http://onsaddr-internal.aliyun.com:8080/rocketmq/nsaddr4client-internal\n  // 公有云公测环境：http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet\n  // 杭州金融云环境：http://jbponsaddr-internal.aliyun.com:8080/rocketmq/nsaddr4client-internal\n  // 杭州深圳云环境：http://mq4finance-sz.addr.aliyun.com:8080/rocketmq/nsaddr4client-internal\n  onsAddr: 'http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet',\n  // https://help.aliyun.com/document_detail/102895.html 阿里云产品更新，支持实例化\n  // nameSrv: 'onsaddr.mq-internet-access.mq-internet.aliyuncs.com:80',\n  onsChannel: 'ALIYUN', // CLOUD, ALIYUN, ALL\n};\n\nclass ClientConfig extends Base {\n\n  /**\n   * Producer 与 Consumer的公共配置\n   * @param {Object} options\n   *  - {String} instanceName 示例名称\n   *  - {Number} pollNameServerInteval name server 同步的间隔\n   *  - {Number} heartbeatBrokerInterval 心跳的间隔\n   *  - {Number} persistConsumerOffsetInterval 持久化消费进度的间隔\n   *  - {Boolean} unitMode 是否为单元化的订阅组\n   */\n  constructor(options) {\n    super(Object.assign({}, defaultOptions, options));\n    this.instanceName = this.options.instanceName;\n  }\n\n  get clientId() {\n    return this.unitName ?\n      `${this.options.clientIP}@${this.instanceName}@${this.unitName}` :\n      `${this.options.clientIP}@${this.instanceName}`;\n  }\n\n  get pollNameServerInteval() {\n    return this.options.pollNameServerInteval;\n  }\n\n  get heartbeatBrokerInterval() {\n    return this.options.heartbeatBrokerInterval;\n  }\n\n  get persistConsumerOffsetInterval() {\n    return this.options.persistConsumerOffsetInterval;\n  }\n\n  get rebalanceInterval() {\n    return this.options.rebalanceInterval;\n  }\n\n  get unitMode() {\n    return this.options.unitMode;\n  }\n\n  get unitName() {\n    return this.options.unitName;\n  }\n\n  get namespace() {\n    return this.options.namespace;\n  }\n\n  formatTopic(topic) {\n    if (this.namespace && (!topic.startsWith(this.namespace) &&\n        !topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX))) {\n      topic = `${this.namespace}%${topic}`;\n    }\n    return topic;\n  }\n\n  /**\n   * 将实例名修改为进程号\n   */\n  changeInstanceNameToPID() {\n    if (this.instanceName === 'DEFAULT') {\n      this.instanceName = process.pid + '';\n    }\n  }\n}\n\nmodule.exports = ClientConfig;\n"
  },
  {
    "path": "lib/consumer/consume_from_where.js",
    "content": "'use strict';\n\nmodule.exports = {\n  /**\n   * 一个新的订阅组第一次启动从队列的最后位置开始消费<br>\n   * 后续再启动接着上次消费的进度开始消费\n   */\n  CONSUME_FROM_LAST_OFFSET: 'CONSUME_FROM_LAST_OFFSET',\n\n  CONSUME_FROM_LAST_OFFSET_AND_FROM_MIN_WHEN_BOOT_FIRST: 'CONSUME_FROM_LAST_OFFSET_AND_FROM_MIN_WHEN_BOOT_FIRST',\n  CONSUME_FROM_MIN_OFFSET: 'CONSUME_FROM_MIN_OFFSET',\n  CONSUME_FROM_MAX_OFFSET: 'CONSUME_FROM_MAX_OFFSET',\n  /**\n   * 一个新的订阅组第一次启动从队列的最前位置开始消费<br>\n   * 后续再启动接着上次消费的进度开始消费\n   */\n  CONSUME_FROM_FIRST_OFFSET: 'CONSUME_FROM_FIRST_OFFSET',\n  /**\n   * 一个新的订阅组第一次启动从指定时间点开始消费<br>\n   * 后续再启动接着上次消费的进度开始消费<br>\n   * 时间点设置参见DefaultMQPushConsumer.consumeTimestamp参数\n   */\n  CONSUME_FROM_TIMESTAMP: 'CONSUME_FROM_TIMESTAMP',\n};\n"
  },
  {
    "path": "lib/consumer/mq_push_consumer.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst is = require('is-type-of');\nconst utils = require('../utils');\nconst logger = require('../logger');\nconst MixAll = require('../mix_all');\nconst MQClient = require('../mq_client');\nconst MQProducer = require('../producer/mq_producer');\nconst sleep = require('mz-modules/sleep');\nconst ClientConfig = require('../client_config');\nconst ProcessQueue = require('../process_queue');\nconst PullStatus = require('../consumer/pull_status');\nconst PullSysFlag = require('../utils/pull_sys_flag');\nconst ConsumeFromWhere = require('./consume_from_where');\nconst MessageModel = require('../protocol/message_model');\nconst ConsumeType = require('../protocol/consume_type');\nconst ReadOffsetType = require('../store/read_offset_type');\nconst LocalFileOffsetStore = require('../store/local_file');\nconst LocalMemoryOffsetStore = require('../store/local_memory');\nconst RemoteBrokerOffsetStore = require('../store/remote_broker');\nconst AllocateMessageQueueAveragely = require('./rebalance/allocate_message_queue_averagely');\nconst Message = require('../message/message');\nconst MessageConst = require('../message/message_const');\n\nconst defaultOptions = {\n  logger,\n  persistent: false, // 是否持久化消费进度\n  isBroadcast: false, // 是否是广播模式（默认集群消费模式）\n  brokerSuspendMaxTimeMillis: 1000 * 15, // 长轮询模式，Consumer连接在Broker挂起最长时间\n  pullTimeDelayMillsWhenException: 3000, // 拉消息异常时，延迟一段时间再拉\n  pullTimeDelayMillsWhenFlowControl: 5000, // 进入流控逻辑，延迟一段时间再拉\n  consumerTimeoutMillisWhenSuspend: 1000 * 30, // 长轮询模式，Consumer超时时间（必须要大于brokerSuspendMaxTimeMillis）\n  consumerGroup: MixAll.DEFAULT_CONSUMER_GROUP,\n  consumeFromWhere: ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET, // Consumer第一次启动时，从哪里开始消费\n  /**\n   * Consumer第一次启动时，如果回溯消费，默认回溯到哪个时间点，数据格式如下，时间精度秒：\n   * 20131223171201\n   * 表示2013年12月23日17点12分01秒\n   * 默认回溯到相对启动时间的半小时前\n   */\n  consumeTimestamp: utils.timeMillisToHumanString(Date.now() - 1000 * 60 * 30),\n  pullThresholdForQueue: 500, // 本地队列消息数超过此阀值，开始流控\n  pullInterval: 0, // 拉取消息的频率, 如果为了降低拉取速度，可以设置大于0的值\n  consumeMessageBatchMaxSize: 1, // 消费一批消息，最大数\n  pullBatchSize: 32, // 拉消息，一次拉多少条\n  parallelConsumeLimit: 1, // 并发消费消息限制\n  postSubscriptionWhenPull: true, // 是否每次拉消息时，都上传订阅关系\n  allocateMessageQueueStrategy: new AllocateMessageQueueAveragely(), // 队列分配算法，应用可重写\n  maxReconsumeTimes: 16, // 最大重试次数\n};\n\nclass MQPushConsumer extends ClientConfig {\n  constructor(options) {\n    assert(options && options.consumerGroup, '[MQPushConsumer] options.consumerGroup is required');\n    const mergedOptions = Object.assign({ initMethod: 'init' }, defaultOptions, options);\n    assert(mergedOptions.parallelConsumeLimit <= mergedOptions.pullBatchSize,\n      '[MQPushConsumer] options.parallelConsumeLimit must lte pullBatchSize');\n    super(mergedOptions);\n\n    // @example:\n    // pullFromWhichNodeTable => {\n    //   '[topic=\"TEST_TOPIC\", brokerName=\"qdinternet-03\", queueId=\"1\"]': 0\n    // }\n    this._pullFromWhichNodeTable = new Map();\n    this._subscriptions = new Map();\n    this._handles = new Map();\n    this._topicSubscribeInfoTable = new Map();\n    this._processQueueTable = new Map();\n    this._inited = false;\n    this._isClosed = false;\n\n    if (this.messageModel === MessageModel.CLUSTERING) {\n      this.changeInstanceNameToPID();\n    }\n\n    this._mqClient = MQClient.getAndCreateMQClient(this);\n    this._offsetStore = this.newOffsetStoreInstance();\n\n    this._mqClient.on('error', err => this._handleError(err));\n    this._offsetStore.on('error', err => this._handleError(err));\n  }\n\n  get logger() {\n    return this.options.logger;\n  }\n\n  get subscriptions() {\n    return this._subscriptions;\n  }\n\n  get processQueueTable() {\n    return this._processQueueTable;\n  }\n\n  get parallelConsumeLimit() {\n    return this.options.parallelConsumeLimit;\n  }\n\n  get consumerGroup() {\n    if (this.namespace) {\n      return `${this.namespace}%${this.options.consumerGroup}`;\n    }\n    return this.options.consumerGroup;\n  }\n\n  get messageModel() {\n    return this.options.isBroadcast ? MessageModel.BROADCASTING : MessageModel.CLUSTERING;\n  }\n\n  get consumeType() {\n    return ConsumeType.CONSUME_PASSIVELY;\n  }\n\n  get consumeFromWhere() {\n    return this.options.consumeFromWhere;\n  }\n\n  get allocateMessageQueueStrategy() {\n    return this.options.allocateMessageQueueStrategy;\n  }\n\n  async init() {\n    this._mqClient.registerConsumer(this.consumerGroup, this);\n    await MQProducer.getDefaultProducer(this.options);\n    await this._mqClient.ready();\n    await this._offsetStore.load();\n    this.logger.info('[mq:consumer] consumer started');\n    this._inited = true;\n\n    // 订阅重试 TOPIC\n    if (this.messageModel === MessageModel.CLUSTERING) {\n      const retryTopic = MixAll.getRetryTopic(this.consumerGroup);\n      this.subscribe(retryTopic, '*', async msg => {\n        const originTopic = msg.retryTopic;\n        const originMsgId = msg.originMessageId;\n        const subscription = this._subscriptions.get(originTopic) || {};\n        const handler = subscription.handler;\n        if (!MixAll.isRetryTopic(originTopic) && handler) {\n          await handler(msg);\n        } else {\n          this.logger.warn('[MQPushConsumer] retry message no handler, originTopic: %s, originMsgId: %s, msgId: %s',\n            originTopic,\n            originMsgId,\n            msg.msgId);\n        }\n      });\n    }\n  }\n\n  /**\n   * close the consumer\n   */\n  async close() {\n    this._isClosed = true;\n    await this.persistConsumerOffset();\n    this._pullFromWhichNodeTable.clear();\n    this._subscriptions.clear();\n    this._topicSubscribeInfoTable.clear();\n    this._processQueueTable.clear();\n\n    await this._mqClient.unregisterConsumer(this.consumerGroup);\n    await this._mqClient.close();\n    this.logger.info('[mq:consumer] consumer closed');\n    this.emit('close');\n  }\n\n  newOffsetStoreInstance() {\n    if (this.messageModel === MessageModel.BROADCASTING) {\n      if (this.options.persistent) {\n        return new LocalFileOffsetStore(this._mqClient, this.consumerGroup);\n      }\n      return new LocalMemoryOffsetStore(this._mqClient, this.consumerGroup);\n    }\n    return new RemoteBrokerOffsetStore(this._mqClient, this.consumerGroup);\n  }\n\n  /**\n   * subscribe\n   * @param {String} topic - topic\n   * @param {String} subExpression - tag\n   * @param {Function} handler - message handler\n   * @return {void}\n   */\n  subscribe(topic, subExpression, handler) {\n    // 添加 namespace 前缀\n    topic = this.formatTopic(topic);\n\n    if (arguments.length === 2) {\n      handler = subExpression;\n      subExpression = null;\n    }\n    assert(is.asyncFunction(handler), '[MQPushConsumer] handler should be a asyncFunction');\n    assert(!this.subscriptions.has(topic), `[MQPushConsumer] ONLY one handler allowed for topic=${topic}`);\n\n    const subscriptionData = this.buildSubscriptionData(this.consumerGroup, topic, subExpression);\n    const tagsSet = subscriptionData.tagsSet;\n    const needFilter = !!tagsSet.length;\n    this.subscriptions.set(topic, {\n      handler,\n      subscriptionData,\n    });\n\n    (async () => {\n      try {\n        await this.ready();\n        // 如果 topic 没有路由信息，先更新一下\n        if (!this._topicSubscribeInfoTable.has(topic)) {\n          await this._mqClient.updateAllTopicRouterInfo();\n          await this._mqClient.sendHeartbeatToAllBroker();\n          await this._mqClient.doRebalance();\n        }\n\n        // 消息消费循环\n        while (!this._isClosed && this.subscriptions.has(topic)) {\n          await this._consumeMessageLoop(topic, needFilter, tagsSet, subExpression);\n        }\n\n      } catch (err) {\n        this._handleError(err);\n      }\n    })();\n\n    this.logger.info('[MQPushConsumer] cancel subscribe for topic=%s, subExpression=%s', topic, subExpression);\n  }\n\n  async _consumeMessageLoop(topic, needFilter, tagsSet, subExpression) {\n    const mqList = this._topicSubscribeInfoTable.get(topic);\n    let hasMsg = false;\n    if (mqList && mqList.length) {\n      for (const mq of mqList) {\n        const item = this._processQueueTable.get(mq.key);\n        if (item) {\n          const pq = item.processQueue;\n          this.logger.debug('[MQPushConsumer] process msg for processQueue=%s, msgCount=%', mq.key, pq.msgCount);\n          while (pq.msgCount) {\n            hasMsg = true;\n            let msgs;\n            if (this.parallelConsumeLimit > pq.msgCount) {\n              msgs = pq.msgList.slice(0, pq.msgCount);\n            } else {\n              msgs = pq.msgList.slice(0, this.parallelConsumeLimit);\n            }\n            // 并发消费任务\n            const consumeTasks = [];\n            for (const msg of msgs) {\n              const handler = this._subscriptions.get(msg.topic).handler;\n              if (!msg.tags || !needFilter || tagsSet.includes(msg.tags)) {\n                consumeTasks.push(this.consumeSingleMsg(handler, msg, mq, pq));\n              } else {\n                this.logger.debug('[MQPushConsumer] message filter by tags=, msg.tags=%s', subExpression, msg.tags);\n              }\n            }\n            // 必须全部成功\n            try {\n              await Promise.all(consumeTasks);\n            } catch (err) {\n              continue;\n            }\n            // 注意这里必须是批量确认\n            const offset = pq.remove(msgs.length);\n            if (offset >= 0) {\n              this._offsetStore.updateOffset(mq, offset, true);\n            }\n          }\n        }\n      }\n    }\n\n    if (!hasMsg) {\n      const changedEvent = `topic_${topic}_changed`;\n      this.logger.debug(`[MQPushConsumer] waiting event for ${changedEvent}`);\n      await this.await(changedEvent);\n    }\n  }\n\n  async consumeSingleMsg(handler, msg, mq, pq) {\n    // 集群消费模式下，如果消费失败，反复重试\n    while (!this._isClosed) {\n      if (msg.reconsumeTimes > this.options.maxReconsumeTimes) {\n        this.logger.warn('[MQPushConsumer] consume message failed, drop it for reconsumeTimes=%d and maxReconsumeTimes=%d, msgId: %s, originMsgId: %s',\n          msg.reconsumeTimes, this.options.maxReconsumeTimes, msg.msgId, msg.originMessageId);\n        return;\n      }\n\n      utils.resetRetryTopic(msg, this.consumerGroup);\n\n      try {\n        const value = await handler(msg, mq, pq);\n        if (value !== MQPushConsumer.ACTION_RETRY) {\n          return;\n        }\n      } catch (err) {\n        err.message = `process mq message failed, topic: ${msg.topic}, msgId: ${msg.msgId}, ${err.message}`;\n        this.emit('error', err);\n      }\n\n      if (this.messageModel === MessageModel.CLUSTERING) {\n        // 发送重试消息\n        try {\n          // delayLevel 为 0 代表由服务端控制重试间隔\n          await this.sendMessageBack(msg, 0, mq.brokerName, this.consumerGroup);\n          return;\n        } catch (err) {\n          this.emit('error', err);\n          this.logger.error(\n            '[MQPushConsumer] send reconsume message failed, fallback to local retry, msgId: %s',\n            msg.msgId\n          );\n          // 重试消息发送失败，本地重试\n          await this._sleep(5000);\n        }\n        // 本地重试情况下需要给 reconsumeTimes +1\n        msg.reconsumeTimes++;\n      } else {\n        this.logger.warn('[MQPushConsumer] BROADCASTING consume message failed, drop it, msgId: %s', msg.msgId);\n        return;\n      }\n    }\n    this.logger.info('[MQPushConsumer] consumer is closed, skip consume msg, msgId=%s', msg.msgId);\n  }\n\n  /**\n   * construct subscription data\n   * @param {String} consumerGroup - consumer group name\n   * @param {String} topic - topic\n   * @param {String} subString - tag\n   * @return {Object} subscription\n   */\n  buildSubscriptionData(consumerGroup, topic, subString) {\n    const subscriptionData = {\n      topic,\n      subString,\n      classFilterMode: false,\n      tagsSet: [],\n      codeSet: [],\n      subVersion: Date.now(),\n    };\n    if (is.nullOrUndefined(subString) || subString === '*' || subString === '') {\n      subscriptionData.subString = '*';\n    } else if (typeof subString === 'object') {\n      subscriptionData.expressionType = subString.expressionType;\n      subscriptionData.subString = subString.subString;\n    } else {\n      const tags = subString.split('||');\n      for (let tag of tags) {\n        tag = tag.trim();\n        if (tag) {\n          subscriptionData.tagsSet.push(tag);\n          subscriptionData.codeSet.push(utils.hashCode(tag));\n        }\n      }\n    }\n    return subscriptionData;\n  }\n\n  async persistConsumerOffset() {\n    const mqs = [];\n    for (const key of this._processQueueTable.keys()) {\n      if (this._processQueueTable.get(key)) {\n        mqs.push(this._processQueueTable.get(key).messageQueue);\n      }\n    }\n    await this._offsetStore.persistAll(mqs);\n  }\n\n  /**\n   * pull message from queue\n   * @param {MessageQueue} messageQueue - message queue\n   * @param {ProcessQueue} processQueue - process queue\n   * @return {void}\n   */\n  pullMessageQueue(messageQueue, processQueue) {\n    const _self = this;\n    (async () => {\n      while (!_self._isClosed) {\n        const pullRequest = _self._processQueueTable.get(messageQueue.key);\n        if (!pullRequest) {\n          break;\n        }\n        if (pullRequest.processQueue !== processQueue) {\n          // 不是同一个引用，退出循环，避免重复监听\n          break;\n        }\n        try {\n          await _self.executePullRequestImmediately(messageQueue);\n          await _self._sleep(_self.options.pullInterval);\n        } catch (err) {\n          if (!_self._isClosed) {\n            err.name = 'MQConsumerPullMessageError';\n            err.message = `[mq:consumer] pull message for queue: ${messageQueue.key}, occurred error: ${err.message}`;\n            _self._handleError(err);\n            await _self._sleep(_self.options.pullTimeDelayMillsWhenException);\n          }\n        }\n      }\n      _self.logger.info('[MQPushConsumer] stop pulling message from queue: %s', messageQueue.key);\n    })();\n  }\n\n  /**\n   * execute pull message immediately\n   * @param {MessageQueue} messageQueue - messageQueue\n   * @return {Promise}\n   */\n  async executePullRequestImmediately(messageQueue) {\n    // close or queue removed\n    if (!this._processQueueTable.has(messageQueue.key)) {\n      return;\n    }\n    const pullRequest = this._processQueueTable.get(messageQueue.key);\n    const processQueue = pullRequest.processQueue;\n    // queue droped\n    if (processQueue.droped) {\n      return;\n    }\n\n    // flow control\n    const size = processQueue.msgCount;\n    if (size > this.options.pullThresholdForQueue) {\n      await this._sleep(this.options.pullTimeDelayMillsWhenFlowControl);\n      return;\n    }\n\n    processQueue.lastPullTimestamp = Date.now();\n    const data = this.subscriptions.get(messageQueue.topic);\n    const subscriptionData = data && data.subscriptionData;\n    if (!subscriptionData) {\n      this.logger.warn('[mq:consumer] execute pull request, but subscriptionData not found, topic: %s, queueId: %s', messageQueue.topic, messageQueue.queueId);\n      await this._sleep(this.options.pullTimeDelayMillsWhenException);\n      return;\n    }\n\n    let commitOffset = 0;\n    const subExpression = this.options.postSubscriptionWhenPull ? subscriptionData.subString : null;\n    const expressionType = subscriptionData.expressionType;\n    const subVersion = subscriptionData.subVersion;\n\n    // cluster model\n    if (MessageModel.CLUSTERING === this.messageModel) {\n      const offset = await this._offsetStore.readOffset(pullRequest.messageQueue, ReadOffsetType.READ_FROM_MEMORY);\n      if (offset) {\n        commitOffset = offset;\n      }\n    }\n\n    this.logger.info('[MQPushConsumer] start to pull message from queue: %s, nextOffset: %s, commitOffset: %s, subExpression: %s, expressionType: %s, subVersion: %s',\n      messageQueue.key, pullRequest.nextOffset, commitOffset, subExpression, expressionType, subVersion);\n    const pullResult = await this.pullKernelImpl(messageQueue, subExpression, expressionType, subVersion, pullRequest.nextOffset, commitOffset);\n    this.updatePullFromWhichNode(messageQueue, pullResult.suggestWhichBrokerId);\n    const originOffset = pullRequest.nextOffset;\n    // update next pull offset\n    pullRequest.nextOffset = pullResult.nextBeginOffset;\n\n    this.logger.info('[MQPushConsumer] pull message result: %s from queue: %s, requestOffset: %s, nextBeginOffset: %s, minOffset: %s, maxOffset: %s, suggestWhichBrokerId: %s',\n      pullResult.pullStatus, messageQueue.key, originOffset, pullResult.nextBeginOffset, pullResult.minOffset, pullResult.maxOffset, pullResult.suggestWhichBrokerId);\n\n    switch (pullResult.pullStatus) {\n      case PullStatus.FOUND:\n      {\n        const pullRT = Date.now() - processQueue.lastPullTimestamp;\n        const msgIds = pullResult.msgFoundList.map(v => v.msgId);\n        this.logger.info('[MQPushConsumer] pull message success, found new message size: %d, topic: %s, msgId: [%s], consumerGroup: %s, messageQueue: %s, cost: %dms.',\n          pullResult.msgFoundList.length, messageQueue.topic, msgIds.join(','), this.consumerGroup, messageQueue.key, pullRT);\n\n        // submit to consumer\n        processQueue.putMessage(pullResult.msgFoundList);\n        this.emit(`topic_${messageQueue.topic}_changed`);\n        break;\n      }\n      case PullStatus.NO_NEW_MSG:\n      case PullStatus.NO_MATCHED_MSG:\n        this.logger.debug('[mq:consumer] no new message for topic: %s at message queue => %s', subscriptionData.topic, messageQueue.key);\n        this.correctTagsOffset(pullRequest);\n        break;\n      case PullStatus.OFFSET_ILLEGAL:\n        this.logger.warn('[mq:consumer] the pull request offset illegal, message queue => %s, the originOffset => %d, pullResult => %j', messageQueue.key, originOffset, pullResult);\n        this._offsetStore.updateOffset(messageQueue, pullRequest.nextOffset);\n        break;\n      default:\n        break;\n    }\n  }\n\n  async pullKernelImpl(messageQueue, subExpression, expressionType, subVersion, offset, commitOffset) {\n    let sysFlag = PullSysFlag.buildSysFlag( //\n      commitOffset > 0, // commitOffset\n      true, // suspend\n      !!subExpression, // subscription\n      false // class filter\n    );\n    const result = await this.findBrokerAddress(messageQueue);\n    if (!result) {\n      throw new Error(`The broker[${messageQueue.brokerName}] not exist`);\n    }\n\n    // Slave不允许实时提交消费进度，可以定时提交\n    if (result.slave) {\n      sysFlag = PullSysFlag.clearCommitOffsetFlag(sysFlag);\n    }\n\n    const requestHeader = {\n      consumerGroup: this.consumerGroup,\n      topic: messageQueue.topic,\n      queueId: messageQueue.queueId,\n      queueOffset: offset,\n      maxMsgNums: this.options.pullBatchSize,\n      sysFlag,\n      commitOffset,\n      suspendTimeoutMillis: this.options.brokerSuspendMaxTimeMillis,\n      subscription: subExpression,\n      subVersion,\n    };\n    // 拉取消息时需要设置表达式类型，未设置默认为 tag\n    if (expressionType) {\n      requestHeader.expressionType = expressionType;\n    }\n    return await this._mqClient.pullMessage(result.brokerAddr, requestHeader, this.options.consumerTimeoutMillisWhenSuspend);\n  }\n\n  async findBrokerAddress(messageQueue) {\n    let findBrokerResult = this._mqClient.findBrokerAddressInSubscribe(\n      messageQueue.brokerName, this.recalculatePullFromWhichNode(messageQueue), false);\n\n    if (!findBrokerResult) {\n      await this._mqClient.updateTopicRouteInfoFromNameServer(messageQueue.topic);\n      findBrokerResult = this._mqClient.findBrokerAddressInSubscribe(\n        messageQueue.brokerName, this.recalculatePullFromWhichNode(messageQueue), false);\n    }\n    return findBrokerResult;\n  }\n\n  recalculatePullFromWhichNode(messageQueue) {\n    // @example:\n    // pullFromWhichNodeTable => {\n    //   '[topic=\"TEST_TOPIC\", brokerName=\"qdinternet-03\", queueId=\"1\"]': 0\n    // }\n    return this._pullFromWhichNodeTable.get(messageQueue.key) || MixAll.MASTER_ID;\n  }\n\n  correctTagsOffset(pullRequest) {\n    // 仅当已拉下的消息消费完的情况下才更新 offset\n    if (pullRequest.processQueue.msgCount === 0) {\n      this._offsetStore.updateOffset(pullRequest.messageQueue, pullRequest.nextOffset, true);\n    }\n  }\n\n  updatePullFromWhichNode(messageQueue, brokerId) {\n    this._pullFromWhichNodeTable.set(messageQueue.key, brokerId);\n  }\n\n  /**\n   * update subscription data\n   * @param {String} topic - topic\n   * @param {Array} info - info\n   * @return {void}\n   */\n  updateTopicSubscribeInfo(topic, info) {\n    if (this._subscriptions.has(topic)) {\n      this._topicSubscribeInfoTable.set(topic, info);\n    }\n  }\n\n  /**\n   * whether need update\n   * @param {String} topic - topic\n   * @return {Boolean} need update?\n   */\n  isSubscribeTopicNeedUpdate(topic) {\n    if (this._subscriptions && this._subscriptions.has(topic)) {\n      return !this._topicSubscribeInfoTable.has(topic);\n    }\n    return false;\n  }\n\n  /**\n   * rebalance\n   * @return {void}\n   */\n  async doRebalance() {\n    for (const topic of this.subscriptions.keys()) {\n      await this.rebalanceByTopic(topic);\n    }\n  }\n\n  async rebalanceByTopic(topic) {\n    this.logger.info('[mq:consumer] rebalanceByTopic: %s, messageModel: %s', topic, this.messageModel);\n    const mqSet = this._topicSubscribeInfoTable.get(topic); // messageQueue list\n    if (!mqSet || !mqSet.length) {\n      this.logger.warn('[mq:consumer] doRebalance, %s, but the topic[%s] not exist.', this.consumerGroup, topic);\n      return;\n    }\n\n    let changed;\n    let allocateResult = mqSet;\n    if (this.options.isBroadcast) {\n      changed = await this.updateProcessQueueTableInRebalance(topic, mqSet);\n    } else {\n      const cidAll = await this._mqClient.findConsumerIdList(topic, this.consumerGroup);\n      this.logger.info('[mq:consumer] rebalance topic: %s, with consumer ids: %j', topic, cidAll);\n      if (cidAll && cidAll.length) {\n        // 排序\n        mqSet.sort(compare);\n        cidAll.sort();\n\n        allocateResult = this.allocateMessageQueueStrategy.allocate(this.consumerGroup, this._mqClient.clientId, mqSet, cidAll);\n        this.logger.info('[mq:consumer] allocate queue for group: %s, clientId: %s, result: %j', this.consumerGroup, this._mqClient.clientId, allocateResult);\n        changed = await this.updateProcessQueueTableInRebalance(topic, allocateResult);\n      }\n    }\n    if (changed) {\n      this.logger.info('[mq:consumer] do rebalance and message queue changed, topic: %s, mqSet: %j', topic, allocateResult);\n      this.emit(`topic_${topic}_queue_changed`);\n    }\n  }\n\n  /**\n   * update process queue\n   * @param {String} topic - topic\n   * @param {Array} mqSet - message queue set\n   * @return {Promise<void>}\n   */\n  async updateProcessQueueTableInRebalance(topic, mqSet) {\n    let changed = false;\n    // delete unnecessary queue\n    for (const key of this._processQueueTable.keys()) {\n      const obj = this._processQueueTable.get(key);\n      const messageQueue = obj.messageQueue;\n      const processQueue = obj.processQueue;\n\n      if (topic === messageQueue.topic) {\n        // not found in mqSet, that means the process queue is unnecessary.\n        if (!mqSet.some(mq => mq.key === messageQueue.key)) {\n          processQueue.droped = true;\n          await this.removeProcessQueue(messageQueue);\n          changed = true;\n        } else if (processQueue.isPullExpired && this.consumeType === ConsumeType.CONSUME_PASSIVELY) {\n          processQueue.droped = true;\n          await this.removeProcessQueue(messageQueue);\n          changed = true;\n          this.logger.warn('[MQPushConsumer] BUG doRebalance, %s, remove unnecessary mq=%s, because pull is pause, so try to fixed it',\n            this.consumerGroup, messageQueue.key);\n        }\n      }\n    }\n\n    for (const messageQueue of mqSet) {\n      if (this._processQueueTable.has(messageQueue.key)) {\n        continue;\n      }\n\n      const nextOffset = await this.computePullFromWhere(messageQueue);\n      // double check\n      if (this._processQueueTable.has(messageQueue.key)) {\n        continue;\n      }\n\n      if (nextOffset >= 0) {\n        const processQueue = new ProcessQueue();\n        changed = true;\n        this._processQueueTable.set(messageQueue.key, {\n          messageQueue,\n          processQueue,\n          nextOffset,\n        });\n        // start to pull this queue;\n        this.pullMessageQueue(messageQueue, processQueue);\n\n        this.logger.info('[mq:consumer] doRebalance, %s, add a new messageQueue, %j, its nextOffset: %s', this.consumerGroup, messageQueue, nextOffset);\n      } else {\n        this.logger.warn('[mq:consumer] doRebalance, %s, new messageQueue, %j, has invalid nextOffset: %s', this.consumerGroup, messageQueue, nextOffset);\n      }\n    }\n\n    return changed;\n  }\n\n  /**\n   * compute consume offset\n   * @param {MessageQueue} messageQueue - message queue\n   * @return {Promise<Number>} offset\n   */\n  async computePullFromWhere(messageQueue) {\n    try {\n      const lastOffset = await this._offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE);\n      this.logger.info('[mq:consumer] read lastOffset => %s from store, topic=\"%s\", brokerName=\"%s\", queueId=\"%s\"', lastOffset, messageQueue.topic, messageQueue.brokerName, messageQueue.queueId);\n\n      let result = -1;\n      switch (this.consumeFromWhere) {\n        case ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET_AND_FROM_MIN_WHEN_BOOT_FIRST:\n        case ConsumeFromWhere.CONSUME_FROM_MIN_OFFSET:\n        case ConsumeFromWhere.CONSUME_FROM_MAX_OFFSET:\n        case ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET:\n          // 第二次启动，根据上次的消费位点开始消费\n          if (lastOffset >= 0) {\n            result = lastOffset;\n          } else if (lastOffset === -1) { // 第一次启动，没有记录消费位点\n            // 重试队列则从队列头部开始\n            if (messageQueue.topic.indexOf(MixAll.RETRY_GROUP_TOPIC_PREFIX) === 0) {\n              result = 0;\n            } else { // 正常队列则从队列尾部开始\n              return await this._mqClient.maxOffset(messageQueue);\n            }\n          }\n          break;\n        case ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET:\n          // 第二次启动，根据上次的消费位点开始消费\n          if (lastOffset >= 0) {\n            result = lastOffset;\n          } else {\n            result = 0;\n          }\n          break;\n        case ConsumeFromWhere.CONSUME_FROM_TIMESTAMP:\n          // 第二次启动，根据上次的消费位点开始消费\n          if (lastOffset >= 0) {\n            result = lastOffset;\n          } else if (lastOffset === -1) { // 第一次启动，没有记录消费为点\n            // 重试队列则从队列尾部开始\n            if (messageQueue.topic.indexOf(MixAll.RETRY_GROUP_TOPIC_PREFIX) === 0) {\n              return await this._mqClient.maxOffset(messageQueue);\n            }\n            // 正常队列则从指定时间点开始\n            // 时间点需要参数配置\n            const timestamp = utils.parseDate(this.options.consumeTimestamp).getTime();\n            return await this._mqClient.searchOffset(messageQueue, timestamp);\n          }\n          break;\n        default:\n          break;\n      }\n      this.logger.info('[mq:consumer] computePullFromWhere() messageQueue => %s should read from offset: %s and lastOffset: %s', messageQueue.key, result, lastOffset);\n      return result;\n    } catch (err) {\n      err.mesasge = 'computePullFromWhere() occurred an exception, ' + err.mesasge;\n      this._handleError(err);\n      return -1;\n    }\n  }\n\n  /**\n   * 移除消费队列\n   * @param {MessageQueue} messageQueue - message queue\n   * @return {Promise<void>}\n   */\n  async removeProcessQueue(messageQueue) {\n    const processQueue = this._processQueueTable.get(messageQueue.key);\n    this._processQueueTable.delete(messageQueue.key);\n    if (processQueue) {\n      processQueue.droped = true;\n      await this.removeUnnecessaryMessageQueue(messageQueue, processQueue);\n      this.logger.info('[mq:consumer] remove unnecessary messageQueue, %s, Droped: %s', messageQueue.key, processQueue.droped);\n    }\n  }\n\n  /**\n   * remove unnecessary queue\n   * @param {MessageQueue} messageQueue - message queue\n   * @return {Promise<void>}\n   */\n  async removeUnnecessaryMessageQueue(messageQueue) {\n    await this._offsetStore.persist(messageQueue);\n    this._offsetStore.removeOffset(messageQueue);\n    // todo: consume later ？\n  }\n\n\n  async sendMessageBack(msg, delayLevel, brokerName, consumerGroup) {\n    const brokerAddr = brokerName ? this._mqClient.findBrokerAddressInPublish(brokerName) :\n      msg.storeHost;\n    const thatConsumerGroup = consumerGroup ? consumerGroup : this.consumerGroup;\n    try {\n      await this._mqClient.consumerSendMessageBack(\n        brokerAddr,\n        msg,\n        thatConsumerGroup,\n        delayLevel,\n        3000,\n        this.options.maxReconsumeTimes);\n      this.logger.info('[MQPushConsumer] consumerSendMessageBack success, topic=%s, consumerGroup=%s, msgId=%s', msg.topic, thatConsumerGroup, msg.msgId);\n    } catch (err) {\n      err.mesasge = 'sendMessageBack() occurred an exception, ' + thatConsumerGroup + ', ' + err.mesasge;\n      this._handleError(err);\n\n      let newMsg;\n      if (MixAll.isRetryTopic(msg.topic)) {\n        newMsg = msg;\n      } else {\n        newMsg = new Message(MixAll.getRetryTopic(thatConsumerGroup), '', msg.body);\n        newMsg.flag = msg.flag;\n        newMsg.properties = msg.properties;\n        newMsg.originMessageId = msg.originMessageId || msg.msgId;\n        newMsg.retryTopic = msg.topic;\n        // 这里需要加 1，因为如果 maxReconsumeTimes 为 1，那么这条 retry 消息发出去始终不会被重新投递了\n        newMsg.properties[MessageConst.PROPERTY_MAX_RECONSUME_TIMES] = String(this.options.maxReconsumeTimes + 1);\n      }\n\n      newMsg.properties[MessageConst.PROPERTY_RECONSUME_TIME] = String(msg.reconsumeTimes + 1);\n      newMsg.delayTimeLevel = 0;\n      await (await MQProducer.getDefaultProducer()).send(newMsg);\n    }\n  }\n\n  // * viewMessage(msgId) {\n  //   const info = MessageDecoder.decodeMessageId(msgId);\n  //   return yield this._mqClient.viewMessage(info.address, Number(info.offset.toString()), 3000);\n  // }\n\n  _handleError(err) {\n    err.message = 'MQPushConsumer occurred an error ' + err.message;\n    this.emit('error', err);\n  }\n\n  _sleep(timeout) {\n    return sleep(timeout);\n  }\n}\n// if subscriber return ACTION_RETRY, message will be directly retried\nMQPushConsumer.ACTION_RETRY = Symbol('ACTION_RETRY');\n\nmodule.exports = MQPushConsumer;\n\n// Helper\n// ------------------\nfunction compare(mqA, mqB) {\n  if (mqA.topic === mqB.topic) {\n    if (mqA.brokerName === mqB.brokerName) {\n      return mqA.queueId - mqB.queueId;\n    }\n    return mqA.brokerName > mqB.brokerName ? 1 : -1;\n  }\n  return mqA.topic > mqB.topic ? 1 : -1;\n}\n"
  },
  {
    "path": "lib/consumer/pull_status.js",
    "content": "'use strict';\n\nmodule.exports = {\n  /**\n   * 找到消息\n   */\n  FOUND: 'FOUND',\n  /**\n   * 没有新的消息可以被拉取\n   */\n  NO_NEW_MSG: 'NO_NEW_MSG',\n  /**\n   * 经过过滤后，没有匹配的消息\n   */\n  NO_MATCHED_MSG: 'NO_MATCHED_MSG',\n  /**\n   * Offset不合法，可能过大或者过小\n   */\n  OFFSET_ILLEGAL: 'OFFSET_ILLEGAL',\n};\n"
  },
  {
    "path": "lib/consumer/rebalance/allocate_message_queue_averagely.js",
    "content": "'use strict';\n\nconst debug = require('debug')('mq:allocate');\nconst AllocateMessageQueueStrategy = require('./allocate_message_queue_strategy');\n\nclass AllocateMessageQueueAveragely extends AllocateMessageQueueStrategy {\n  get name() {\n    return 'AVG';\n  }\n\n  allocate(consumerGroup, currentCID, mqAll, cidAll) {\n    if (!currentCID || currentCID.length < 1) {\n      throw new Error('currentCID is empty');\n    }\n    if (!mqAll || !mqAll.length) {\n      throw new Error('mqAll is null or mqAll empty');\n    }\n    if (!cidAll || !cidAll.length) {\n      throw new Error('cidAll is null or cidAll empty');\n    }\n\n    const result = [];\n    const index = cidAll.indexOf(currentCID);\n    if (index === -1) { // 不存在此ConsumerId ,直接返回\n      debug('[BUG] ConsumerGroup: %s The consumerId: %s not in cidAll: %j', //\n        consumerGroup, //\n        currentCID, //\n        cidAll);\n      return result;\n    }\n\n    const mqLen = mqAll.length;\n    const cidLen = cidAll.length;\n    const mod = mqLen % cidLen;\n    let averageSize = mqLen <= cidLen ?\n      1 :\n      mod > 0 && index < mod ?\n        mqLen / cidLen + 1 :\n        mqLen / cidLen;\n    averageSize = Math.floor(averageSize); // 取整\n    const startIndex = mod > 0 && index < mod ? index * averageSize : index * averageSize + mod;\n    const range = Math.min(averageSize, mqLen - startIndex);\n    for (let i = 0; i < range; i++) {\n      result.push(mqAll[(startIndex + i) % mqLen]);\n    }\n    return result;\n  }\n}\n\nmodule.exports = AllocateMessageQueueAveragely;\n"
  },
  {
    "path": "lib/consumer/rebalance/allocate_message_queue_strategy.js",
    "content": "/* istanbul ignore next */\n/* eslint valid-jsdoc:0 */\n'use strict';\n\nclass AllocateMessageQueueStrategy {\n  /**\n   * 给当前的 ConsumerId 分配队列\n   * @param {String} consumerGroup -\n   * @param {String} currentCID - 当前 ConsumerId\n   * @param {Array} mqAll - 当前 Topic 的所有队列集合，无重复数据，且有序\n   * @param {Array} cidAll - 当前订阅组的所有 Consumer 集合，无重复数据，且有序\n   */\n  allocate(consumerGroup, currentCID, mqAll, cidAll) { /* eslint no-unused-vars: 0 */\n    throw new Error('no implementation');\n  }\n\n  /**\n   * rebalance 算法的名字\n   */\n  get name() {\n    throw new Error('no implementation');\n  }\n\n}\n\nmodule.exports = AllocateMessageQueueStrategy;\n"
  },
  {
    "path": "lib/index.js",
    "content": "'use strict';\n\nexports.Message = require('./message/message');\nexports.Producer = require('./producer/mq_producer');\nexports.Consumer = require('./consumer/mq_push_consumer');\n"
  },
  {
    "path": "lib/logger.js",
    "content": "'use strict';\n\nmodule.exports = {\n  info() {},\n  warn(...args) {\n    console.warn(...args);\n  },\n  error(...args) {\n    console.error(...args);\n  },\n  debug() {},\n};\n"
  },
  {
    "path": "lib/message/message.js",
    "content": "'use strict';\n\nconst is = require('is-type-of');\nconst MessageConst = require('./message_const');\n\nclass Message {\n\n  /**\n   * 创建消息对象\n   * @param {String} topic -\n   * @param {String} tags -\n   * @param {String|Buffer} body -\n   * @class\n   */\n  constructor(topic, tags, body) {\n    if (arguments.length === 2) {\n      body = tags;\n      tags = null;\n    }\n\n    this.storeSize = null;\n    this.bodyCRC = null;\n    this.queueId = null;\n    this.flag = 0;\n    this.queueOffset = null; // long\n    this.commitLogOffset = null; // long\n    this.bornHost = null;\n    this.bornTimestamp = null; // long\n    this.storeTimestamp = null; // long\n    this.storeHost = null;\n    this.reconsumeTimes = 0;\n    this.preparedTransactionOffset = null; // long\n    this.topic = topic;\n    this.properties = {};\n    this.msgId = null;\n\n    this.tags = tags;\n\n    if (body && is.string(body)) {\n      this.body = Buffer.from(body);\n    } else {\n      this.body = body;\n    }\n  }\n\n  /**\n   * 消息标签，用于过滤\n   * @property {String} Message#tags\n   */\n  get tags() {\n    return this.properties && this.properties[MessageConst.PROPERTY_TAGS];\n  }\n\n  set tags(val) {\n    this.properties[MessageConst.PROPERTY_TAGS] = val;\n  }\n\n  /**\n   * 消息关键词\n   * @property {String} Message#keys\n   */\n  get keys() {\n    return this.properties && this.properties[MessageConst.PROPERTY_KEYS];\n  }\n\n  set keys(val) {\n    this.properties[MessageConst.PROPERTY_KEYS] = val.join(MessageConst.KEY_SEPARATOR).trim();\n  }\n\n  /**\n   * 原始消息 Id\n   * @property {String} Message#originMessageId\n   */\n  get originMessageId() {\n    return this.properties && this.properties[MessageConst.PROPERTY_ORIGIN_MESSAGE_ID];\n  }\n\n  set originMessageId(val) {\n    this.properties[MessageConst.PROPERTY_ORIGIN_MESSAGE_ID] = val;\n  }\n\n  /**\n   * 重试 Topic\n   * @property {String} Message#retryTopic\n   */\n  get retryTopic() {\n    return this.properties && this.properties[MessageConst.PROPERTY_RETRY_TOPIC];\n  }\n\n  set retryTopic(val) {\n    this.properties[MessageConst.PROPERTY_RETRY_TOPIC] = val;\n  }\n\n  /**\n   * 消息延时投递时间级别，0表示不延时，大于0表示特定延时级别（具体级别在服务器端定义）\n   * @property {Number} Message#delayTimeLevel\n   */\n  get delayTimeLevel() {\n    const t = this.properties && this.properties[MessageConst.PROPERTY_DELAY_TIME_LEVEL];\n    if (t) {\n      return parseInt(t, 10);\n    }\n    return 0;\n  }\n\n  set delayTimeLevel(val) {\n    this.properties[MessageConst.PROPERTY_DELAY_TIME_LEVEL] = val + '';\n  }\n\n  /**\n   * 是否等待服务器将消息存储完毕再返回（可能是等待刷盘完成或者等待同步复制到其他服务器）\n   * @property {Boolean} Message#waitStoreMsgOK\n   */\n  get waitStoreMsgOK() {\n    const result = this.properties && this.properties[MessageConst.PROPERTY_WAIT_STORE_MSG_OK];\n    return !!result;\n  }\n\n  set waitStoreMsgOK(val) {\n    this.properties[MessageConst.PROPERTY_WAIT_STORE_MSG_OK] = String(!!val);\n  }\n\n  /**\n   * userId，用于单元化\n   * @property {String} Message#buyerId\n   */\n  get buyerId() {\n    return this.properties && this.properties[MessageConst.PROPERTY_BUYER_ID];\n  }\n\n  set buyerId(val) {\n    this.properties[MessageConst.PROPERTY_BUYER_ID] = val;\n  }\n\n  /**\n   * 延迟执行的时间点，毫秒\n   * @param {number} delayTime 延迟执行目标时间戳\n   */\n  setStartDeliverTime(delayTime) {\n    this.properties[MessageConst.SYSTEM_PROP_KEY_STARTDELIVERTIME] = delayTime;\n  }\n\n  /**\n   * 获取消息延迟时间\n   * @return {number} 延迟执行目标时间戳\n   */\n  getStartDeliverTime() {\n    return this.properties[MessageConst.SYSTEM_PROP_KEY_STARTDELIVERTIME] || 0;\n  }\n\n  get shardingKey() {\n    return this.properties && this.properties[MessageConst.PROPERTY_SHARDING_KEY];\n  }\n  set shardingKey(val) {\n    this.properties[MessageConst.PROPERTY_SHARDING_KEY] = val;\n  }\n}\n\nmodule.exports = Message;\n"
  },
  {
    "path": "lib/message/message_const.js",
    "content": "'use strict';\n\n// 消息关键词，多个Key用KEY_SEPARATOR隔开（查询消息使用）\nexports.PROPERTY_KEYS = 'KEYS';\n// 消息标签，只支持设置一个Tag（服务端消息过滤使用）\nexports.PROPERTY_TAGS = 'TAGS';\n// 是否等待服务器将消息存储完毕再返回（可能是等待刷盘完成或者等待同步复制到其他服务器）\nexports.PROPERTY_WAIT_STORE_MSG_OK = 'WAIT';\n// 消息延时投递时间级别，0表示不延时，大于0表示特定延时级别（具体级别在服务器端定义）\nexports.PROPERTY_DELAY_TIME_LEVEL = 'DELAY';\n\n// 内部使用\nexports.PROPERTY_RETRY_TOPIC = 'RETRY_TOPIC';\nexports.PROPERTY_REAL_TOPIC = 'REAL_TOPIC';\nexports.PROPERTY_REAL_QUEUE_ID = 'REAL_QID';\nexports.PROPERTY_TRANSACTION_PREPARED = 'TRAN_MSG';\nexports.PROPERTY_PRODUCER_GROUP = 'PGROUP';\nexports.PROPERTY_MIN_OFFSET = 'MIN_OFFSET';\nexports.PROPERTY_MAX_OFFSET = 'MAX_OFFSET';\nexports.PROPERTY_BUYER_ID = 'BUYER_ID';\nexports.PROPERTY_SHARDING_KEY = '__SHARDING_KEY';\nexports.PROPERTY_ORIGIN_MESSAGE_ID = 'ORIGIN_MESSAGE_ID';\nexports.PROPERTY_TRANSFER_FLAG = 'TRANSFER_FLAG';\nexports.PROPERTY_CORRECTION_FLAG = 'CORRECTION_FLAG';\nexports.PROPERTY_MQ2_FLAG = 'MQ2_FLAG';\nexports.PROPERTY_RECONSUME_TIME = 'RECONSUME_TIME';\nexports.PROPERTY_MAX_RECONSUME_TIMES = 'MAX_RECONSUME_TIMES';\n\nexports.KEY_SEPARATOR = ' ';\n\n// 延迟消息配置key\nexports.SYSTEM_PROP_KEY_STARTDELIVERTIME = '__STARTDELIVERTIME';\n"
  },
  {
    "path": "lib/message/message_decoder.js",
    "content": "'use strict';\n/* eslint no-bitwise: 0 */\n\nconst debug = require('debug')('mq:decoder');\nconst Long = require('long');\nconst is = require('is-type-of');\nconst utils = require('../utils');\nconst ByteBuffer = require('byte');\nconst Message = require('./message');\nconst MessageSysFlag = require('../utils/message_sys_flag');\n\nconst MSG_ID_LENGTH = 8 + 8;\nconst NAME_VALUE_SEPARATOR = String.fromCharCode(1);\nconst PROPERTY_SEPARATOR = String.fromCharCode(2);\n\nconst byteBufferMsgId = ByteBuffer.allocate(MSG_ID_LENGTH);\n\n// 将属性转换为字符串\nexports.messageProperties2String = function(properties) {\n  let str = '';\n  if (properties) {\n    for (const key in properties) {\n      str += key + NAME_VALUE_SEPARATOR;\n      str += properties[key] + PROPERTY_SEPARATOR;\n    }\n  }\n  return str;\n};\n\n// 解析 messageId，从中提取出 address 和 offset 信息\nexports.decodeMessageId = function(msgId) {\n  // 地址\n  const host = string2bytes(msgId.slice(0, 8));\n  const port = string2bytes(msgId.slice(8, 16)).readInt32BE(0);\n\n  // offset\n  const data = string2bytes(msgId.slice(16, 32));\n  const offset = new Long(\n    data.readInt32BE(4), // low, high\n    data.readInt32BE(0)\n  );\n\n  return {\n    address: host[0] + '.' + host[1] + '.' + host[2] + '.' + host[3] + ':' + port,\n    offset,\n  };\n};\n\n/**\n * 解析一条消息\n * @param {ByteBuffer} byteBuffer -\n * @param {Boolean} readBody - 是否读取 body 部分\n * @param {Boolean} deCompressBody - 是否解压 body\n * @return {RemotingCommand} command\n */\nconst decode = exports.decode = function(byteBuffer, readBody, deCompressBody) {\n  if (is.nullOrUndefined(readBody)) {\n    readBody = true;\n  }\n  if (is.nullOrUndefined(deCompressBody)) {\n    deCompressBody = true;\n  }\n  try {\n    return innerDecode(byteBuffer, readBody, deCompressBody);\n  } catch (err) {\n    debug('Error occurred during innerDecode, err: %s', err.stack);\n    // 指向末尾\n    byteBuffer.position(byteBuffer.limit());\n  }\n  return null;\n};\n\n/**\n * 执行解码\n * @param {ByteBuffer} byteBuffer -\n * @param {Boolean} readBody - 是否读取 body 部分\n * @param {Boolean} deCompressBody - 是否解压 body\n * @return {RemotingCommand} command\n */\nfunction innerDecode(byteBuffer, readBody, deCompressBody) {\n  const msgExt = new Message();\n  // 消息ID\n  byteBufferMsgId.reset();\n  // 1 TOTALSIZE\n  msgExt.storeSize = byteBuffer.getInt();\n  // 2 MAGICCODE\n  byteBuffer.getInt();\n  // 3 BODYCRC\n  msgExt.bodyCRC = byteBuffer.getInt();\n  // 4 QUEUEID\n  msgExt.queueId = byteBuffer.getInt();\n  // 5 FLAG\n  msgExt.flag = byteBuffer.getInt();\n  // 6 QUEUEOFFSET\n  msgExt.queueOffset = byteBuffer.getLong().toNumber();\n  // 7 PHYSICALOFFSET\n  msgExt.commitLogOffset = byteBuffer.getLong().toNumber();\n  // 8 SYSFLAG\n  const sysFlag = byteBuffer.getInt();\n  msgExt.sysFlag = sysFlag;\n  // 9 BORNTIMESTAMP\n  msgExt.bornTimestamp = byteBuffer.getLong().toNumber();\n  // 10 BORNHOST\n  const host = Buffer.alloc(4);\n  byteBuffer.get(host);\n  let port = byteBuffer.getInt();\n  msgExt.bornHost = host[0] + '.' + host[1] + '.' + host[2] + '.' + host[3] + ':' + port;\n  // 11 STORETIMESTAMP\n  msgExt.storeTimestamp = byteBuffer.getLong().toNumber();\n  // 12 STOREHOST\n  host.fill(0);\n  byteBuffer.get(host);\n  port = byteBuffer.getInt();\n  msgExt.storeHost = host[0] + '.' + host[1] + '.' + host[2] + '.' + host[3] + ':' + port;\n  byteBufferMsgId.put(host);\n  byteBufferMsgId.putInt(port);\n  byteBufferMsgId.putLong(msgExt.commitLogOffset);\n  // 13 RECONSUMETIMES\n  msgExt.reconsumeTimes = byteBuffer.getInt();\n  // 14 Prepared Transaction Offset\n  msgExt.preparedTransactionOffset = byteBuffer.getLong().toNumber();\n  // 15 BODY\n  const bodyLen = byteBuffer.getInt();\n  if (bodyLen > 0) {\n    if (readBody) {\n      let body = Buffer.alloc(bodyLen);\n      byteBuffer.get(body);\n\n      // uncompress body\n      if (deCompressBody && (sysFlag & MessageSysFlag.CompressedFlag) === MessageSysFlag.CompressedFlag) {\n        body = utils.uncompress(body);\n      }\n      msgExt.body = body;\n    } else {\n      byteBuffer.position(byteBuffer.position() + bodyLen);\n    }\n  }\n\n  // 16 TOPIC\n  const topicLen = byteBuffer.get();\n  const topic = Buffer.alloc(topicLen);\n  byteBuffer.get(topic);\n  msgExt.topic = topic.toString();\n\n  // 17 properties\n  const propertiesLength = byteBuffer.getShort();\n  if (propertiesLength > 0) {\n    const properties = Buffer.alloc(propertiesLength);\n    byteBuffer.get(properties);\n    const propertiesString = properties.toString();\n    msgExt.properties = string2messageProperties(propertiesString);\n  }\n\n  msgExt.msgId = bytes2string(byteBufferMsgId.copy());\n  return msgExt;\n}\n\n/**\n * 解析一批消息\n * @param {ByteBuffer} byteBuffer -\n * @param {Boolean} readBody - 是否读取 body 部分\n * @return {RemotingCommand} command\n */\nexports.decodes = function(byteBuffer, readBody) {\n  readBody = readBody || true;\n  const msgExts = [];\n  let msgExt;\n  while (byteBuffer.hasRemaining()) {\n    msgExt = decode(byteBuffer, readBody);\n    if (msgExt) {\n      msgExts.push(msgExt);\n    } else {\n      break;\n    }\n  }\n  return msgExts;\n};\n\n// Helper\n// -----------\nfunction string2bytes(hexString) {\n  if (!hexString) {\n    return null;\n  }\n  return Buffer.from(hexString, 'hex');\n}\n\nfunction bytes2string(src) {\n  if (!src) {\n    return null;\n  }\n  return src.toString('hex').toUpperCase();\n}\n\nfunction string2messageProperties(properties) {\n  const map = {\n    TAGS: null,\n  };\n  if (properties) {\n    const items = properties.split(PROPERTY_SEPARATOR);\n    for (const item of items) {\n      const index = item.indexOf(NAME_VALUE_SEPARATOR);\n      map[item.slice(0, index)] = item.slice(index + 1);\n    }\n  }\n  return map;\n}\n"
  },
  {
    "path": "lib/message_queue.js",
    "content": "'use strict';\n\nconst fmt = require('util').format;\n\nclass MessageQueue {\n  constructor(topic, brokerName, queueId) {\n    this.topic = topic;\n    this.brokerName = brokerName;\n    this.queueId = queueId;\n\n    this.key = fmt('[topic=\"%s\", brokerName=\"%s\", queueId=\"%s\"]', this.topic, this.brokerName, this.queueId);\n  }\n}\n\nmodule.exports = MessageQueue;\n"
  },
  {
    "path": "lib/mix_all.js",
    "content": "'use strict';\n\nexports.DEFAULT_TOPIC = 'TBW102';\nexports.DEFAULT_PRODUCER_GROUP = 'DEFAULT_PRODUCER';\nexports.DEFAULT_CONSUMER_GROUP = 'DEFAULT_CONSUMER';\nexports.CLIENT_INNER_PRODUCER_GROUP = 'CLIENT_INNER_PRODUCER';\n\nexports.DEFAULT_CHARSET = 'UTF-8';\nexports.MASTER_ID = 0;\n// 为每个Consumer Group建立一个默认的Topic，前缀 + GroupName，用来保存处理失败需要重试的消息\nexports.RETRY_GROUP_TOPIC_PREFIX = '%RETRY%';\n// 为每个Consumer Group建立一个默认的Topic，前缀 + GroupName，用来保存重试多次都失败，接下来不再重试的消息\nexports.DLQ_GROUP_TOPIC_PREFIX = '%DLQ%';\n\n/**\n * 获取 RETRY_TOPIC\n * @param {string} consumerGroup consumerGroup\n * @return {string} %RETRY%+consumerGroup\n */\nexports.getRetryTopic = consumerGroup => {\n  return exports.RETRY_GROUP_TOPIC_PREFIX + consumerGroup;\n};\n\n/**\n * 判断是否为 RETRY_TOPIC\n * @param {String} topic topic\n * @return {boolean} ret\n */\nexports.isRetryTopic = topic => {\n  return topic && topic.startsWith(exports.RETRY_GROUP_TOPIC_PREFIX);\n};\n"
  },
  {
    "path": "lib/mq_client.js",
    "content": "'use strict';\n\nconst is = require('is-type-of');\nconst gather = require('co-gather');\nconst sleep = require('mz-modules/sleep');\nconst utility = require('utility');\nconst MixAll = require('./mix_all');\nconst MQClientAPI = require('./mq_client_api');\nconst MessageQueue = require('./message_queue');\nconst PermName = require('./protocol/perm_name');\nconst TopicPublishInfo = require('./producer/topic_publish_info');\n\nconst instanceTable = new Map();\n\nclass MQClient extends MQClientAPI {\n\n  /**\n   * metaq client\n   * @param {Object} clientConfig -\n   * @class\n   */\n  constructor(clientConfig) {\n    super(clientConfig.options);\n\n    this._clientConfig = clientConfig;\n    this._brokerAddrTable = new Map();\n    this._consumerTable = new Map();\n    this._producerTable = new Map();\n    this._topicRouteTable = new Map();\n    // this.API.on('command', this.handleServerRequest.bind(this));\n  }\n\n  /**\n   * @property {String} MQClient#clientId\n   */\n  get clientId() {\n    return this._clientConfig.clientId;\n  }\n\n  /**\n   * @property {Number} MQClient#pollNameServerInteval\n   */\n  get pollNameServerInteval() {\n    return this._clientConfig.pollNameServerInteval;\n  }\n\n  /**\n   * @property {Number} MQClient#heartbeatBrokerInterval\n   */\n  get heartbeatBrokerInterval() {\n    return this._clientConfig.heartbeatBrokerInterval;\n  }\n\n  /**\n   * @property {Number} MQClient#persistConsumerOffsetInterval\n   */\n  get persistConsumerOffsetInterval() {\n    return this._clientConfig.persistConsumerOffsetInterval;\n  }\n\n  /**\n   * @property {Number} MQClient#rebalanceInterval\n   */\n  get rebalanceInterval() {\n    return this._clientConfig.rebalanceInterval;\n  }\n\n  /**\n   * start the client\n   */\n  async init() {\n    await super.init();\n    await this.updateAllTopicRouterInfo();\n    await this.sendHeartbeatToAllBroker();\n    await this.doRebalance();\n\n    this.startScheduledTask('updateAllTopicRouterInfo', this.pollNameServerInteval);\n    this.startScheduledTask('sendHeartbeatToAllBroker', this.heartbeatBrokerInterval);\n    this.startScheduledTask('doRebalance', this.rebalanceInterval);\n    this.startScheduledTask('persistAllConsumerOffset', this.persistConsumerOffsetInterval);\n  }\n\n  async close() {\n    if (this._consumerTable.size || this._producerTable.size) {\n      return;\n    }\n\n    await super.close();\n    // todo:\n  }\n\n  /**\n   * start a schedule task\n   * @param {String} name - method name\n   * @param {Number} interval - schedule interval\n   * @param {Number} [delay] - delay time interval\n   * @return {void}\n   */\n  startScheduledTask(name, interval, delay) {\n    (async () => {\n      await sleep(delay || interval);\n      while (this._inited) {\n        try {\n          this.logger.info('[mq:client] execute `%s` at %s', name, utility.YYYYMMDDHHmmss());\n          await this[name]();\n        } catch (err) {\n          this.logger.error(err);\n        }\n        await sleep(interval);\n      }\n    })();\n  }\n\n  /**\n   * regitser consumer\n   * @param {String} group - consumer group name\n   * @param {Consumer} consumer - consumer instance\n   * @return {void}\n   */\n  registerConsumer(group, consumer) {\n    if (this._consumerTable.has(group)) {\n      this.logger.warn('[mq:client] the consumer group [%s] exist already.', group);\n      return;\n    }\n    this._consumerTable.set(group, consumer);\n    this.logger.info('[mq:client] new consumer has regitsered, group: %s', group);\n  }\n\n  /**\n   * unregister consumer\n   * @param {String} group - consumer group name\n   * @return {void}\n   */\n  async unregisterConsumer(group) {\n    this._consumerTable.delete(group);\n    await this.unregister(null, group);\n    this.logger.info('[mq:client] unregister consumer, group: %s', group);\n  }\n\n  /**\n   * register producer\n   * @param {String} group - producer group name\n   * @param {Producer} producer - producer\n   * @return {void}\n   */\n  registerProducer(group, producer) {\n    if (this._producerTable.has(group)) {\n      this.logger.warn('[mq:client] the producer group [%s] exist already.', group);\n      return;\n    }\n    this._producerTable.set(group, producer);\n    this.logger.info('[mq:client] new producer has regitsered, group: %s', group);\n  }\n\n  /**\n   * unregister producer\n   * @param {String} group - producer group name\n   * @return {void}\n   */\n  async unregisterProducer(group) {\n    this._producerTable.delete(group);\n    await this.unregister(group, null);\n    this.logger.info('[mq:client] unregister producer, group: %s', group);\n  }\n\n  /**\n   * notify all broker that producer or consumer is offline\n   * @param {String} producerGroup - producer group name\n   * @param {String} consumerGroup - consumer group name\n   * @return {void}\n   */\n  async unregister(producerGroup, consumerGroup) {\n    const brokerAddrTable = this._brokerAddrTable;\n    for (const brokerName of brokerAddrTable.keys()) {\n      const oneTable = brokerAddrTable.get(brokerName);\n      if (!oneTable) {\n        continue;\n      }\n\n      for (const id in oneTable) {\n        const addr = oneTable[id];\n        if (addr) {\n          await this.unregisterClient(addr, this.clientId, producerGroup, consumerGroup, 3000);\n        }\n      }\n    }\n  }\n\n  /**\n   * update all router info\n   * @return {void}\n   */\n  async updateAllTopicRouterInfo() {\n    const topics = [];\n    // consumer\n    for (const groupName of this._consumerTable.keys()) {\n      const consumer = this._consumerTable.get(groupName);\n      if (!consumer) {\n        continue;\n      }\n      for (const topic of consumer.subscriptions.keys()) {\n        topics.push(topic);\n      }\n    }\n\n    // producer\n    for (const groupName of this._producerTable.keys()) {\n      const producer = this._producerTable.get(groupName);\n      if (!producer) {\n        continue;\n      }\n      for (const topic of producer.publishTopicList) {\n        topics.push(topic);\n      }\n    }\n    this.logger.info('[mq:client] try to update all topic route info. topic: %j', topics);\n    const ret = await gather(topics.map(topic => () => this.updateTopicRouteInfoFromNameServer(topic)));\n    ret.forEach(data => {\n      if (data.isError) {\n        data.error.message = `[mq:client] updateAllTopicRouterInfo occurred error, ${data.error.message}`;\n        this.emit('error', data.error);\n      }\n    });\n  }\n\n  /**\n   * update topic route info\n   * @param {String} topic - topic\n   * @param {Boolean} [isDefault] - is default or not\n   * @param {Producer} [defaultMQProducer] - producer\n   */\n  async updateTopicRouteInfoFromNameServer(topic, isDefault, defaultMQProducer) {\n    this.logger.info('[mq:client] updateTopicRouteInfoFromNameServer() topic: %s, isDefault: %s', topic, !!isDefault);\n    if (isDefault && defaultMQProducer) {\n      const topicRouteData = await this.getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.createTopicKey, 3000);\n      if (topicRouteData) {\n        for (const data of topicRouteData.queueDatas) {\n          const queueNums =\n            defaultMQProducer.defaultTopicQueueNums < data.readQueueNums ?\n              defaultMQProducer.defaultTopicQueueNums : data.readQueueNums;\n          data.readQueueNums = queueNums;\n          data.writeQueueNums = queueNums;\n        }\n        this._refreshTopicRouteInfo(topic, topicRouteData);\n      }\n    } else {\n      const topicRouteData = await this.getTopicRouteInfoFromNameServer(topic, 3000);\n      if (topicRouteData) {\n        this._refreshTopicRouteInfo(topic, topicRouteData);\n      }\n    }\n  }\n\n  _refreshTopicRouteInfo(topic, topicRouteData) {\n    if (!topicRouteData) {\n      return;\n    }\n\n    // @example\n    // topicRouteData => {\n    //   \"brokerDatas\": [{\n    //     \"brokerAddrs\": {\n    //       \"0\": \"10.218.145.166:10911\"\n    //     },\n    //     \"brokerName\": \"taobaodaily-02\"\n    //   }],\n    //   \"filterServerTable\": {},\n    //   \"queueDatas\": [{\n    //     \"brokerName\": \"taobaodaily-02\",\n    //     \"perm\": 6,\n    //     \"readQueueNums\": 8,\n    //     \"topicSynFlag\": 0,\n    //     \"writeQueueNums\": 8\n    //   }]\n    // }\n    const prev = this._topicRouteTable.get(topic);\n    const needUpdate = this._isRouteDataChanged(prev, topicRouteData) || this._isNeedUpdateTopicRouteInfo(topic);\n\n    this.logger.info('[mq:client] refresh route data for topic: %s, route data: %j, needUpdate: %s', topic, topicRouteData, needUpdate);\n    if (!needUpdate) {\n      return;\n    }\n\n    // @example:\n    // this.brokerAddrTable => {\n    //   \"taobaodaily-02\": {\n    //     \"0\": \"10.218.145.166:10911\"\n    //   }\n    // }\n    for (const brokerData of topicRouteData.brokerDatas) {\n      this._brokerAddrTable.set(brokerData.brokerName, brokerData.brokerAddrs);\n    }\n\n    // update producer route data\n    const publishInfo = this._topicRouteData2TopicPublishInfo(topic, topicRouteData);\n    publishInfo.haveTopicRouterInfo = true;\n    for (const groupName of this._producerTable.keys()) {\n      const producer = this._producerTable.get(groupName);\n      if (producer) {\n        producer.updateTopicPublishInfo(topic, publishInfo);\n      }\n    }\n\n    // 更新订阅队列信息\n    const subscribeInfo = this._topicRouteData2TopicSubscribeInfo(topic, topicRouteData);\n    for (const groupName of this._consumerTable.keys()) {\n      const consumer = this._consumerTable.get(groupName);\n      if (consumer) {\n        consumer.updateTopicSubscribeInfo(topic, subscribeInfo);\n      }\n    }\n\n    this.logger.info('[mq:client] update topicRouteTable for topic: %s, with topicRouteData: %j', topic, topicRouteData);\n    this._topicRouteTable.set(topic, topicRouteData);\n  }\n\n  _isRouteDataChanged(prev, current) {\n    if (is.nullOrUndefined(prev) || is.nullOrUndefined(current)) {\n      return true;\n    }\n    // todo: performance enhance ?\n    return JSON.stringify(prev) !== JSON.stringify(current);\n  }\n\n  _isNeedUpdateTopicRouteInfo(topic) {\n    // producer\n    for (const groupName of this._producerTable.keys()) {\n      const producer = this._producerTable.get(groupName);\n      if (producer && producer.isPublishTopicNeedUpdate(topic)) {\n        return true;\n      }\n    }\n\n    // consumer\n    for (const groupName of this._consumerTable.keys()) {\n      const consumer = this._consumerTable.get(groupName);\n      if (consumer && consumer.isSubscribeTopicNeedUpdate(topic)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  _topicRouteData2TopicPublishInfo(topic, topicRouteData) {\n    const info = new TopicPublishInfo();\n    // 顺序消息\n    if (topicRouteData.orderTopicConf && topicRouteData.orderTopicConf.length) {\n      const brokers = topicRouteData.orderTopicConf.split(';');\n      for (const broker of brokers) {\n        const item = broker.split(':');\n        const nums = parseInt(item[1], 10);\n        for (let i = 0; i < nums; i++) {\n          info.messageQueueList.push(new MessageQueue(topic, item[0], i));\n        }\n      }\n      info.orderTopic = true;\n    } else { // 非顺序消息\n      for (const queueData of topicRouteData.queueDatas) {\n        if (PermName.isWriteable(queueData.perm)) {\n          const brokerData = topicRouteData.brokerDatas.find(data => {\n            return data.brokerName === queueData.brokerName;\n          });\n          if (!brokerData || !brokerData.brokerAddrs[MixAll.MASTER_ID]) {\n            continue;\n          }\n          for (let i = 0, nums = queueData.writeQueueNums; i < nums; i++) {\n            info.messageQueueList.push(new MessageQueue(topic, queueData.brokerName, i));\n          }\n        }\n      }\n      info.orderTopic = false;\n    }\n    return info;\n  }\n\n  _topicRouteData2TopicSubscribeInfo(topic, topicRouteData) {\n    const messageQueueList = [];\n    for (const queueData of topicRouteData.queueDatas) {\n      if (PermName.isReadable(queueData.perm)) {\n        for (let i = 0, nums = queueData.readQueueNums; i < nums; i++) {\n          messageQueueList.push(new MessageQueue(topic, queueData.brokerName, i));\n        }\n      }\n    }\n    return messageQueueList;\n  }\n\n  /**\n   * send heartbeat to all brokers\n   * @return {void}\n   */\n  async sendHeartbeatToAllBroker() {\n    this._cleanOfflineBroker();\n\n    const heartbeatData = this._prepareHeartbeatData();\n    const consumerEmpty = heartbeatData.consumerDataSet.length === 0;\n    const producerEmpty = heartbeatData.producerDataSet.length === 0;\n    if (consumerEmpty && producerEmpty) {\n      this.logger.info('[mq:client] sending hearbeat, but no consumer and no producer');\n      return;\n    }\n\n    const brokers = [];\n    for (const brokerName of this._brokerAddrTable.keys()) {\n      const oneTable = this._brokerAddrTable.get(brokerName);\n      if (!oneTable) {\n        continue;\n      }\n\n      for (const id in oneTable) {\n        const addr = oneTable[id];\n        if (!addr) {\n          continue;\n        }\n\n        // 说明只有Producer，则不向Slave发心跳\n        if (consumerEmpty && Number(id) !== MixAll.MASTER_ID) {\n          continue;\n        }\n\n        brokers.push(addr);\n      }\n    }\n    const ret = await gather(brokers.map(addr => () => this.sendHearbeat(addr, heartbeatData, 3000)));\n    this.logger.info('[mq:client] send heartbeat: %j to : %j, and result: %j', heartbeatData, brokers, ret);\n  }\n\n  _prepareHeartbeatData() {\n    const heartbeatData = {\n      clientID: this.clientId,\n      consumerDataSet: [],\n      producerDataSet: [],\n    };\n\n    // Consumer\n    for (const groupName of this._consumerTable.keys()) {\n      const consumer = this._consumerTable.get(groupName);\n      if (consumer) {\n        const subscriptionDataSet = [];\n        for (const topic of consumer.subscriptions.keys()) {\n          const data = consumer.subscriptions.get(topic);\n          if (data && data.subscriptionData) {\n            subscriptionDataSet.push(data.subscriptionData);\n          }\n        }\n\n        heartbeatData.consumerDataSet.push({\n          groupName: consumer.consumerGroup,\n          consumeType: consumer.consumeType,\n          messageModel: consumer.messageModel,\n          consumeFromWhere: consumer.consumeFromWhere,\n          subscriptionDataSet,\n          unitMode: consumer.unitMode,\n        });\n      }\n    }\n\n    // Producer\n    for (const groupName of this._producerTable.keys()) {\n      const producer = this._producerTable.get(groupName);\n      if (producer) {\n        heartbeatData.producerDataSet.push({\n          groupName,\n        });\n      }\n    }\n    return heartbeatData;\n  }\n\n  _cleanOfflineBroker() {\n    for (const brokerName of this._brokerAddrTable.keys()) {\n      const oneTable = this._brokerAddrTable.get(brokerName);\n      let exists = false;\n\n      for (const brokerId in oneTable) {\n        const addr = oneTable[brokerId];\n        if (this._isBrokerAddrExistInTopicRouteTable(addr)) {\n          exists = true;\n        } else {\n          delete oneTable[brokerId];\n          this.logger.info('[mq:client] the broker addr[%s %s] is offline, remove it', brokerName, addr);\n        }\n      }\n\n      if (!exists) {\n        this._brokerAddrTable.delete(brokerName);\n        this.logger.info('[mq:client] the broker[%s] name\\'s host is offline, remove it', brokerName);\n      }\n    }\n  }\n\n  _isBrokerAddrExistInTopicRouteTable(addr) {\n    for (const topic of this._topicRouteTable.keys()) {\n      const topicRouteData = this._topicRouteTable.get(topic);\n      for (const brokerData of topicRouteData.brokerDatas) {\n        for (const brokerId in brokerData.brokerAddrs) {\n          if (brokerData.brokerAddrs[brokerId] === addr) {\n            return true;\n          }\n        }\n      }\n    }\n    return false;\n  }\n\n  /**\n   * rebalance\n   * @return {void}\n   */\n  async doRebalance() {\n    for (const groupName of this._consumerTable.keys()) {\n      const consumer = this._consumerTable.get(groupName);\n      if (consumer) {\n        await consumer.doRebalance();\n      }\n    }\n  }\n\n  /**\n   * get all consumer list of topic\n   * @param {String} topic - topic\n   * @param {String} group - consumer group\n   * @return {Array} consumer list\n   */\n  async findConsumerIdList(topic, group) {\n    let brokerAddr = this.findBrokerAddrByTopic(topic);\n    if (!brokerAddr) {\n      await this.updateTopicRouteInfoFromNameServer(topic);\n      brokerAddr = this.findBrokerAddrByTopic(topic);\n\n      if (!brokerAddr) {\n        throw new Error(`The broker of topic[${topic}] not exist`);\n      } else {\n        return await this.getConsumerIdListByGroup(brokerAddr, group, 3000);\n      }\n    } else {\n      return await this.getConsumerIdListByGroup(brokerAddr, group, 3000);\n    }\n  }\n\n  // get broker address by topic\n  findBrokerAddrByTopic(topic) {\n    const topicRouteData = this._topicRouteTable.get(topic);\n    if (topicRouteData) {\n      const brokerDatas = topicRouteData.brokerDatas || [];\n      const broker = brokerDatas[0];\n      if (broker) {\n        // master first, not found try slave\n        const addr = broker.brokerAddrs[MixAll.MASTER_ID];\n        if (!addr) {\n          for (const id in broker.brokerAddrs) {\n            return broker.brokerAddrs[id];\n          }\n        }\n        return addr;\n      }\n    }\n    return null;\n  }\n\n  /**\n   * find broker address\n   * @param {String} brokerName - broker name\n   * @return {Object} broker info\n   */\n  findBrokerAddressInAdmin(brokerName) {\n    const map = this._brokerAddrTable.get(brokerName);\n    if (!map) {\n      return null;\n    }\n    if (map[MixAll.MASTER_ID]) {\n      return {\n        brokerAddr: map[MixAll.MASTER_ID],\n        slave: false,\n      };\n    }\n    for (const id in map) {\n      if (map[id]) {\n        return {\n          brokerAddr: map[id],\n          slave: true,\n        };\n      }\n    }\n    return null;\n  }\n\n  findBrokerAddressInSubscribe(brokerName, brokerId, onlyThisBroker) {\n    let brokerAddr = null;\n    let slave = false;\n    let found = false;\n    const map = this._brokerAddrTable.get(brokerName);\n\n    if (map) {\n      brokerAddr = map[brokerId];\n      slave = Number(brokerId) !== MixAll.MASTER_ID;\n      found = !is.nullOrUndefined(brokerAddr);\n\n      // 尝试寻找其他Broker\n      if (!found && !onlyThisBroker) {\n        for (const id in map) {\n          brokerAddr = map[id];\n          slave = Number(id) !== MixAll.MASTER_ID;\n          found = true;\n          break;\n        }\n      }\n    }\n    if (found) {\n      return {\n        brokerAddr,\n        slave,\n      };\n    }\n    return null;\n  }\n\n\n  /**\n   * get current offset of message queue\n   * @param {MessageQueue} messageQueue - message queue\n   * @return {Number} offset\n   */\n  async maxOffset(messageQueue) {\n    let brokerAddr = this.findBrokerAddressInPublish(messageQueue.brokerName);\n    if (!brokerAddr) {\n      await this.updateTopicRouteInfoFromNameServer(messageQueue.topic);\n      brokerAddr = this.findBrokerAddressInPublish(messageQueue.brokerName);\n\n      if (!brokerAddr) {\n        throw new Error(`The broker[${messageQueue.brokerName}] not exist`);\n      } else {\n        return await this.getMaxOffset(brokerAddr, messageQueue.topic, messageQueue.queueId, 3000);\n      }\n    } else {\n      return await this.getMaxOffset(brokerAddr, messageQueue.topic, messageQueue.queueId, 3000);\n    }\n  }\n\n  // should be master\n  findBrokerAddressInPublish(brokerName) {\n    const map = this._brokerAddrTable.get(brokerName);\n    return map && map[MixAll.MASTER_ID];\n  }\n\n  async persistAllConsumerOffset() {\n    for (const groupName of this._consumerTable.keys()) {\n      const consumer = this._consumerTable.get(groupName);\n      if (consumer) {\n        await consumer.persistConsumerOffset();\n      }\n    }\n  }\n\n  async searchOffset(messageQueue, timestamp) {\n    let brokerAddr = this.findBrokerAddressInPublish(messageQueue.brokerName);\n    if (!brokerAddr) {\n      await this.updateTopicRouteInfoFromNameServer(messageQueue.topic);\n      brokerAddr = this.findBrokerAddressInPublish(messageQueue.brokerName);\n\n      if (!brokerAddr) {\n        throw new Error('The broker[' + messageQueue.brokerName + '] not exist');\n      } else {\n        return await super.searchOffset(brokerAddr, messageQueue.topic, messageQueue.queueId, timestamp, 3000);\n      }\n    } else {\n      return await super.searchOffset(brokerAddr, messageQueue.topic, messageQueue.queueId, timestamp, 3000);\n    }\n  }\n\n  static getAndCreateMQClient(clientConfig) {\n    const clientId = clientConfig.clientId;\n    let instance = instanceTable.get(clientId);\n    if (!instance) {\n      instance = new MQClient(clientConfig);\n      instanceTable.set(clientId, instance);\n      instance.once('close', () => {\n        instanceTable.delete(clientId);\n      });\n    }\n    return instance;\n  }\n}\n\nmodule.exports = MQClient;\n"
  },
  {
    "path": "lib/mq_client_api.js",
    "content": "'use strict';\n\nconst JSON2 = require('JSON2');\nconst bytes = require('bytes');\nconst fmt = require('util').format;\nconst ByteBuffer = require('byte');\nconst MessageQueue = require('./message_queue');\nconst RemotingClient = require('./remoting_client');\nconst PullStatus = require('./consumer/pull_status');\nconst SendStatus = require('./producer/send_status');\nconst RequestCode = require('./protocol/request_code');\nconst ResponseCode = require('./protocol/response_code');\nconst MessageConst = require('./message/message_const');\nconst MessageDecoder = require('./message/message_decoder');\nconst RemotingCommand = require('./protocol/command/remoting_command');\n\n// const localIp = require('address').ip();\n// const NAMESPACE_ORDER_TOPIC_CONFIG = 'ORDER_TOPIC_CONFIG';\nconst NAMESPACE_PROJECT_CONFIG = 'PROJECT_CONFIG';\nconst VIRTUAL_APPGROUP_PREFIX = '%%PROJECT_%s%%';\n\nconst byteBuffer = ByteBuffer.allocate(bytes('1m'));\n\n/* eslint no-fallthrough:0 */\n\nclass MQClientAPI extends RemotingClient {\n\n  /**\n   * Metaq api wrapper\n   * @param {Object} options\n   *   - {HttpClient} httpclient - http request client\n   *   - {Object} [logger] - log module\n   *   - {Number} [responseTimeout] - tcp response timeout\n   * @class\n   */\n  constructor(options) {\n    super(options);\n\n    // virtual env project group\n    this.projectGroupPrefix = null;\n  }\n\n  /**\n   * start the client\n   * @return {void}\n   */\n  async init() {\n    await super.init();\n    // this.projectGroupPrefix = await this.getProjectGroupByIp(localIp, 3000);\n  }\n\n  /**\n   * get project group prefix by ip address\n   * @param {String} ip - ip address\n   * @param {Number} [timeoutMillis] - timeout in milliseconds\n   * @return {String} prefix\n   */\n  async getProjectGroupByIp(ip, timeoutMillis) {\n    try {\n      return await this.getKVConfigByValue(NAMESPACE_PROJECT_CONFIG, ip, timeoutMillis);\n    } catch (err) {\n      err.message = `[mq:api] Can not get project config from server, ${err.message}`;\n      this.logger.error(err);\n      return null;\n    }\n  }\n\n  /**\n   * get key-value config\n   * @param {String} namespace - config namespace\n   * @param {String} value - config key\n   * @param {Number} [timeoutMillis] - timeout in milliseconds\n   * @return {String} config value\n   */\n  async getKVConfigByValue(namespace, value, timeoutMillis) {\n    const requestHeader = {\n      namespace,\n      key: value,\n    };\n    const request = RemotingCommand.createRequestCommand(RequestCode.GET_KV_CONFIG_BY_VALUE, requestHeader);\n    const response = await this.invokeForNameSrvAtLeastOnce(request, timeoutMillis);\n    switch (response.code) {\n      case ResponseCode.SUCCESS:\n      {\n        const responseHeader = response.decodeCommandCustomHeader();\n        return responseHeader && responseHeader.value;\n      }\n      default:\n        this._defaultHandler(request, response);\n        break;\n    }\n  }\n\n  /**\n   * get route info of topic\n   * @param {String} topic - topic\n   * @param {Number} [timeoutMillis] - timeout in milliseconds\n   * @return {Object} router info\n   */\n  async getDefaultTopicRouteInfoFromNameServer(topic, timeoutMillis) {\n    const requestHeader = {\n      topic,\n    };\n\n    const request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINTO_BY_TOPIC, requestHeader);\n    const response = await this.invokeForNameSrvAtLeastOnce(request, timeoutMillis);\n    switch (response.code) {\n      case ResponseCode.SUCCESS:\n      {\n        const body = response.body;\n        if (body) {\n          this.logger.info('[mq:client_api] get Topic [%s] RouteInfoFromNameServer: %s', topic, body.toString());\n          // JSON.parse dose not work here\n          const routerInfoData = JSON2.parse(body.toString());\n          // sort\n          routerInfoData.queueDatas.sort(compare);\n          routerInfoData.brokerDatas.sort(compare);\n          return routerInfoData;\n        }\n        break;\n      }\n      case ResponseCode.TOPIC_NOT_EXIST:\n        this.logger.info('[mq:client_api] get Topic [%s] RouteInfoFromNameServer is not exist value', topic);\n      default:\n        this._defaultHandler(request, response);\n        break;\n    }\n  }\n\n  /**\n   * notify broker that client is offline\n   * @param {String} addr - brokder address\n   * @param {String} clientId - clientId\n   * @param {String} producerGroup - producer group name\n   * @param {String} consumerGroup - consumer group name\n   * @param {Number} [timeoutMillis] - timeout in milliseconds\n   * @return {void}\n   */\n  async unregisterClient(addr, clientId, producerGroup, consumerGroup, timeoutMillis) {\n    producerGroup = this._buildWithProjectGroup(producerGroup);\n    consumerGroup = this._buildWithProjectGroup(consumerGroup);\n\n    const requestHeader = {\n      clientID: clientId,\n      producerGroup,\n      consumerGroup,\n    };\n    const request = RemotingCommand.createRequestCommand(RequestCode.UNREGISTER_CLIENT, requestHeader);\n    const response = await this.invoke(addr, request, timeoutMillis);\n\n    switch (response.code) {\n      case ResponseCode.SUCCESS:\n        break;\n      default:\n        this._defaultHandler(request, response);\n        break;\n    }\n  }\n\n  /**\n   * get route info from name server\n   * @param {String} topic - topic\n   * @param {Number} [timeoutMillis] - timeout in milliseconds\n   * @return {Object} route info\n   */\n  async getTopicRouteInfoFromNameServer(topic, timeoutMillis) {\n    topic = this._buildWithProjectGroup(topic);\n    const request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINTO_BY_TOPIC, {\n      topic,\n    });\n    const response = await this.invokeForNameSrvAtLeastOnce(request, timeoutMillis);\n    switch (response.code) {\n      case ResponseCode.SUCCESS:\n      {\n        const body = response.body;\n        if (body) {\n          const routerInfoData = JSON2.parse(body.toString());\n          // sort\n          routerInfoData.queueDatas.sort(compare);\n          routerInfoData.brokerDatas.sort(compare);\n          return routerInfoData;\n        }\n        break;\n      }\n      case ResponseCode.TOPIC_NOT_EXIST:\n        this.logger.warn('[mq:client_api] get Topic [%s] RouteInfoFromNameServer is not exist value', topic);\n      default:\n        this._defaultHandler(request, response);\n        break;\n    }\n  }\n\n  /**\n   * send heartbeat\n   * @param {String} addr - broker address\n   * @param {Object} heartbeatData - heartbeat data\n   * @param {Number} [timeout] - timeout in milliseconds\n   * @return {void}\n   */\n  async sendHearbeat(addr, heartbeatData, timeout) {\n    if (this.projectGroupPrefix) {\n      for (const consumerData of heartbeatData.consumerDataSet) {\n        consumerData.groupName = this._buildWithProjectGroup(consumerData.groupName);\n        for (const subscriptionData of consumerData.subscriptionDataSet) {\n          subscriptionData.topic = this._buildWithProjectGroup(subscriptionData.topic);\n        }\n      }\n      for (const producerData of heartbeatData.producerDataSet) {\n        producerData.groupName = this._buildWithProjectGroup(producerData.groupName);\n      }\n    }\n    const request = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, null);\n    request.body = Buffer.from(JSON.stringify(heartbeatData));\n    const response = await this.invoke(addr, request, timeout);\n    if (response.code !== ResponseCode.SUCCESS) {\n      this._defaultHandler(request, response);\n    }\n  }\n\n  /**\n   * update consumer offset\n   * @param {String} brokerAddr - broker address\n   * @param {Object} requestHeader - request header\n   * @return {void}\n   */\n  async updateConsumerOffsetOneway(brokerAddr, requestHeader) {\n    requestHeader.consumerGroup = this._buildWithProjectGroup(requestHeader.consumerGroup);\n    requestHeader.topic = this._buildWithProjectGroup(requestHeader.topic);\n\n    const request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONSUMER_OFFSET, requestHeader);\n    await this.invokeOneway(brokerAddr, request);\n  }\n\n  /**\n   * query consume offset\n   * @param {String} brokerAddr - broker address\n   * @param {Object} requestHeader - request header\n   * @param {Number} [timeoutMillis] - timeout in milliseconds\n   * @return {Number} offset\n   */\n  async queryConsumerOffset(brokerAddr, requestHeader, timeoutMillis) {\n    requestHeader.consumerGroup = this._buildWithProjectGroup(requestHeader.consumerGroup);\n    requestHeader.topic = this._buildWithProjectGroup(requestHeader.topic);\n\n    const request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUMER_OFFSET, requestHeader);\n    const response = await this.invoke(brokerAddr, request, timeoutMillis);\n    switch (response.code) {\n      case ResponseCode.SUCCESS:\n      {\n        const responseHeader = response.decodeCommandCustomHeader();\n        return Number(responseHeader.offset.toString());\n      }\n      default:\n        this._defaultHandler(request, response);\n        break;\n    }\n  }\n\n  /**\n   * get current max offset of queue\n   * @param {String} addr - broker address\n   * @param {String} topic - topic\n   * @param {Number} queueId - queue id\n   * @param {Number} [timeoutMillis] - timeout in milliseconds\n   * @return {Number} offset\n   */\n  async getMaxOffset(addr, topic, queueId, timeoutMillis) {\n    topic = this._buildWithProjectGroup(topic);\n    const requestHeader = {\n      topic,\n      queueId,\n    };\n    const request = RemotingCommand.createRequestCommand(RequestCode.GET_MAX_OFFSET, requestHeader);\n    const response = await this.invoke(addr, request, timeoutMillis);\n\n    switch (response.code) {\n      case ResponseCode.SUCCESS:\n      {\n        const responseHeader = response.decodeCommandCustomHeader();\n        // todo:\n        return responseHeader && Number(responseHeader.offset);\n      }\n      default:\n        this._defaultHandler(request, response);\n        break;\n    }\n  }\n\n  /**\n   * search consume offset by timestamp\n   * @param {String} addr - broker address\n   * @param {String} topic - topic\n   * @param {Number} queueId - queue id\n   * @param {String} timestamp - timestamp used to query\n   * @param {Number} [timeoutMillis] - timeout in milliseconds\n   * @return {Number} offset\n   */\n  async searchOffset(addr, topic, queueId, timestamp, timeoutMillis) {\n    topic = this._buildWithProjectGroup(topic);\n    const requestHeader = {\n      topic,\n      queueId,\n      timestamp,\n    };\n    const request = RemotingCommand.createRequestCommand(RequestCode.SEARCH_OFFSET_BY_TIMESTAMP, requestHeader);\n    const response = await this.invoke(addr, request, timeoutMillis);\n    switch (response.code) {\n      case ResponseCode.SUCCESS:\n      {\n        const responseHeader = response.decodeCommandCustomHeader();\n        // todo:\n        return responseHeader && Number(responseHeader.offset);\n      }\n      default:\n        this._defaultHandler(request, response);\n        break;\n    }\n  }\n\n  /**\n   * get all consumer's id in same group\n   * @param {String} addr - broker address\n   * @param {String} consumerGroup - consumer group\n   * @param {Number} [timeoutMillis] - timeout in milliseconds\n   * @return {Array} consumer list\n   */\n  async getConsumerIdListByGroup(addr, consumerGroup, timeoutMillis) {\n    consumerGroup = this._buildWithProjectGroup(consumerGroup);\n    const requestHeader = {\n      consumerGroup,\n    };\n    const request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_LIST_BY_GROUP, requestHeader);\n    const response = await this.invoke(addr, request, timeoutMillis);\n    switch (response.code) {\n      case ResponseCode.SUCCESS:\n        if (response.body) {\n          const body = JSON2.parse(response.body.toString());\n          return body.consumerIdList;\n        }\n        break;\n      default:\n        this._defaultHandler(request, response);\n        break;\n    }\n  }\n\n  /**\n   * pull message from broker\n   * @param {String} brokerAddr - broker address\n   * @param {Object} requestHeader - request header\n   * @param {Number} [timeoutMillis] - timeout in milliseconds\n   * @return {Object} pull result\n   */\n  async pullMessage(brokerAddr, requestHeader, timeoutMillis) {\n    requestHeader.consumerGroup = this._buildWithProjectGroup(requestHeader.consumerGroup);\n    requestHeader.topic = this._buildWithProjectGroup(requestHeader.topic);\n\n    const request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader);\n    const response = await this.invoke(brokerAddr, request, timeoutMillis);\n    let pullStatus = PullStatus.NO_NEW_MSG;\n    switch (response.code) {\n      case ResponseCode.SUCCESS:\n        pullStatus = PullStatus.FOUND;\n        break;\n      case ResponseCode.PULL_NOT_FOUND:\n        pullStatus = PullStatus.NO_NEW_MSG;\n        break;\n      case ResponseCode.PULL_RETRY_IMMEDIATELY:\n        pullStatus = PullStatus.NO_MATCHED_MSG;\n        break;\n      case ResponseCode.PULL_OFFSET_MOVED:\n        pullStatus = PullStatus.OFFSET_ILLEGAL;\n        break;\n      default:\n        this._defaultHandler(request, response);\n        break;\n    }\n\n    const responseHeader = response.decodeCommandCustomHeader();\n    let msgList = [];\n    if (pullStatus === PullStatus.FOUND) {\n      byteBuffer.reset();\n      byteBuffer.put(response.body).flip();\n      msgList = MessageDecoder.decodes(byteBuffer);\n\n      for (const msg of msgList) {\n        msg.topic = this._clearProjectGroup(msg.topic);\n        msg.properties[MessageConst.PROPERTY_MIN_OFFSET] = responseHeader.minOffset.toString();\n        msg.properties[MessageConst.PROPERTY_MAX_OFFSET] = responseHeader.maxOffset.toString();\n      }\n    }\n\n    return {\n      pullStatus,\n      nextBeginOffset: Number(responseHeader.nextBeginOffset),\n      minOffset: Number(responseHeader.minOffset),\n      maxOffset: Number(responseHeader.maxOffset),\n      msgFoundList: msgList,\n      suggestWhichBrokerId: responseHeader.suggestWhichBrokerId,\n    };\n  }\n\n  /**\n   * create topic\n   * @param {String} addr - broker address\n   * @param {String} defaultTopic - default topic: TBW102\n   * @param {Object} topicConfig - new topic config\n   * @param {Number} [timeoutMillis] - timeout in milliseconds\n   * @return {void}\n   */\n  async createTopic(addr, defaultTopic, topicConfig, timeoutMillis) {\n    const topicWithProjectGroup = this._buildWithProjectGroup(topicConfig.topicName);\n    const requestHeader = {\n      topic: topicWithProjectGroup,\n      defaultTopic,\n      readQueueNums: topicConfig.readQueueNums,\n      writeQueueNums: topicConfig.writeQueueNums,\n      perm: topicConfig.perm,\n      topicFilterType: topicConfig.topicFilterType,\n      topicSysFlag: topicConfig.topicSysFlag,\n      order: topicConfig.order,\n    };\n    const request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_TOPIC, requestHeader);\n    const response = await this.invoke(addr, request, timeoutMillis);\n    switch (response.code) {\n      case ResponseCode.SUCCESS:\n        return;\n      default:\n        this._defaultHandler(request, response);\n        break;\n    }\n  }\n\n  /**\n   * send message\n   * @param {String} brokerAddr - broker address\n   * @param {String} brokerName - broker name\n   * @param {Message} msg - msg object\n   * @param {Object} requestHeader - request header\n   * @param {Number} [timeoutMillis] - timeout in milliseconds\n   * @return {Object} sendResult\n   */\n  async sendMessage(brokerAddr, brokerName, msg, requestHeader, timeoutMillis) {\n    msg.topic = this._buildWithProjectGroup(msg.topic);\n    requestHeader.producerGroup = this._buildWithProjectGroup(requestHeader.producerGroup);\n    requestHeader.topic = this._buildWithProjectGroup(requestHeader.topic);\n\n    const requestHeaderV2 = {\n      a: requestHeader.producerGroup,\n      b: requestHeader.topic,\n      c: requestHeader.defaultTopic,\n      d: requestHeader.defaultTopicQueueNums,\n      e: requestHeader.queueId,\n      f: requestHeader.sysFlag,\n      g: requestHeader.bornTimestamp,\n      h: requestHeader.flag,\n      i: requestHeader.properties,\n      j: requestHeader.reconsumeTimes,\n      k: requestHeader.unitMode,\n      l: requestHeader.maxReconsumeTimes,\n    };\n    const request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, requestHeaderV2);\n    request.body = msg.body;\n    const response = await this.invoke(brokerAddr, request, timeoutMillis);\n    let sendStatus = SendStatus.SEND_OK;\n    switch (response.code) {\n      case ResponseCode.FLUSH_DISK_TIMEOUT:\n        sendStatus = SendStatus.FLUSH_DISK_TIMEOUT;\n        break;\n      case ResponseCode.FLUSH_SLAVE_TIMEOUT:\n        sendStatus = SendStatus.FLUSH_SLAVE_TIMEOUT;\n        break;\n      case ResponseCode.SLAVE_NOT_AVAILABLE:\n        sendStatus = SendStatus.SLAVE_NOT_AVAILABLE;\n        break;\n      case ResponseCode.SUCCESS:\n        sendStatus = SendStatus.SEND_OK;\n        break;\n      default:\n        this._defaultHandler(request, response);\n        break;\n    }\n    const responseHeader = response.decodeCommandCustomHeader();\n    const messageQueue = new MessageQueue(msg.topic, brokerName, responseHeader.queueId);\n    messageQueue.topic = this._clearProjectGroup(messageQueue.topic);\n\n    return {\n      sendStatus,\n      msgId: responseHeader.msgId,\n      messageQueue,\n      queueOffset: Number(responseHeader.queueOffset),\n    };\n  }\n\n  /**\n   * consumer send message back\n   * @param {String} brokerAddr - broker address\n   * @param {Message} msg - message object\n   * @param {String} consumerGroup - consumer group\n   * @param {Number} delayLevel - delay level\n   * @param {Number} timeoutMillis - timeout in millis\n   * @param {Number} maxConsumeRetryTimes - max retry times\n   */\n  async consumerSendMessageBack(brokerAddr, msg, consumerGroup, delayLevel, timeoutMillis, maxConsumeRetryTimes) {\n    const requestHeader = {\n      offset: msg.commitLogOffset,\n      group: consumerGroup,\n      delayLevel,\n      originMsgId: msg.msgId,\n      originTopic: msg.topic,\n      unitMode: false,\n      maxReconsumeTimes: maxConsumeRetryTimes,\n    };\n    const request = RemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK, requestHeader);\n    const response = await this.invoke(brokerAddr, request, timeoutMillis);\n    switch (response.code) {\n      case ResponseCode.SUCCESS:\n        return;\n      default:\n        this._defaultHandler(request, response);\n        break;\n    }\n  }\n\n  // * viewMessage(brokerAddr, phyoffset, timeoutMillis) {\n  //   const requestHeader = {\n  //     offset: phyoffset,\n  //   };\n  //   const request = RemotingCommand.createRequestCommand(RequestCode.VIEW_MESSAGE_BY_ID, requestHeader);\n  //   const response = await this.invoke(brokerAddr, request, timeoutMillis);\n  //   switch (response.code) {\n  //     case ResponseCode.SUCCESS:\n  //       {\n  //         const byteBuffer = ByteBuffer.wrap(response.body);\n  //         const messageExt = MessageDecoder.decode(byteBuffer);\n  //         // 清除虚拟运行环境相关的projectGroupPrefix\n  //         if (this.projectGroupPrefix) {\n  //           messageExt.topic = this._clearProjectGroup(messageExt.topic, this.projectGroupPrefix);\n  //         }\n  //         return messageExt;\n  //       }\n  //     default:\n  //       this._defaultHandler(request, response);\n  //       break;\n  //   }\n  // }\n\n  // default handler\n  _defaultHandler(request, response) {\n    const err = new Error(response.remark);\n    err.name = 'MQClientException';\n    err.code = response.code;\n    throw err;\n  }\n\n  _buildWithProjectGroup(origin) {\n    if (this.projectGroupPrefix) {\n      const prefix = fmt(VIRTUAL_APPGROUP_PREFIX, this.projectGroupPrefix);\n      if (!origin.endsWith(prefix)) {\n        return origin + prefix;\n      }\n      return origin;\n    }\n    return origin;\n  }\n\n  _clearProjectGroup(origin) {\n    const prefix = fmt(VIRTUAL_APPGROUP_PREFIX, this.projectGroupPrefix);\n    if (prefix && origin.endsWith(prefix)) {\n      return origin.slice(0, origin.lastIndexOf(prefix));\n    }\n    return origin;\n  }\n}\n\nmodule.exports = MQClientAPI;\n\n// Helper\n// ---------------\nfunction compare(routerA, routerB) {\n  if (routerA.brokerName > routerB.brokerName) {\n    return 1;\n  } else if (routerA.brokerName < routerB.brokerName) {\n    return -1;\n  }\n  return 0;\n}\n"
  },
  {
    "path": "lib/process_queue.js",
    "content": "'use strict';\n\nconst Base = require('sdk-base');\nconst bsInsert = require('binary-search-insert');\nconst comparator = (a, b) => a.queueOffset - b.queueOffset;\n\nconst pullMaxIdleTime = 120000;\n\nclass ProcessQueue extends Base {\n  constructor(options = {}) {\n    super(options);\n\n    this.msgList = [];\n    this.droped = false;\n    this.lastPullTimestamp = Date.now();\n    this.lastConsumeTimestamp = Date.now();\n\n    this.locked = false;\n    this.lastLockTimestamp = Date.now();\n  }\n\n  get maxSpan() {\n    const msgCount = this.msgCount;\n    if (msgCount) {\n      return this.msgList[msgCount - 1].queueOffset - this.msgList[0].queueOffset;\n    }\n    return 0;\n  }\n\n  get msgCount() {\n    return this.msgList.length;\n  }\n\n  get isPullExpired() {\n    return Date.now() - this.lastPullTimestamp > pullMaxIdleTime;\n  }\n\n  putMessage(msgs) {\n    for (const msg of msgs) {\n      bsInsert(this.msgList, comparator, msg);\n    }\n    this.queueOffsetMax = this.msgList[this.msgCount - 1].queueOffset;\n  }\n\n  remove(count = 1) {\n    this.msgList.splice(0, count);\n    return this.msgCount ? this.msgList[0].queueOffset : this.queueOffsetMax + 1;\n  }\n\n  clear() {\n    this.msgList = [];\n  }\n}\n\nmodule.exports = ProcessQueue;\n"
  },
  {
    "path": "lib/producer/mq_producer.js",
    "content": "'use strict';\n/* eslint no-bitwise: 0 */\n\nconst utils = require('../utils');\nconst logger = require('../logger');\nconst MixAll = require('../mix_all');\nconst MQClient = require('../mq_client');\nconst SendStatus = require('./send_status');\nconst ClientConfig = require('../client_config');\n// const PermName = require('../protocol/perm_name');\nconst MessageConst = require('../message/message_const');\nconst TopicPublishInfo = require('./topic_publish_info');\nconst ResponseCode = require('../protocol/response_code');\nconst MessageSysFlag = require('../utils/message_sys_flag');\nconst MessageDecoder = require('../message/message_decoder');\n\nconst defaultOptions = {\n  logger,\n  producerGroup: MixAll.DEFAULT_PRODUCER_GROUP,\n  createTopicKey: MixAll.DEFAULT_TOPIC,\n  defaultTopicQueueNums: 4,\n  sendMsgTimeout: 3000,\n  compressMsgBodyOverHowmuch: 1024 * 4,\n  retryTimesWhenSendFailed: 3,\n  retryAnotherBrokerWhenNotStoreOK: false,\n  maxMessageSize: 1024 * 128,\n  connectTimeout: 10000,\n};\n\nclass MQProducer extends ClientConfig {\n\n  /**\n   * metaq message producer\n   * @param {Object} options -\n   * @class\n   */\n  constructor(options) {\n    super(Object.assign({ initMethod: 'init' }, defaultOptions, options));\n\n    this._topicPublishInfoTable = new Map();\n    this._inited = false;\n\n    if (this.producerGroup !== MixAll.CLIENT_INNER_PRODUCER_GROUP) {\n      this.changeInstanceNameToPID();\n    }\n\n    this._mqClient = MQClient.getAndCreateMQClient(this);\n    this._mqClient.on('error', err => this._error(err));\n  }\n\n  get logger() {\n    return this.options.logger;\n  }\n\n  get producerGroup() {\n    if (this.namespace) {\n      return `${this.namespace}%${this.options.producerGroup}`;\n    }\n    return this.options.producerGroup;\n  }\n\n  get createTopicKey() {\n    return this.options.createTopicKey;\n  }\n\n  get defaultTopicQueueNums() {\n    return this.options.defaultTopicQueueNums;\n  }\n\n  /**\n   * topic list\n   * @property {Array} MQProducer#publishTopicList\n   */\n  get publishTopicList() {\n    return this._topicPublishInfoTable.keys();\n  }\n\n  /**\n   * start the producer\n   * @return {void}\n   */\n  async init() {\n    // this._topicPublishInfoTable.set(this.createTopicKey, new TopicPublishInfo());\n    await MQProducer.getDefaultProducer(this.options);\n    this._mqClient.registerProducer(this.producerGroup, this);\n    await this._mqClient.ready();\n    this.logger.info('[mq:producer] producer started');\n    this._inited = true;\n  }\n\n  /**\n   * close the producer\n   */\n  async close() {\n    this._topicPublishInfoTable.clear();\n\n    await this._mqClient.unregisterProducer(this.producerGroup);\n    await this._mqClient.close();\n    this.removeAllListeners();\n    this._inited = false;\n    this.logger.info('[mq:producer] producer close');\n  }\n\n  /**\n   * default error handler\n   * @param {Error} err - error\n   * @return {void}\n   */\n  _error(err) {\n    setImmediate(() => {\n      err.message = 'MQPushConsumer occurred an error' + err.message;\n      this.emit('error', err);\n    });\n  }\n\n  /**\n   * update topic info\n   * @param {String} topic - topic\n   * @param {Object} info - route info\n   * @return {void}\n   */\n  updateTopicPublishInfo(topic, info) {\n    if (topic && info) {\n      const prev = this._topicPublishInfoTable.get(topic);\n      this._topicPublishInfoTable.set(topic, info);\n      if (prev) {\n        info.sendWhichQueue = prev.sendWhichQueue;\n        this.logger.warn('[mq:producer] updateTopicPublishInfo() prev is not null, %j', prev);\n      }\n    }\n  }\n\n  /**\n   * is publish topic info need update\n   * @param {String} topic - topic\n   * @return {Boolean} need update?\n   */\n  isPublishTopicNeedUpdate(topic) {\n    const info = this._topicPublishInfoTable.get(topic);\n    return !info || !info.ok;\n  }\n\n  /**\n   * create topic\n   * @param {String} key - TBW102\n   * @param {String} newTopic -\n   * @param {Number} queueNum - queue number\n   * @param {Number} topicSysFlag - 0\n   * @return {void}\n   */\n  // * createTopic(key, newTopic, queueNum, topicSysFlag) {\n  //   await this.ready();\n\n  //   topicSysFlag = topicSysFlag || 0;\n  //   const topicRouteData = await this._mqClient.getTopicRouteInfoFromNameServer(key, 1000 * 3);\n  //   const brokerDataList = topicRouteData.brokerDatas;\n  //   if (brokerDataList && brokerDataList.length) {\n  //     // 排序原因：即使没有配置顺序消息模式，默认队列的顺序同配置的一致。\n  //     brokerDataList.sort(compare);\n\n  //     await brokerDataList.map(brokerData => {\n  //       const addr = brokerData.brokerAddrs[MixAll.MASTER_ID];\n  //       if (addr) {\n  //         const topicConfig = {\n  //           topicName: newTopic,\n  //           readQueueNums: queueNum,\n  //           writeQueueNums: queueNum,\n  //           topicSysFlag,\n  //           perm: PermName.PERM_READ | PermName.PERM_WRITE,\n  //           topicFilterType: 'SINGLE_TAG',\n  //           order: false,\n  //         };\n  //         this.logger.info('[mq:producer] execute createTopic at broker: %s, topic: %s', addr, newTopic);\n  //         return this._mqClient.createTopic(addr, key, topicConfig, 1000 * 3);\n  //       }\n  //       return null;\n  //     });\n  //   } else {\n  //     throw new Error('Not found broker, maybe key is wrong');\n  //   }\n  // }\n\n  /**\n   * send message\n   * @param {Message} msg - message object\n   * @return {Object} sendResult\n   */\n  async send(msg) {\n    await this.ready();\n    msg.topic = this.formatTopic(msg.topic);\n\n    const topicPublishInfo = await this.tryToFindTopicPublishInfo(msg.topic);\n    if (!topicPublishInfo) {\n      throw new Error(`no publish router data for topic: ${msg.topic}`);\n    }\n\n\n    const maxTimeout = this.options.sendMsgTimeout + 1000;\n    const timesTotal = 1 + this.options.retryTimesWhenSendFailed;\n    const beginTimestamp = Date.now();\n\n    const brokersSent = [];\n    let times = 0;\n    let lastBrokerName;\n    let sendResult;\n    for (; times < timesTotal && Date.now() - beginTimestamp < maxTimeout; times++) {\n      const messageQueue = topicPublishInfo.selectOneMessageQueue(lastBrokerName, msg);\n      if (!messageQueue) {\n        continue;\n      }\n      lastBrokerName = messageQueue.brokerName;\n      brokersSent[times] = lastBrokerName;\n      try {\n        sendResult = await this.sendKernelImpl(msg, messageQueue);\n        if (sendResult.sendStatus === SendStatus.SEND_OK || !this.options.retryAnotherBrokerWhenNotStoreOK) {\n          break;\n        }\n      } catch (err) {\n        if (err.name === 'MQClientException') {\n          switch (err.code) {\n            case ResponseCode.TOPIC_NOT_EXIST:\n            case ResponseCode.SERVICE_NOT_AVAILABLE:\n            case ResponseCode.SYSTEM_ERROR:\n            case ResponseCode.NO_PERMISSION:\n            case ResponseCode.NO_BUYER_ID:\n            case ResponseCode.NOT_IN_CURRENT_UNIT:\n              break;\n            default:\n              throw err;\n          }\n        }\n      }\n    }\n    if (!sendResult) {\n      throw new Error(`Send [${times}] times, still failed, cost [${Date.now() - beginTimestamp}]ms, Topic: ${msg.topic}, BrokersSent: ${JSON.stringify(brokersSent)}`);\n    }\n    return sendResult;\n  }\n\n  /**\n   * try to find topic info from name server\n   * @param {String} topic - topic\n   * @return {Object} route info\n   */\n  async tryToFindTopicPublishInfo(topic) {\n    let topicPublishInfo = this._topicPublishInfoTable.get(topic);\n    if (!topicPublishInfo || !topicPublishInfo.ok) {\n      this._topicPublishInfoTable.set(topic, new TopicPublishInfo());\n      await this._mqClient.updateTopicRouteInfoFromNameServer(topic);\n      topicPublishInfo = this._topicPublishInfoTable.get(topic);\n    }\n\n    if (topicPublishInfo.haveTopicRouterInfo || topicPublishInfo && topicPublishInfo.ok) {\n      return topicPublishInfo;\n    }\n\n    await this._mqClient.updateTopicRouteInfoFromNameServer(topic, true, this);\n    return this._topicPublishInfoTable.get(topic);\n  }\n\n  async sendKernelImpl(msg, messageQueue) {\n    let brokerAddr = this._mqClient.findBrokerAddressInPublish(messageQueue.brokerName);\n\n    if (!brokerAddr) {\n      await this.tryToFindTopicPublishInfo(messageQueue.topic);\n      brokerAddr = this._mqClient.findBrokerAddressInPublish(messageQueue.brokerName);\n      if (!brokerAddr) {\n        const err = new Error(`The broker[${messageQueue.brokerName}] not exist`);\n        err.name = 'MQClientException';\n        throw err;\n      }\n    }\n\n    let sysFlag = 0;\n    if (this.tryToCompressMessage(msg)) {\n      sysFlag |= MessageSysFlag.CompressedFlag;\n    }\n\n    const tranMsg = msg.properties[MessageConst.PROPERTY_TRANSACTION_PREPARED];\n    if (tranMsg) {\n      sysFlag |= MessageSysFlag.TransactionPreparedType;\n    }\n\n    const requestHeader = {\n      producerGroup: this.producerGroup,\n      topic: msg.topic,\n      defaultTopic: this.createTopicKey,\n      defaultTopicQueueNums: this.defaultTopicQueueNums,\n      queueId: messageQueue.queueId,\n      sysFlag,\n      bornTimestamp: Date.now(),\n      flag: msg.flag,\n      properties: MessageDecoder.messageProperties2String(msg.properties),\n      reconsumeTimes: 0,\n      unitMode: this.unitMode,\n    };\n\n    if (requestHeader.topic.indexOf(MixAll.RETRY_GROUP_TOPIC_PREFIX) === 0) {\n      const reconsumeTimes = msg.properties[MessageConst.PROPERTY_RECONSUME_TIME];\n      if (reconsumeTimes) {\n        requestHeader.reconsumeTimes = parseInt(reconsumeTimes, 10);\n        delete msg.properties[MessageConst.PROPERTY_RECONSUME_TIME];\n      }\n      const maxReconsumeTimes = msg.properties[MessageConst.PROPERTY_MAX_RECONSUME_TIMES];\n      if (maxReconsumeTimes) {\n        requestHeader.maxReconsumeTimes = parseInt(maxReconsumeTimes, 10);\n        delete msg.properties[MessageConst.PROPERTY_MAX_RECONSUME_TIMES];\n      }\n    }\n\n    return await this._mqClient.sendMessage(brokerAddr, messageQueue.brokerName, msg, requestHeader, this.options.sendMsgTimeout);\n  }\n\n  /**\n   * 尝试压缩消息\n   * @param {Message} msg - 消息对象\n   * @return {Boolean} 是否压缩\n   */\n  tryToCompressMessage(msg) {\n    const body = msg.body;\n    if (body) {\n      if (body.length >= this.options.compressMsgBodyOverHowmuch) {\n        try {\n          const data = utils.compress(body);\n          if (data) {\n            msg.body = data;\n            return true;\n          }\n        } catch (err) {\n          err.name = 'MetaQCompressError';\n          this._error(err);\n        }\n      }\n    }\n    return false;\n  }\n\n  /**\n   * 获取默认 Producer 实例\n   * @param {Object} options 配置\n   * @return {MQProducer} 默认 Producer 实例\n   */\n  static async getDefaultProducer(options = {}) {\n    if (!MQProducer.defaultProducer) {\n      const opts = Object.assign({}, options);\n      opts.producerGroup = MixAll.CLIENT_INNER_PRODUCER_GROUP;\n      MQProducer.defaultProducer = new MQProducer(opts);\n      await MQProducer.defaultProducer.ready();\n      return MQProducer.defaultProducer;\n    }\n    return MQProducer.defaultProducer;\n  }\n}\n\nmodule.exports = MQProducer;\n\n// // Helper\n// // ---------------\n// function compare(routerA, routerB) {\n//   if (routerA.brokerName > routerB.brokerName) {\n//     return 1;\n//   } else if (routerA.brokerName < routerB.brokerName) {\n//     return -1;\n//   }\n//   return 0;\n// }\n"
  },
  {
    "path": "lib/producer/send_status.js",
    "content": "'use strict';\n\nmodule.exports = {\n  // 消息发送成功\n  SEND_OK: 'SEND_OK',\n  // 消息发送成功，但是服务器刷盘超时，消息已经进入服务器队列，只有此时服务器宕机，消息才会丢失\n  FLUSH_DISK_TIMEOUT: 'FLUSH_DISK_TIMEOUT',\n  // 消息发送成功，但是服务器同步到Slave时超时，消息已经进入服务器队列，只有此时服务器宕机，消息才会丢失\n  FLUSH_SLAVE_TIMEOUT: 'FLUSH_SLAVE_TIMEOUT',\n  // 消息发送成功，但是此时slave不可用，消息已经进入服务器队列，只有此时服务器宕机，消息才会丢失\n  SLAVE_NOT_AVAILABLE: 'SLAVE_NOT_AVAILABLE',\n};\n"
  },
  {
    "path": "lib/producer/topic_publish_info.js",
    "content": "'use strict';\n\nconst crypto = require('crypto');\n\nclass TopicPublishInfo {\n  constructor() {\n    this.orderTopic = false;\n    this.haveTopicRouterInfo = false;\n    this.messageQueueList = [];\n    this.sendWhichQueue = Math.floor(Math.random() * Math.floor(Math.pow(2, 31))); // 打散 queue 选择起点\n  }\n\n  /**\n   * 是否可用\n   */\n  get ok() {\n    return this.messageQueueList && this.messageQueueList.length;\n  }\n\n  /**\n   * 如果lastBrokerName不为null，则寻找与其不同的MessageQueue\n   * @param {String} lastBrokerName - last broker name\n   * @param {Object} message - message\n   * @return {Object} queue\n   */\n  selectOneMessageQueue(lastBrokerName, message) {\n    let index,\n      pos,\n      mq;\n\n    const shardingKey = message && message.shardingKey;\n    let queueId = '';\n    if (shardingKey) {\n      // 这里不应该是所有可用队列数字，应该是queue总数\n      // 但这里的 messageQueueList 是所有可写队列， golang和java客户端均为在queue总对恶劣中寻找位置\n      // 通过寻找所有可写queueId总数计算应该写入的queueId\n      const queueIds = [];\n      this.messageQueueList.forEach(queue => {\n        if (!queueIds.includes(queue.queueId)) queueIds.push(queue.queueId);\n      });\n\n      // 参考golang和java的sdk，没有规范确定的shardingKey找queueId的实现标准\n      // 若引入其他hash算法就显得比较笨重, 可以使用 nodejs 原生hash方法生成可计算的hash值\n      // Reference: https://github.com/apache/rocketmq/blob/c11ed78eeb73c23da4cf9a36a6bad493a1279210/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivity.java#L382\n      // Reference: https://github.com/apache/rocketmq-client-go/blob/896a8a3453be4bcfcb5bfb5bb9139c4df6d4cdab/producer/selector.go#L112\n      const queueIndex = crypto\n        .createHash('md5')\n        .update(shardingKey)\n        .digest()\n        .readUInt32BE() % queueIds.length;\n      queueId = queueIds[queueIndex] || '';\n    }\n\n    if (!Number.isSafeInteger(this.sendWhichQueue)) {\n      this.sendWhichQueue = 0; // 超出安全范围，重置为 0\n    }\n    index = this.sendWhichQueue++;\n    for (let i = 0, len = this.messageQueueList.length; i < len; i++) {\n      pos = Math.abs(index++) % len;\n      mq = this.messageQueueList[pos];\n      if (lastBrokerName && mq.brokerName === lastBrokerName) {\n        continue;\n      }\n      if (queueId && mq.queueId !== queueId) {\n        continue;\n      }\n      return mq;\n    }\n    return null;\n  }\n}\n\nmodule.exports = TopicPublishInfo;\n"
  },
  {
    "path": "lib/protocol/command/opaque_generator.js",
    "content": "'use strict';\n\nconst MAX_INT_31 = Math.pow(2, 31) - 10;\nlet opaque = 0;\n\nexports.getNextOpaque = () => {\n  if (opaque >= MAX_INT_31) {\n    this.resetOpaque();\n  }\n  return ++opaque;\n};\n\nexports.resetOpaque = () => { opaque = 0; };\n\nexports.getCurrentOpaque = () => opaque;\n"
  },
  {
    "path": "lib/protocol/command/remoting_command.js",
    "content": "'use strict';\n/* eslint no-bitwise: 0*/\n\nconst bytes = require('bytes');\nconst is = require('is-type-of');\nconst ByteBuffer = require('byte');\nconst OpaqueGenerator = require('./opaque_generator');\n\nconst REQUEST_COMMAND = 'REQUEST_COMMAND';\nconst RESPONSE_COMMAND = 'RESPONSE_COMMAND';\n\n// 0, REQUEST_COMMAND\n// 1, RESPONSE_COMMAND\nconst RPC_TYPE = 0;\n// 0, RPC\n// 1, Oneway\nconst RPC_ONEWAY = 1;\n\n// V3_4_9 支持设置重试的 maxReconsumeTimes\nconst MQ_VERSION = 121;\n\nconst buf = new ByteBuffer({ size: bytes('1m') });\n\nclass RemotingCommand {\n\n  /**\n   * metaq 命令\n   * @param {Object} options\n   *  - code {Number} 命令代号\n   *  - customHeader {Object} 自定义头部\n   *  - flag {Number} 标识命令的属性\n   *  - opaque {Number} 关联请求、响应的字段\n   *  - remark {String}\n   *  - extFields {Object} 扩展字段\n   */\n  constructor(options) {\n    this.code = options.code;\n    this.customHeader = options.customHeader;\n    this.flag = options.flag || 0;\n    this.opaque = options.opaque || OpaqueGenerator.getNextOpaque();\n    this.remark = options.remark;\n    this.extFields = options.extFields || {};\n    this.body = null;\n  }\n\n  get language() {\n    return 'JAVA';\n  }\n\n  get version() {\n    return MQ_VERSION;\n  }\n\n  get type() {\n    return this.isResponseType ? RESPONSE_COMMAND : REQUEST_COMMAND;\n  }\n\n  get isResponseType() {\n    const bits = 1 << RPC_TYPE;\n    return (this.flag & bits) === bits;\n  }\n\n  get isOnewayRPC() {\n    const bits = 1 << RPC_ONEWAY;\n    return (this.flag & bits) === bits;\n  }\n\n  markResponseType() {\n    const bits = 1 << RPC_TYPE;\n    this.flag |= bits;\n  }\n\n  markOnewayRPC() {\n    const bits = 1 << RPC_ONEWAY;\n    this.flag |= bits;\n  }\n\n  makeCustomHeaderToNet() {\n    const customHeader = this.customHeader;\n    if (!customHeader) {\n      return;\n    }\n\n    this.extFields = this.extFields || {};\n    for (const key in customHeader) {\n      const field = customHeader[key];\n      if (is.nullOrUndefined(field) || is.function(field)) {\n        continue;\n      }\n      this.extFields[key] = field;\n    }\n  }\n\n  decodeCommandCustomHeader() {\n    return JSON.parse(JSON.stringify(this.extFields)); // 深拷贝\n  }\n\n  buildHeader() {\n    this.makeCustomHeaderToNet();\n    return Buffer.from(JSON.stringify({\n      code: this.code,\n      language: this.language,\n      opaque: this.opaque,\n      flag: this.flag,\n      version: this.version,\n      remark: this.remark,\n      extFields: this.extFields,\n    }), 'utf8');\n  }\n\n  encode() {\n    buf.reset();\n    let length = 4;\n    const headerData = this.buildHeader();\n    length += headerData.length;\n\n    if (this.body) {\n      length += this.body.length;\n    }\n    buf.putInt(length);\n    buf.putInt(headerData.length);\n    buf.put(headerData);\n\n    if (this.body) {\n      buf.put(this.body);\n    }\n    return buf.copy();\n  }\n\n  /**\n   * 创建 request 命令\n   * @param {Number} code - 命令代号\n   * @param {Object} customHeader -\n   * @return {RemotingCommand} command\n   */\n  static createRequestCommand(code, customHeader) {\n    return new RemotingCommand({\n      code,\n      customHeader,\n    });\n  }\n\n  /**\n   * 创建 request 命令\n   * @param {Number} code - 命令代号\n   * @param {Number} opaque -\n   * @param {String} remark -\n   * @return {RemotingCommand} command\n   */\n  static createResponseCommand(code, opaque, remark) {\n    const command = new RemotingCommand({\n      code,\n      opaque,\n      remark: remark || 'not set any response code',\n    });\n    command.markResponseType();\n    return command;\n  }\n\n  /**\n   * 反序列化报文\n   * @param {Buffer} packet -\n   * @return {RemotingCommand} command\n   */\n  static decode(packet) {\n    const customHeaderLength = packet.readInt32BE(4);\n    const customHeaderData = packet.slice(8, 8 + customHeaderLength);\n    let body;\n    if (packet.length - 8 - customHeaderLength > 0) {\n      body = packet.slice(8 + customHeaderLength);\n    }\n    const command = new RemotingCommand(JSON.parse(customHeaderData));\n    command.body = body;\n    return command;\n  }\n}\n\nmodule.exports = RemotingCommand;\n"
  },
  {
    "path": "lib/protocol/consume_type.js",
    "content": "'use strict';\n\nmodule.exports = {\n  CONSUME_ACTIVELY: 'CONSUME_ACTIVELY',\n  CONSUME_PASSIVELY: 'CONSUME_PASSIVELY',\n};\n"
  },
  {
    "path": "lib/protocol/message_model.js",
    "content": "'use strict';\n\nmodule.exports = {\n  BROADCASTING: 'BROADCASTING',\n  CLUSTERING: 'CLUSTERING',\n};\n"
  },
  {
    "path": "lib/protocol/perm_name.js",
    "content": "'use strict';\n/* eslint no-bitwise: 0 */\n\n// var PERM_PRIORITY = 0x1 << 3;\nconst PERM_READ = 0x1 << 2;\nconst PERM_WRITE = 0x1 << 1;\nconst PERM_INHERIT = 0x1 << 0;\n\nexports.PERM_READ = PERM_READ;\nexports.PERM_WRITE = PERM_WRITE;\n\nconst isReadable = exports.isReadable = function(perm) {\n  return (perm & PERM_READ) === PERM_READ;\n};\n\nconst isWriteable = exports.isWriteable = function(perm) {\n  return (perm & PERM_WRITE) === PERM_WRITE;\n};\n\nconst isInherited = exports.isInherited = function(perm) {\n  return (perm & PERM_INHERIT) === PERM_INHERIT;\n};\n\nexports.perm2String = function(perm) {\n  let str = '';\n  str += isReadable(perm) ? 'R' : '-';\n  str += isWriteable(perm) ? 'W' : '-';\n  str += isInherited(perm) ? 'X' : '-';\n  return str;\n};\n"
  },
  {
    "path": "lib/protocol/request_code.js",
    "content": "'use strict';\n\n// Broker 发送消息\nexports.SEND_MESSAGE = 10;\n// Broker 订阅消息\nexports.PULL_MESSAGE = 11;\n// Broker 查询消息\nexports.QUERY_MESSAGE = 12;\n// Broker 查询Broker Offset\nexports.QUERY_BROKER_OFFSET = 13;\n// Broker 查询Consumer Offset\nexports.QUERY_CONSUMER_OFFSET = 14;\n// Broker 更新Consumer Offset\nexports.UPDATE_CONSUMER_OFFSET = 15;\n// Broker 更新或者增加一个Topic\nexports.UPDATE_AND_CREATE_TOPIC = 17;\n// Broker 获取所有Topic的配置（Slave和Namesrv都会向Master请求此配置）\nexports.GET_ALL_TOPIC_CONFIG = 21;\n// Broker 获取所有Topic配置（Slave和Namesrv都会向Master请求此配置）\nexports.GET_TOPIC_CONFIG_LIST = 22;\n// Broker 获取所有Topic名称列表\nexports.GET_TOPIC_NAME_LIST = 23;\n// Broker 更新Broker上的配置\nexports.UPDATE_BROKER_CONFIG = 25;\n// Broker 获取Broker上的配置\nexports.GET_BROKER_CONFIG = 26;\n// Broker 触发Broker删除文件\nexports.TRIGGER_DELETE_FILES = 27;\n// Broker 获取Broker运行时信息\nexports.GET_BROKER_RUNTIME_INFO = 28;\n// Broker 根据时间查询队列的Offset\nexports.SEARCH_OFFSET_BY_TIMESTAMP = 29;\n// Broker 查询队列最大Offset\nexports.GET_MAX_OFFSET = 30;\n// Broker 查询队列最小Offset\nexports.GET_MIN_OFFSET = 31;\n// Broker 查询队列最早消息对应时间\nexports.GET_EARLIEST_MSG_STORETIME = 32;\n// Broker 根据消息ID来查询消息\nexports.VIEW_MESSAGE_BY_ID = 33;\n// Broker Client向Client发送心跳，并注册自身\nexports.HEART_BEAT = 34;\n// Broker Client注销\nexports.UNREGISTER_CLIENT = 35;\n// Broker Consumer将处理不了的消息发回服务器\nexports.CONSUMER_SEND_MSG_BACK = 36;\n// Broker Commit或者Rollback事务\nexports.END_TRANSACTION = 37;\n// Broker 获取ConsumerId列表通过GroupName\nexports.GET_CONSUMER_LIST_BY_GROUP = 38;\n// Broker 主动向Producer回查事务状态\nexports.CHECK_TRANSACTION_STATE = 39;\n// Broker Broker通知Consumer列表变化\nexports.NOTIFY_CONSUMER_IDS_CHANGED = 40;\n// Broker Consumer向Master锁定队列\nexports.LOCK_BATCH_MQ = 41;\n// Broker Consumer向Master解锁队列\nexports.UNLOCK_BATCH_MQ = 42;\n// Broker 获取所有Consumer Offset\nexports.GET_ALL_CONSUMER_OFFSET = 43;\n// Broker 获取所有定时进度\nexports.GET_ALL_DELAY_OFFSET = 45;\n// Namesrv 向Namesrv追加KV配置\nexports.PUT_KV_CONFIG = 100;\n// Namesrv 从Namesrv获取KV配置\nexports.GET_KV_CONFIG = 101;\n// Namesrv 从Namesrv获取KV配置\nexports.DELETE_KV_CONFIG = 102;\n// Namesrv 注册一个Broker，数据都是持久化的，如果存在则覆盖配置\nexports.REGISTER_BROKER = 103;\n// Namesrv 卸载一个Broker，数据都是持久化的\nexports.UNREGISTER_BROKER = 104;\n// Namesrv 根据Topic获取Broker Name、队列数(包含读队列与写队列)\nexports.GET_ROUTEINTO_BY_TOPIC = 105;\n// Namesrv 获取注册到Name Server的所有Broker集群信息\nexports.GET_BROKER_CLUSTER_INFO = 106;\nexports.UPDATE_AND_CREATE_SUBSCRIPTIONGROUP = 200;\nexports.GET_ALL_SUBSCRIPTIONGROUP_CONFIG = 201;\nexports.GET_TOPIC_STATS_INFO = 202;\nexports.GET_CONSUMER_CONNECTION_LIST = 203;\nexports.GET_PRODUCER_CONNECTION_LIST = 204;\nexports.WIPE_WRITE_PERM_OF_BROKER = 205;\n\n// 从Name Server获取完整Topic列表\nexports.GET_ALL_TOPIC_LIST_FROM_NAMESERVER = 206;\n// 从Broker删除订阅组\nexports.DELETE_SUBSCRIPTIONGROUP = 207;\n// 从Broker获取消费状态（进度）\nexports.GET_CONSUME_STATS = 208;\n// Suspend Consumer消费过程\nexports.SUSPEND_CONSUMER = 209;\n// Resume Consumer消费过程\nexports.RESUME_CONSUMER = 210;\n// 重置Consumer Offset\nexports.RESET_CONSUMER_OFFSET_IN_CONSUMER = 211;\n// 重置Consumer Offset\nexports.RESET_CONSUMER_OFFSET_IN_BROKER = 212;\n// 调整Consumer线程池数量\nexports.ADJUST_CONSUMER_THREAD_POOL = 213;\n// 查询消息被哪些消费组消费\nexports.WHO_CONSUME_THE_MESSAGE = 214;\n\n// 从Broker删除Topic配置\nexports.DELETE_TOPIC_IN_BROKER = 215;\n// 从Namesrv删除Topic配置\nexports.DELETE_TOPIC_IN_NAMESRV = 216;\n// Namesrv 通过 project 获取所有的 server ip 信息\nexports.GET_KV_CONFIG_BY_VALUE = 217;\n// Namesrv 删除指定 project group 下的所有 server ip 信息\nexports.DELETE_KV_CONFIG_BY_VALUE = 218;\n// 通过NameSpace获取所有的KV List\nexports.GET_KVLIST_BY_NAMESPACE = 219;\n\n// offset 重置\nexports.RESET_CONSUMER_CLIENT_OFFSET = 220;\n// 客户端订阅消息\nexports.GET_CONSUMER_STATUS_FROM_CLIENT = 221;\n// 通知 broker 调用 offset 重置处理\nexports.INVOKE_BROKER_TO_RESET_OFFSET = 222;\n// 通知 broker 调用客户端订阅消息处理\nexports.INVOKE_BROKER_TO_GET_CONSUMER_STATUS = 223;\n\n// Broker 查询topic被谁消费\n// 2014-03-21 Add By shijia\nexports.QUERY_TOPIC_CONSUME_BY_WHO = 300;\n\n// 获取指定集群下的所有 topic\n// 2014-03-26\nexports.GET_TOPICS_BY_CLUSTER = 224;\n\n// 向Broker注册Filter Server\n// 2014-04-06 Add By shijia\nexports.REGISTER_FILTER_SERVER = 301;\n// 向Filter Server注册Class\n// 2014-04-06 Add By shijia\nexports.REGISTER_MESSAGE_FILTER_CLASS = 302;\n// 根据 topic 和 group 获取消息的时间跨度\nexports.QUERY_CONSUME_TIME_SPAN = 303;\n// 获取所有系统内置 Topic 列表\nexports.GET_SYSTEM_TOPIC_LIST_FROM_NS = 304;\nexports.GET_SYSTEM_TOPIC_LIST_FROM_BROKER = 305;\n\n// 清理失效队列\nexports.CLEAN_EXPIRED_CONSUMEQUEUE = 306;\n\n// 通过Broker查询Consumer内存数据\n// 2014-07-19 Add By shijia\nexports.GET_CONSUMER_RUNNING_INFO = 307;\n\n// 查找被修正 offset (转发组件）\nexports.QUERY_CORRECTION_OFFSET = 308;\n\n// 通过Broker直接向某个Consumer发送一条消息，并立刻消费，返回结果给broker，再返回给调用方\n// 2014-08-11 Add By shijia\nexports.CONSUME_MESSAGE_DIRECTLY = 309;\n\n// Broker 发送消息，优化网络数据包\nexports.SEND_MESSAGE_V2 = 310;\n\n// 单元化相关 topic\nexports.GET_UNIT_TOPIC_LIST = 311;\n// 获取含有单元化订阅组的 Topic 列表\nexports.GET_HAS_UNIT_SUB_TOPIC_LIST = 312;\n// 获取含有单元化订阅组的非单元化 Topic 列表\nexports.GET_HAS_UNIT_SUB_UNUNIT_TOPIC_LIST = 313;\n// 克隆某一个组的消费进度到新的组\nexports.CLONE_GROUP_OFFSET = 314;\n\n// 查看Broker上的各种统计信息\nexports.VIEW_BROKER_STATS_DATA = 315;\n"
  },
  {
    "path": "lib/protocol/response_code.js",
    "content": "'use strict';\n\n// 成功\nexports.SUCCESS = 0;\n// 发生了未捕获异常\nexports.SYSTEM_ERROR = 1;\n// 由于线程池拥堵，系统繁忙\nexports.SYSTEM_BUSY = 2;\n// 请求代码不支持\nexports.REQUEST_CODE_NOT_SUPPORTED = 3;\n\n// Broker 刷盘超时\nexports.FLUSH_DISK_TIMEOUT = 10;\n// Broker 同步双写，Slave不可用\nexports.SLAVE_NOT_AVAILABLE = 11;\n// Broker 同步双写，等待Slave应答超时\nexports.FLUSH_SLAVE_TIMEOUT = 12;\n// Broker 消息非法\nexports.MESSAGE_ILLEGAL = 13;\n// Broker, Namesrv 服务不可用，可能是正在关闭或者权限问题\nexports.SERVICE_NOT_AVAILABLE = 14;\n// Broker, Namesrv 版本号不支持\nexports.VERSION_NOT_SUPPORTED = 15;\n// Broker, Namesrv 无权限执行此操作，可能是发、收、或者其他操作\nexports.NO_PERMISSION = 16;\n// Broker, Topic不存在\nexports.TOPIC_NOT_EXIST = 17;\n// Broker, Topic已经存在，创建Topic\nexports.TOPIC_EXIST_ALREADY = 18;\n// Broker 拉消息未找到（请求的Offset等于最大Offset，最大Offset无对应消息）\nexports.PULL_NOT_FOUND = 19;\n// Broker 可能被过滤，或者误通知等\nexports.PULL_RETRY_IMMEDIATELY = 20;\n// Broker 拉消息请求的Offset不合法，太小或太大\nexports.PULL_OFFSET_MOVED = 21;\n// Broker 查询消息未找到\nexports.QUERY_NOT_FOUND = 22;\n// Broker 订阅关系解析失败\nexports.SUBSCRIPTION_PARSE_FAILED = 23;\n// Broker 订阅关系不存在\nexports.SUBSCRIPTION_NOT_EXIST = 24;\n// Broker 订阅关系不是最新的\nexports.SUBSCRIPTION_NOT_LATEST = 25;\n// Broker 订阅组不存在\nexports.SUBSCRIPTION_GROUP_NOT_EXIST = 26;\n// Producer 事务应该被提交\nexports.TRANSACTION_SHOULD_COMMIT = 200;\n// Producer 事务应该被回滚\nexports.TRANSACTION_SHOULD_ROLLBACK = 201;\n// Producer 事务状态未知\nexports.TRANSACTION_STATE_UNKNOW = 202;\n// Producer ProducerGroup错误\nexports.TRANSACTION_STATE_GROUP_WRONG = 203;\n// 单元化消息，需要设置 buyerId\nexports.NO_BUYER_ID = 204;\n\n// 单元化消息，非本单元消息\nexports.NOT_IN_CURRENT_UNIT = 205;\n\n// Consumer不在线\nexports.CONSUMER_NOT_ONLINE = 206;\n\n// Consumer消费消息超时\nexports.CONSUME_MSG_TIMEOUT = 207;\n"
  },
  {
    "path": "lib/remoting_client.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst Base = require('sdk-base');\nconst logger = require('./logger');\nconst Channel = require('./channel');\n\nconst defaultOptions = {\n  logger,\n  responseTimeout: 30000,\n};\n\nclass RemotingClient extends Base {\n\n  /**\n   * rocketmq remoting client\n   * @param {Object} options\n   *   - {HttpClient} httpclient - http request client\n   *   - {Object} [logger] - log module\n   *   - {Number} [responseTimeout] - tcp response timeout\n   * @class\n   */\n  constructor(options) {\n    assert(options.onsAddr || options.nameSrv, '[RemotingClient] options.onsAddr or options.nameSrv one of them is required');\n    assert(options.httpclient, '[RemotingClient] options.httpclient is required');\n    super(Object.assign({ initMethod: 'init' }, defaultOptions, options));\n    this._index = 0;\n    this._inited = false;\n    this._namesrvAddrList = [];\n    this._channels = new Map();\n  }\n\n  /**\n   * @property {HttpClient} RemotingClient#httpclient\n   */\n  get httpclient() {\n    return this.options.httpclient;\n  }\n\n  /**\n   * @property {Object} RemotingClient#logger\n   */\n  get logger() {\n    return this.options.logger;\n  }\n\n  /**\n   * @property {Number} RemotingClient#responseTimeout\n   */\n  get responseTimeout() {\n    return this.options.responseTimeout;\n  }\n\n  get unitName() {\n    return this.options.unitName;\n  }\n\n  /**\n   * start the client\n   * @return {void}\n   */\n  async init() {\n    // get name server address at first\n    await this.updateNameServerAddressList();\n    this._inited = true;\n    this.logger.info('[mq:remoting_client] remoting client started');\n  }\n\n  /**\n   * close the client\n   * @return {void}\n   */\n  async close() {\n    if (!this._inited) {\n      return;\n    }\n\n    // wait all channel close\n    await Promise.all(Array.from(this._channels.keys()).map(addr => {\n      return new Promise(resolve => {\n        const channel = this._channels.get(addr);\n        if (channel && channel.isOK) {\n          channel.once('close', resolve);\n          channel.close();\n        } else {\n          resolve();\n        }\n        this._channels.delete(addr);\n      });\n    }));\n\n    this._inited = false;\n    this.emit('close');\n    this.removeAllListeners();\n\n    this.logger.info('[mq:remoting_client] remoting client is closed');\n  }\n\n  /**\n   * default error handler\n   * @param {Error} err - error object\n   * @return {void}\n   */\n  error(err) {\n    this.emit('error', err);\n  }\n\n  async handleClose(addr, channel) {\n    if (this._channels.get(addr) && this._channels.get(addr).clientId === channel.clientId) {\n      this._channels.delete(addr);\n    }\n    await this.updateNameServerAddressListThrottle();\n  }\n\n  async updateNameServerAddressListThrottle() {\n    if (this._updatingNameServerAddressList) {\n      // if it is in the process of updating and there is a new update task\n      // it needs to be recorded at this time and update again at the end to avoid missing the last update.\n      this._needToUpdatingNameServerAddressList = true;\n      return;\n    }\n    this._updatingNameServerAddressList = true;\n    let updateError;\n    try {\n      await this.updateNameServerAddressList();\n    } catch (e) {\n      updateError = e;\n    }\n    this._updatingNameServerAddressList = false;\n    if (this._needToUpdatingNameServerAddressList) {\n      this._needToUpdatingNameServerAddressList = false;\n      this.updateNameServerAddressListThrottle();\n    }\n    if (updateError) {\n      throw updateError;\n    }\n  }\n\n  /**\n   * fetch name server address list\n   * @return {Promise<void>}\n   */\n  async updateNameServerAddressList() {\n    if (this.options.nameSrv) {\n      this._namesrvAddrList = Array.isArray(this.options.nameSrv) ? this.options.nameSrv : [ this.options.nameSrv ];\n      return;\n    }\n    let url = this.options.onsAddr;\n    if (this.unitName) {\n      url = url + '-' + this.unitName + '?nofix=1';\n    }\n    let retries = 3;\n    let ret;\n    while (retries-- > 0) {\n      ret = await this.httpclient.request(url, {\n        timeout: this.options.connectTimeout || 10000,\n      });\n      if (ret.status === 200) {\n        const addrs = ret.data.toString().trim();\n        const newList = addrs.split(';');\n        if (newList.length) {\n          this._namesrvAddrList = newList;\n        }\n        this.logger.info('[mq:remoting_client] fetch name server addresses successfully, address: %s', addrs);\n        return;\n      }\n    }\n    throw new Error('[mq:remoting_client] fetch name server addresses failed, ret.statusCode: ' + ret.status);\n  }\n\n  /**\n   * invoke command\n   * @param {String} addr - server address\n   * @param {RemotingCommand} command - remoting command\n   * @param {Number} [timeout] - response timeout\n   * @return {Promise<Object>} response\n   */\n  async invoke(addr, command, timeout) {\n    if (!this._inited) {\n      await this.ready();\n    }\n    return await this.getAndCreateChannel(addr)\n      .invoke(command, timeout || this.responseTimeout);\n  }\n\n  /**\n   * invoke command for namesrv, return success at least once success\n   * @param {RemotingCommand} command - remoting command\n   * @param {Number} [timeout] - response timeout\n   * @return {Object} response\n   */\n  async invokeForNameSrvAtLeastOnce(command, timeout) {\n    let response = {};\n    let count = this._namesrvAddrList.length;\n    let err = null;\n    while (count--) {\n      try {\n        response = await this.invoke(null, command, timeout);\n        // 有一个成功即无需抛 invoke 错误\n        err = null;\n        break;\n      } catch (e) {\n        err = e;\n        this.logger.warn(err);\n      }\n    }\n    if (err) {\n      throw err;\n    }\n    return response;\n  }\n\n  /**\n   * invoke command without response\n   * @param {String} addr - server address\n   * @param {RemotingCommand} command - remoting command\n   * @return {Promise<void>}\n   */\n  async invokeOneway(addr, command) {\n    if (!this._inited) {\n      await this.ready();\n    }\n    await this.getAndCreateChannel(addr).invokeOneway(command);\n  }\n\n  /**\n   * get request channel from address\n   * @param {String} [addr] - server address\n   * @return {Promise<Channel>} request channel\n   */\n  getAndCreateChannel(addr) {\n    if (!addr) {\n      this._index = ++this._index % this._namesrvAddrList.length;\n      addr = this._namesrvAddrList[this._index];\n    }\n    let channel = this._channels.get(addr);\n    if (channel && channel.isOK) {\n      return channel;\n    }\n    channel = new Channel(addr, this.options);\n    this._channels.set(addr, channel);\n    channel.once('close', this.handleClose.bind(this, addr, channel));\n    channel.on('error', err => this.error(err));\n    channel.on('request', (request, address) => this.emit('request', request, address));\n    return channel;\n  }\n}\n\nmodule.exports = RemotingClient;\n"
  },
  {
    "path": "lib/store/index.js",
    "content": "'use strict';\n\nexports.LocalFileOffsetStore = require('./local_file');\nexports.LocalMemoryOffsetStore = require('./local_memory');\nexports.RemoteBrokerOffsetStore = require('./remote_broker');\n"
  },
  {
    "path": "lib/store/local_file.js",
    "content": "'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\nconst osenv = require('osenv');\nconst mkdirp = require('mkdirp');\nconst is = require('is-type-of');\nconst Base = require('sdk-base');\nconst ReadOffsetType = require('./read_offset_type');\nconst localOffsetStoreDir = path.join(osenv.home(), '.rocketmq_offsets_node');\n\nclass LocalFileOffsetStore extends Base {\n\n  /**\n   * 消费进度存储到Consumer本地\n   * @param {MQClient} mqClient -\n   * @param {String} groupName 分组名\n   * @class\n   */\n  constructor(mqClient, groupName) {\n    super();\n    this.mqClient = mqClient;\n    this.groupName = groupName;\n    this.offsetTable = new Map();\n    this.storePath = path.join(localOffsetStoreDir, mqClient.clientId, groupName, 'offsets.json');\n\n    mkdirp.sync(path.dirname(this.storePath));\n  }\n\n  get logger() {\n    return this.mqClient.logger;\n  }\n\n  /**\n   * 加载Offset\n   */\n  async load() {\n    const data = await this.readLocalOffset();\n\n    if (data && data.offsetTable) {\n      for (const key in data.offsetTable) {\n        if (is.number(data.offsetTable[key])) {\n          this.offsetTable.set(key, data.offsetTable[key]);\n        }\n      }\n    }\n  }\n\n  /**\n   * 更新消费进度，存储到内存\n   * @param {MessageQueue} messageQueue -\n   * @param {Number} offset 新的进度\n   * @param {Boolean} increaseOnly 是否强制覆盖\n   * @return {void}\n   */\n  updateOffset(messageQueue, offset, increaseOnly) {\n    increaseOnly = increaseOnly || false;\n    if (!messageQueue) {\n      return;\n    }\n\n    let prev = -1;\n    if (this.offsetTable.has(messageQueue.key)) {\n      prev = this.offsetTable.get(messageQueue.key);\n    }\n    if (prev >= offset && increaseOnly) {\n      return;\n    }\n    this.offsetTable.set(messageQueue.key, offset);\n    this.logger.info('[mq:LocalFileOffsetStore] update offset for messageQueue: %s, current offset: %d, prev offset: %s, increaseOnly: %s', messageQueue.key, offset, prev, increaseOnly);\n  }\n\n  /**\n   * 从本地缓存读取消费进度\n   * @param {MessageQueue} messageQueue -\n   * @param {String} type 消费类型（从哪里开始消费）\n   * @return {void}\n   */\n  async readOffset(messageQueue, type) {\n    if (!messageQueue) {\n      return -1;\n    }\n\n    switch (type) {\n      case ReadOffsetType.MEMORY_FIRST_THEN_STORE:\n      case ReadOffsetType.READ_FROM_MEMORY:\n      {\n        const offset = this.offsetTable.get(messageQueue.key);\n        if (!is.nullOrUndefined(offset)) {\n          return offset;\n        }\n        break;\n      }\n      case ReadOffsetType.READ_FROM_STORE:\n      {\n        const data = await this.readLocalOffset();\n        if (data && data.offsetTable) {\n          const offset = data.offsetTable[messageQueue.key];\n          if (is.number(offset)) {\n            this.updateOffset(messageQueue, offset, false);\n            return offset;\n          }\n        }\n        break;\n      }\n      default:\n        break;\n    }\n    return -1;\n  }\n\n  /**\n   * 持久化全部消费进度，可能持久化本地或者远端Broker\n   * @param {Array} mqs -\n   * @return {void}\n   */\n  async persistAll(mqs) {\n    if (mqs && mqs.length) {\n      const data = {\n        offsetTable: {},\n      };\n\n      const newTable = new Map();\n      for (const messageQueue of mqs) {\n        const offset = this.offsetTable.get(messageQueue.key) || -1;\n        data.offsetTable[messageQueue.key] = offset;\n        newTable.set(messageQueue.key, offset);\n      }\n      this.offsetTable = newTable;\n\n      fs.writeFileSync(this.storePath, JSON.stringify(data));\n    }\n  }\n\n  /* eslint-disable no-empty-function */\n  /**\n   * 持久化消费进度\n   * @return {void}\n   */\n  async persist() {}\n\n  /* eslint-enable no-empty-function */\n\n  /**\n   * 删除不必要的MessageQueue offset\n   * @param {Object} messageQueue 消息队列\n   * @return {void}\n   */\n  removeOffset(messageQueue) {\n    if (messageQueue) {\n      this.offsetTable.delete(messageQueue.key);\n    }\n  }\n\n  /**\n   * 从本地文件读消费进度\n   * @return {void}\n   */\n  async readLocalOffset() {\n    if (!fs.existsSync(this.storePath)) {\n      return null;\n    }\n\n    const content = fs.readFileSync(this.storePath);\n    if (!content) {\n      return null;\n    }\n    try {\n      return JSON.parse(content.toString());\n    } catch (e) {\n      e.message = 'readLocalOffset() failed' + e.message;\n      this.emit('error', e);\n      return null;\n    }\n  }\n}\n\nmodule.exports = LocalFileOffsetStore;\n"
  },
  {
    "path": "lib/store/local_memory.js",
    "content": "'use strict';\n\nconst is = require('is-type-of');\nconst Base = require('sdk-base');\nconst ReadOffsetType = require('./read_offset_type');\n\nclass LocalMemoryOffsetStore extends Base {\n  /**\n   * 消费进度存储到Consumer本地\n   * @param {MQClient} mqClient -\n   * @param {String} groupName 分组名\n   * @class\n   */\n  constructor(mqClient, groupName) {\n    super();\n    this.mqClient = mqClient;\n    this.groupName = groupName;\n    this.offsetTable = new Map();\n  }\n\n  get logger() {\n    return this.mqClient.logger;\n  }\n\n  /**\n   * 更新消费进度，存储到内存\n   * @param {MessageQueue} messageQueue -\n   * @param {Number} offset 新的进度\n   * @param {Boolean} increaseOnly 是否强制覆盖\n   * @return {void}\n   */\n  updateOffset(messageQueue, offset, increaseOnly) {\n    increaseOnly = increaseOnly || false;\n    if (!messageQueue) {\n      return;\n    }\n\n    let prev = -1;\n    if (this.offsetTable.has(messageQueue.key)) {\n      prev = this.offsetTable.get(messageQueue.key);\n    }\n    if (prev >= offset && increaseOnly) {\n      return;\n    }\n    this.offsetTable.set(messageQueue.key, offset);\n    this.logger.info('[mq:LocalMemoryOffsetStore] update offset for messageQueue: %s, current offset: %d, prev offset: %s, increaseOnly: %s', messageQueue.key, offset, prev, increaseOnly);\n  }\n\n  /**\n   * 从本地缓存读取消费进度\n   * @param {MessageQueue} messageQueue -\n   * @param {String} type 消费类型（从哪里开始消费）\n   * @return {void}\n   */\n  async readOffset(messageQueue, type) {\n    if (!messageQueue) {\n      return -1;\n    }\n    switch (type) {\n      case ReadOffsetType.MEMORY_FIRST_THEN_STORE:\n      case ReadOffsetType.READ_FROM_MEMORY:\n      case ReadOffsetType.READ_FROM_STORE:\n      {\n        const offset = this.offsetTable.get(messageQueue.key);\n        if (!is.nullOrUndefined(offset)) {\n          return offset;\n        }\n        break;\n      }\n      default:\n        break;\n    }\n    return -1;\n  }\n\n  /* eslint-disable no-empty-function */\n\n  async load() {}\n\n  async persistAll() {}\n\n  async persist() {}\n\n  /* eslint-enable no-empty-function */\n\n  /**\n   * 删除不必要的MessageQueue offset\n   * @param {Object} messageQueue 消息队列\n   * @return {void}\n   */\n  removeOffset(messageQueue) {\n    if (messageQueue) {\n      this.offsetTable.delete(messageQueue.key);\n    }\n  }\n}\n\nmodule.exports = LocalMemoryOffsetStore;\n"
  },
  {
    "path": "lib/store/read_offset_type.js",
    "content": "'use strict';\n\nmodule.exports = {\n  // 只从Memory读取\n  READ_FROM_MEMORY: 'READ_FROM_MEMORY',\n  // 只从存储层读取（本地或者远端）\n  READ_FROM_STORE: 'READ_FROM_STORE',\n  // 先从内存读，内存不存在再从存储层读\n  MEMORY_FIRST_THEN_STORE: 'MEMORY_FIRST_THEN_STORE',\n};\n"
  },
  {
    "path": "lib/store/remote_broker.js",
    "content": "'use strict';\n\nconst is = require('is-type-of');\nconst Base = require('sdk-base');\nconst gather = require('co-gather');\nconst ReadOffsetType = require('./read_offset_type');\n\nclass RemoteBrokerOffsetStore extends Base {\n\n  /**\n   * consume offset store at remote server\n   * @param {MQClient} mqClient - mq client\n   * @param {String} groupName - group name\n   * @class\n   */\n  constructor(mqClient, groupName) {\n    super();\n    this.mqClient = mqClient;\n    this.groupName = groupName;\n\n    this.offsetTable = new Map();\n  }\n\n  get logger() {\n    return this.mqClient.logger;\n  }\n\n  /* eslint-disable no-empty-function */\n  /**\n   * load the offset\n   * @return {void}\n   */\n  async load() {}\n\n  /* eslint-enable no-empty-function */\n\n  /**\n   * update consume offset\n   * @param {MessageQueue} messageQueue - message queue\n   * @param {Number} offset - new offset\n   * @param {Boolean} increaseOnly - force override\n   * @return {void}\n   */\n  updateOffset(messageQueue, offset, increaseOnly) {\n    increaseOnly = increaseOnly || false;\n    if (!messageQueue) {\n      return;\n    }\n\n    let prev = -1;\n    if (this.offsetTable.has(messageQueue.key)) {\n      prev = this.offsetTable.get(messageQueue.key);\n    }\n    if (prev >= offset && increaseOnly) {\n      return;\n    }\n    this.offsetTable.set(messageQueue.key, offset);\n    this.logger.info('[mq:RemoteBrokerOffsetStore] update offset for messageQueue: %s, current offset: %d, prev offset: %s, increaseOnly: %s', messageQueue.key, offset, prev, increaseOnly);\n  }\n\n  /**\n   * read consume offset\n   * @param {MessageQueue} messageQueue - message queue\n   * @param {String} type - consume from where\n   * @return {Promise<Number>} offset\n   */\n  async readOffset(messageQueue, type) {\n    if (!messageQueue) {\n      return -1;\n    }\n\n    try {\n      switch (type) {\n        case ReadOffsetType.MEMORY_FIRST_THEN_STORE:\n        case ReadOffsetType.READ_FROM_MEMORY:\n        {\n          const offset = this.offsetTable.get(messageQueue.key);\n          if (!is.nullOrUndefined(offset)) {\n            return offset;\n          }\n          break;\n        }\n        case ReadOffsetType.READ_FROM_STORE:\n        {\n          const brokerOffset = await this.fetchConsumeOffsetFromBroker(messageQueue);\n          this.updateOffset(messageQueue, brokerOffset, false);\n          return brokerOffset;\n        }\n        default:\n          break;\n      }\n    } catch (err) {\n      err.message = `RemoteBrokerOffsetStore.readOffset failed, topic: ${messageQueue.topic}, group: ${this.groupName}, queueId: ${messageQueue.queueId}, type: ${type} ` + err.message;\n      this.emit('error', err);\n    }\n    return -1;\n  }\n\n  /**\n   * persist all consume offset\n   * @param {Array} mqs - all queues\n   * @return {void}\n   */\n  async persistAll(mqs) {\n    if (!mqs || !mqs.length) {\n      return;\n    }\n\n    const newTable = new Map();\n    const tasks = [];\n    let info = 'persist all consume offset\\n';\n    for (const messageQueue of mqs) {\n      const offset = this.offsetTable.get(messageQueue.key) || -1;\n      newTable.set(messageQueue.key, offset);\n      info += `\\t- topic=\"${messageQueue.topic}\", brokerName=\"${messageQueue.brokerName}\", queueId=\"${messageQueue.queueId}\", consumer offset => ${offset}\\n`;\n      tasks.push(() => this.updateConsumeOffsetToBroker(messageQueue, offset));\n    }\n    this.logger.info('[mq:RemoteBrokerOffsetStore] %s', info);\n    this.offsetTable = newTable;\n    const ret = await gather(tasks);\n    const errors = ret.filter(item => item.isError);\n    for (const err of errors) {\n      this.emit('error', err);\n    }\n  }\n\n  /**\n   * persist consume offset\n   * @param {MessageQueue} messageQueue - message queue\n   * @return {void}\n   */\n  async persist(messageQueue) {\n    const offset = this.offsetTable.get(messageQueue.key);\n    this.logger.info('[mq:RemoteBrokerOffsetStore] persist consume offset of [%s], offset: %d', messageQueue.key, offset);\n    if (is.number(offset)) {\n      await this.updateConsumeOffsetToBroker(messageQueue, offset);\n    }\n  }\n\n  /**\n   * remove message queue\n   * @param {MessageQueue} messageQueue - message queue\n   * @return {void}\n   */\n  removeOffset(messageQueue) {\n    if (messageQueue) {\n      this.offsetTable.delete(messageQueue.key);\n    }\n  }\n\n  /**\n   * fetch consume offset from broker\n   * @param {MessageQueue} messageQueue - message queue\n   * @return {Promise<Number>} brokerOffset\n   */\n  async fetchConsumeOffsetFromBroker(messageQueue) {\n    const requestHeader = {\n      topic: messageQueue.topic,\n      consumerGroup: this.groupName,\n      queueId: messageQueue.queueId,\n    };\n    const findBrokerResult = await this.findBrokerAddressInAdmin(messageQueue);\n    return await this.mqClient.queryConsumerOffset(findBrokerResult.brokerAddr, requestHeader, 1000 * 5);\n  }\n\n  /**\n   * update consume offset to broker\n   * @param {MessageQueue} messageQueue - message queue\n   * @param {Number} offset - offset\n   * @return {void}\n   */\n  async updateConsumeOffsetToBroker(messageQueue, offset) {\n    const requestHeader = {\n      topic: messageQueue.topic,\n      consumerGroup: this.groupName,\n      queueId: messageQueue.queueId,\n      commitOffset: offset,\n    };\n    const findBrokerResult = await this.findBrokerAddressInAdmin(messageQueue);\n    // use oneway, cause this action may cost time.\n    await this.mqClient.updateConsumerOffsetOneway(findBrokerResult.brokerAddr, requestHeader);\n  }\n\n  async findBrokerAddressInAdmin(messageQueue) {\n    let findBrokerResult = this.mqClient.findBrokerAddressInAdmin(messageQueue.brokerName);\n    if (!findBrokerResult) {\n      await this.mqClient.updateTopicRouteInfoFromNameServer(messageQueue.topic);\n      findBrokerResult = this.mqClient.findBrokerAddressInAdmin(messageQueue.brokerName);\n      if (!findBrokerResult) {\n        throw new Error(`The broker[${messageQueue.brokerName}] not exist`);\n      }\n    }\n    return findBrokerResult;\n  }\n}\n\nmodule.exports = RemoteBrokerOffsetStore;\n"
  },
  {
    "path": "lib/utils/index.js",
    "content": "'use strict';\n\n/* eslint no-bitwise:0 */\n\nconst zlib = require('zlib');\nconst MixAll = require('../mix_all');\nconst is = require('is-type-of');\n\n// 压缩\nexports.compress = function(bs) {\n  return zlib.deflateSync(bs);\n};\n\n// 解压\nexports.uncompress = function(bs) {\n  return zlib.inflateSync(bs);\n};\n\n// 解析日期（注意时区为当地时区）\nexports.parseDate = function(str) {\n  return new Date(\n    Number(str.slice(0, 4)),\n    Number(str.slice(4, 6)) - 1,\n    Number(str.slice(6, 8)),\n    Number(str.slice(8, 10)),\n    Number(str.slice(10, 12)),\n    Number(str.slice(12)));\n};\n\n// 返回日期时间格式，精度到秒\n// 格式如下：20131223051900\nexports.timeMillisToHumanString = function(time) {\n  const date = is.date(time) ? time : new Date(time);\n\n  return '' +\n    date.getFullYear() +\n    zeroize(date.getMonth() + 1) +\n    zeroize(date.getDate()) +\n    zeroize(date.getHours()) +\n    zeroize(date.getMinutes()) +\n    zeroize(date.getSeconds());\n};\n\n// 获取字符串的哈希值\nexports.hashCode = function(str) {\n  // s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]\n\n  let hash = 0;\n  const len = str.length;\n  for (let i = 0; i < len; i++) {\n    hash = 31 * hash + str.charCodeAt(i) << 0;\n  }\n\n  return hash & 4294967295;\n};\n\n// 重置重试消息的 Topic\nexports.resetRetryTopic = function(msg, consumerGroup) {\n  const groupTopic = MixAll.getRetryTopic(consumerGroup);\n  if (msg.topic === groupTopic) {\n    msg.topic = msg.retryTopic;\n  }\n};\n\nfunction zeroize(value, length) {\n  if (!length) {\n    length = 2;\n  }\n\n  value = String(value);\n  let zeros = '';\n  for (let i = 0; i < length - value.length; i++) {\n    zeros += '0';\n  }\n  return zeros + value;\n}\n"
  },
  {
    "path": "lib/utils/message_sys_flag.js",
    "content": "'use strict';\n/* eslint no-bitwise: 0 */\n\nexports.CompressedFlag = 1 << 0;\nexports.MultiTagsFlag = 1 << 1;\n\n/**\n * 7 6 5 4 3 2 1 0\n * SysFlag 事务相关，从左属，2与3\n */\nexports.TransactionNotType = 0 << 2;\nexports.TransactionPreparedType = 1 << 2;\nexports.TransactionCommitType = 2 << 2;\nexports.TransactionRollbackType = 3 << 2;\n\nexports.getTransactionValue = function(flag) {\n  return flag & exports.TransactionRollbackType;\n};\n\nexports.resetTransactionValue = function(flag, type) {\n  return flag & ~exports.TransactionRollbackType | type;\n};\n\nexports.clearCompressedFlag = function(flag) {\n  return flag & ~exports.CompressedFlag;\n};\n"
  },
  {
    "path": "lib/utils/pull_sys_flag.js",
    "content": "'use strict';\n/* eslint no-bitwise: 0 */\n\nconst FLAG_COMMIT_OFFSET = 1 << 0;\nconst FLAG_SUSPEND = 1 << 1;\nconst FLAG_SUBSCRIPTION = 1 << 2;\nconst FLAG_CLASS_FILTER = 1 << 3;\n\nexports.buildSysFlag = function(commitOffset, suspend, subscription, classFilter) {\n  let flag = 0;\n  if (commitOffset) {\n    flag |= FLAG_COMMIT_OFFSET;\n  }\n\n  if (suspend) {\n    flag |= FLAG_SUSPEND;\n  }\n\n  if (subscription) {\n    flag |= FLAG_SUBSCRIPTION;\n  }\n\n  if (classFilter) {\n    flag |= FLAG_CLASS_FILTER;\n  }\n  return flag;\n};\n\nexports.clearCommitOffsetFlag = function(sysFlag) {\n  return sysFlag & ~FLAG_COMMIT_OFFSET;\n};\n\nexports.hasCommitOffsetFlag = function(sysFlag) {\n  return (sysFlag & FLAG_COMMIT_OFFSET) === FLAG_COMMIT_OFFSET;\n};\n\nexports.hasSuspendFlag = function(sysFlag) {\n  return (sysFlag & FLAG_SUSPEND) === FLAG_SUSPEND;\n};\n\nexports.hasSubscriptionFlag = function(sysFlag) {\n  return (sysFlag & FLAG_SUBSCRIPTION) === FLAG_SUBSCRIPTION;\n};\n\nexports.hasClassFilterFlag = function(sysFlag) {\n  return (sysFlag & FLAG_CLASS_FILTER) === FLAG_CLASS_FILTER;\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"ali-ons\",\n  \"version\": \"3.12.2\",\n  \"description\": \"Aliyun Open Notification Service Client\",\n  \"main\": \"lib/index.js\",\n  \"files\": [\n    \"lib\"\n  ],\n  \"scripts\": {\n    \"pkgfiles\": \"egg-bin pkgfiles --check\",\n    \"lint\": \"eslint . --ext .js\",\n    \"test\": \"npm run lint && npm run test-local\",\n    \"test-local\": \"TEST_TIMEOUT=120000 egg-bin test\",\n    \"cov\": \"TEST_TIMEOUT=120000 egg-bin cov\",\n    \"ci\": \"npm run pkgfiles && npm run lint && npm run cov\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ali-sdk/ali-ons.git\"\n  },\n  \"keywords\": [\n    \"ons\",\n    \"aliyun\",\n    \"ali-sdk\"\n  ],\n  \"author\": \"gxcsoccer <gxcsoccer@126.com>\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ali-sdk/ali-ons/issues\"\n  },\n  \"homepage\": \"https://github.com/ali-sdk/ali-ons#readme\",\n  \"dependencies\": {\n    \"address\": \"^1.1.2\",\n    \"binary-search-insert\": \"^1.0.3\",\n    \"byte\": \"^2.0.0\",\n    \"bytes\": \"^3.1.0\",\n    \"co-gather\": \"^1.0.1\",\n    \"debug\": \"^4.3.1\",\n    \"is-type-of\": \"^1.2.1\",\n    \"JSON2\": \"^0.1.0\",\n    \"long\": \"^4.0.0\",\n    \"mkdirp\": \"^1.0.4\",\n    \"mz-modules\": \"^2.1.0\",\n    \"osenv\": \"^0.1.5\",\n    \"sdk-base\": \"^3.6.0\",\n    \"tcp-base\": \"^3.1.0\",\n    \"utility\": \"^1.17.0\"\n  },\n  \"devDependencies\": {\n    \"egg-bin\": \"^4.16.1\",\n    \"eslint\": \"^7.25.0\",\n    \"eslint-config-egg\": \"^9.0.0\",\n    \"mm\": \"^3.2.0\",\n    \"nyc\": \"^17.1.0\",\n    \"urllib\": \"^2.37.1\"\n  },\n  \"engines\": {\n    \"node\": \">= 8.0.0\"\n  }\n}\n"
  },
  {
    "path": "test/allocate_message_queue_averagely.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst AllocateMessageQueueAveragely = require('../lib/consumer/rebalance/allocate_message_queue_averagely');\n\ndescribe('test/allocate_message_queue_averagely.test.js', function() {\n\n  const allocator = new AllocateMessageQueueAveragely();\n\n  it('should allocate queue averagely', function() {\n    assert(allocator.name === 'AVG');\n\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2' ]);\n    assert.deepEqual(result, [ 1, 2, 3, 4 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2' ]);\n    assert.deepEqual(result, [ 5, 6, 7, 8 ]);\n  });\n\n  it('should allocate ok, indivisible', function() {\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2', '3' ]);\n    assert.deepEqual(result, [ 1, 2, 3 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2', '3' ]);\n    assert.deepEqual(result, [ 4, 5, 6 ]);\n    result = allocator.allocate('test', '3', [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2', '3' ]);\n    assert.deepEqual(result, [ 7, 8 ]);\n  });\n\n  it('should allocate ok, indivisible 2', function() {\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5 ], [ '1', '2' ]);\n    assert.deepEqual(result, [ 1, 2, 3 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5 ], [ '1', '2' ]);\n    assert.deepEqual(result, [ 4, 5 ]);\n  });\n\n  it('should allocate ok, indivisible 3', function() {\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3' ]);\n    assert.deepEqual(result, [ 1, 2 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3' ]);\n    assert.deepEqual(result, [ 3, 4 ]);\n    result = allocator.allocate('test', '3', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3' ]);\n    assert.deepEqual(result, [ 5 ]);\n  });\n\n  it('should allocate ok, indivisible 4', function() {\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4' ]);\n    assert.deepEqual(result, [ 1, 2 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4' ]);\n    assert.deepEqual(result, [ 3 ]);\n    result = allocator.allocate('test', '3', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4' ]);\n    assert.deepEqual(result, [ 4 ]);\n    result = allocator.allocate('test', '4', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4' ]);\n    assert.deepEqual(result, [ 5 ]);\n  });\n\n  it('should allocate ok, indivisible 5', function() {\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5' ]);\n    assert.deepEqual(result, [ 1 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5' ]);\n    assert.deepEqual(result, [ 2 ]);\n    result = allocator.allocate('test', '3', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5' ]);\n    assert.deepEqual(result, [ 3 ]);\n    result = allocator.allocate('test', '4', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5' ]);\n    assert.deepEqual(result, [ 4 ]);\n    result = allocator.allocate('test', '5', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5' ]);\n    assert.deepEqual(result, [ 5 ]);\n  });\n\n  it('should allocate ok, indivisible 6', function() {\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5', '6' ]);\n    assert.deepEqual(result, [ 1 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5', '6' ]);\n    assert.deepEqual(result, [ 2 ]);\n    result = allocator.allocate('test', '3', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5', '6' ]);\n    assert.deepEqual(result, [ 3 ]);\n    result = allocator.allocate('test', '4', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5', '6' ]);\n    assert.deepEqual(result, [ 4 ]);\n    result = allocator.allocate('test', '5', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5', '6' ]);\n    assert.deepEqual(result, [ 5 ]);\n    result = allocator.allocate('test', '6', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5', '6' ]);\n    assert.deepEqual(result, []);\n  });\n\n  it('should throw error if currentId is null', function() {\n    assert.throws(() => {\n      allocator.allocate('test', null, [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2' ]);\n    }, /currentCID is empty/);\n  });\n\n  it('should throw error if mqAll is empty', function() {\n    assert.throws(() => {\n      allocator.allocate('test', '1', [], [ '1', '2' ]);\n    }, /mqAll is null or mqAll empty/);\n  });\n\n  it('should throw error if cidAll is empty', function() {\n    assert.throws(() => {\n      allocator.allocate('test', '1', [ 1, 2, 3, 4, 5, 6, 7, 8 ], []);\n    }, /cidAll is null or cidAll empty/);\n  });\n\n  it('should get empty array if currentId not exists in cidAll', function() {\n    const result = allocator.allocate('test', 'not exists', [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2' ]);\n    assert.deepEqual(result, []);\n  });\n});\n"
  },
  {
    "path": "test/channel.test.js",
    "content": "'use strict';\n\nconst mm = require('mm');\nconst net = require('net');\nconst assert = require('assert');\nconst urllib = require('urllib');\nconst sleep = require('mz-modules/sleep');\nconst Channel = require('../lib/channel');\nconst config = require('../example/config');\nconst RequestCode = require('../lib/protocol/request_code');\nconst ResponseCode = require('../lib/protocol/response_code');\nconst RemotingCommand = require('../lib/protocol/command/remoting_command');\nconst localIp = require('address').ip();\nconst { nameSrv } = require('../example/config');\n\nconst TOPIC = config.topic;\n\ndescribe('test/channel.test.js', function() {\n  const onsAddr = 'http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet';\n  let address;\n  before(function(done) {\n    urllib.request(onsAddr, function(err, data, result) {\n      if (err) {\n        return done(err);\n      }\n      if (result && result.status === 200) {\n        address = data.toString().trim().split(';')[0];\n        done();\n      } else {\n        done(new Error('no addresses'));\n      }\n    });\n  });\n\n  afterEach(mm.restore);\n\n  it('should connect ok', () => {\n    const channel = new Channel(nameSrv);\n    return channel.ready();\n  });\n\n  it('should close ok', done => {\n    const channel = new Channel(nameSrv);\n    channel.ready(err => {\n      if (err) {\n        done(err);\n        return;\n      }\n      assert(channel._socket);\n      channel.close();\n    });\n    channel.on('close', () => {\n      assert(!channel._socket);\n      done();\n    });\n  });\n\n  it('should invoke ok', async () => {\n    const channel = new Channel(nameSrv, config);\n    await channel.ready();\n    const res = await channel.invoke(new RemotingCommand({\n      code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n      customHeader: {\n        topic: TOPIC,\n      },\n    }), 5000);\n    assert(res);\n    assert(res.body);\n    channel.close();\n  });\n\n  it('should invoke ok though channel not ready', async () => {\n    const channel = new Channel(address);\n    const res = await channel.invoke(new RemotingCommand({\n      code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n      customHeader: {\n        topic: TOPIC,\n      },\n    }), 5000);\n    assert(res);\n    assert(res.remark && res.remark.includes('__accessKey is blank'));\n    channel.close();\n  });\n\n  it('should invoke oneway ok', async () => {\n    const channel = new Channel(nameSrv);\n    await channel.invokeOneway(new RemotingCommand({\n      code: RequestCode.GET_KV_CONFIG_BY_VALUE,\n      customHeader: {\n        namespace: 'PROJECT_CONFIG',\n        key: localIp,\n      },\n    }));\n    await channel.ready();\n  });\n\n  it('should close if timeout', async () => {\n    const server = net.createServer();\n    server.listen();\n    const { port } = server.address();\n    await sleep(100);\n\n    const channel = new Channel(`127.0.0.1:${port}`);\n    await assert.rejects(async () => {\n      await channel.invoke(new RemotingCommand({\n        code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n        customHeader: {\n          topic: TOPIC,\n        },\n      }), 5000);\n    }, {\n      name: 'ResponseTimeoutError',\n    });\n    await sleep(100);\n\n    assert(!channel._socket);\n\n    server.close();\n  });\n\n  // it('should emit error if connect failed', function(done) {\n  //   done = pedding(done, 2);\n  //   const channel = new Channel('100.100.100.100:9876');\n  //   channel.once('close', function() {\n  //     console.log('channel closed')\n  //     done();\n  //   });\n  //   channel.once('error', function(err) {\n  //     console.log('channel error', err);\n  //     should.exist(err);\n  //     done();\n  //   });\n  // });\n\n  it('should get error response', async () => {\n    const channel = new Channel('100.100.100.100:9876');\n    try {\n      await channel.invoke(new RemotingCommand({\n        code: RequestCode.GET_KV_CONFIG_BY_VALUE,\n        customHeader: {\n          namespace: 'PROJECT_CONFIG',\n          key: localIp,\n        },\n      }));\n    } catch (err) {\n      console.log(err);\n    }\n    channel.close();\n  });\n\n  // it.only('should throw error if command is invalid', function*() {\n  //   const channel = new Channel(nameSrv);\n  //   let isError = false;\n  //   try {\n  //     yield channel.invoke('invalid command', 5000);\n  //   } catch (err) {\n  //     isError = true;\n  //     err.name.should.equal('MQInvalidCommandError');\n  //     err.message.should.equal('Invalid command');\n  //   }\n  //   isError.should.be.ok();\n  //   channel.close();\n  //   channel.close();\n  // });\n\n  it('should clearupInvokes after connection close', async () => {\n    const channel = new Channel(nameSrv);\n    await channel.ready();\n    channel.close();\n    let isError = false;\n    try {\n      await channel.invoke(new RemotingCommand({\n        code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n        customHeader: {\n          topic: 'NOT_EXISTS',\n        },\n      }), 5000);\n    } catch (err) {\n      isError = true;\n      assert(err.message === `The socket was closed. (address: ${nameSrv})`);\n    }\n    assert(isError);\n  });\n\n  // it('should throw error if invoke after close', function(done) {\n  //   const channel = new Channel(nameSrv);\n  //   channel.on('close', function() {\n  //     channel.invoke(new RemotingCommand({\n  //       code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n  //       customHeader: {\n  //         topic: 'NOT_EXISTS'\n  //       }\n  //     }), 5000, function(err, res) {\n  //       should.exist(err);\n  //       err.message.should.equal(`The socket is already closed`);\n  //       done();\n  //     });\n  //   });\n  //   channel.close();\n  // });\n\n  it('should invoke response ok', async () => {\n    const channel = new Channel(nameSrv);\n    await channel.ready();\n    await channel.invokeOneway(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, 1, ''));\n  });\n\n  it('should throw error if invoke timeout', async () => {\n    const channel = new Channel(nameSrv);\n    let isError = false;\n    try {\n      await channel.invoke(new RemotingCommand({\n        code: RequestCode.GET_KV_CONFIG_BY_VALUE,\n        customHeader: {\n          namespace: 'PROJECT_CONFIG',\n          key: localIp,\n        },\n      }), 1);\n    } catch (err) {\n      isError = true;\n      assert(err.name === 'ResponseTimeoutError');\n      assert(err.message === `Server no response in 1ms, address#${nameSrv}`);\n    }\n    channel.close();\n    assert(isError);\n  });\n\n  // it('should throw error if connection close by remote server', function(done) {\n  //   const channel = new Channel(nameSrv);\n  //   yield channel.ready();\n  //   channel.invoke(new RemotingCommand({\n  //     code: RequestCode.GET_KV_CONFIG_BY_VALUE,\n  //     customHeader: {\n  //       namespace: 'PROJECT_CONFIG',\n  //       key: localIp,\n  //     },\n  //   }), 1, function(err, res) {\n  //     should.exist(err);\n  //     channel.invokes.size.should.equal(0);\n  //     err.name.should.equal('MQSocketClosedError');\n  //     err.message.should.containEql(`${address} conn closed by remote server`);\n  //     done();\n  //   });\n  //   channel.close();\n  // });\n\n});\n"
  },
  {
    "path": "test/consumer/rebalance/allocate_message_queue_averagely.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst AllocateMessageQueueAveragely = require('../../../lib/consumer/rebalance/allocate_message_queue_averagely');\n\ndescribe('test/allocate_message_queue_averagely.test.js', function() {\n  const allocator = new AllocateMessageQueueAveragely();\n  it('should allocate queue averagely', function() {\n    assert(allocator.name === 'AVG');\n\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2' ]);\n    assert.deepEqual(result, [ 1, 2, 3, 4 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2' ]);\n    assert.deepEqual(result, [ 5, 6, 7, 8 ]);\n  });\n\n  it('should allocate ok, indivisible', function() {\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2', '3' ]);\n    assert.deepEqual(result, [ 1, 2, 3 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2', '3' ]);\n    assert.deepEqual(result, [ 4, 5, 6 ]);\n    result = allocator.allocate('test', '3', [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2', '3' ]);\n    assert.deepEqual(result, [ 7, 8 ]);\n  });\n\n  it('should allocate ok, indivisible 2', function() {\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5 ], [ '1', '2' ]);\n    assert.deepEqual(result, [ 1, 2, 3 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5 ], [ '1', '2' ]);\n    assert.deepEqual(result, [ 4, 5 ]);\n  });\n\n  it('should allocate ok, indivisible 3', function() {\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3' ]);\n    assert.deepEqual(result, [ 1, 2 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3' ]);\n    assert.deepEqual(result, [ 3, 4 ]);\n    result = allocator.allocate('test', '3', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3' ]);\n    assert.deepEqual(result, [ 5 ]);\n  });\n\n  it('should allocate ok, indivisible 4', function() {\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4' ]);\n    assert.deepEqual(result, [ 1, 2 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4' ]);\n    assert.deepEqual(result, [ 3 ]);\n    result = allocator.allocate('test', '3', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4' ]);\n    assert.deepEqual(result, [ 4 ]);\n    result = allocator.allocate('test', '4', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4' ]);\n    assert.deepEqual(result, [ 5 ]);\n  });\n\n  it('should allocate ok, indivisible 5', function() {\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5' ]);\n    assert.deepEqual(result, [ 1 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5' ]);\n    assert.deepEqual(result, [ 2 ]);\n    result = allocator.allocate('test', '3', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5' ]);\n    assert.deepEqual(result, [ 3 ]);\n    result = allocator.allocate('test', '4', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5' ]);\n    assert.deepEqual(result, [ 4 ]);\n    result = allocator.allocate('test', '5', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5' ]);\n    assert.deepEqual(result, [ 5 ]);\n  });\n\n  it('should allocate ok, indivisible 6', function() {\n    let result = allocator.allocate('test', '1', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5', '6' ]);\n    assert.deepEqual(result, [ 1 ]);\n    result = allocator.allocate('test', '2', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5', '6' ]);\n    assert.deepEqual(result, [ 2 ]);\n    result = allocator.allocate('test', '3', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5', '6' ]);\n    assert.deepEqual(result, [ 3 ]);\n    result = allocator.allocate('test', '4', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5', '6' ]);\n    assert.deepEqual(result, [ 4 ]);\n    result = allocator.allocate('test', '5', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5', '6' ]);\n    assert.deepEqual(result, [ 5 ]);\n    result = allocator.allocate('test', '6', [ 1, 2, 3, 4, 5 ], [ '1', '2', '3', '4', '5', '6' ]);\n    assert.deepEqual(result, []);\n  });\n\n  it('should throw error if currentId is null', function() {\n    assert.throws(() => {\n      allocator.allocate('test', null, [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2' ]);\n    }, /currentCID is empty/);\n  });\n\n  it('should throw error if mqAll is empty', function() {\n    assert.throws(() => {\n      allocator.allocate('test', '1', [], [ '1', '2' ]);\n    }, /mqAll is null or mqAll empty/);\n  });\n\n  it('should throw error if cidAll is empty', function() {\n    assert.throws(() => {\n      allocator.allocate('test', '1', [ 1, 2, 3, 4, 5, 6, 7, 8 ], []);\n    }, /cidAll is null or cidAll empty/);\n  });\n\n  it('should get empty array if currentId not exists in cidAll', function() {\n    const result = allocator.allocate('test', 'not exists', [ 1, 2, 3, 4, 5, 6, 7, 8 ], [ '1', '2' ]);\n    assert.deepEqual(result, []);\n  });\n});\n"
  },
  {
    "path": "test/index.test.js",
    "content": "'use strict';\n\nconst mm = require('mm');\nconst path = require('path');\nconst osenv = require('osenv');\nconst assert = require('assert');\nconst httpclient = require('urllib');\nconst utils = require('../lib/utils');\nconst Message = require('../').Message;\nconst Consumer = require('../').Consumer;\nconst Producer = require('../').Producer;\nconst sleep = require('mz-modules/sleep');\nconst rimraf = require('mz-modules/rimraf');\nconst config = require('../example/config');\nconst MixAll = require('../lib/mix_all');\n\nconst TOPIC = config.topic;\n\nconst localOffsetStoreDir = path.join(osenv.home(), '.rocketmq_offsets_node');\n\ndescribe('test/index.test.js', () => {\n  describe('API', () => {\n    let producer;\n    let consumer;\n    before(async () => {\n      producer = new Producer(Object.assign({\n        httpclient,\n        retryAnotherBrokerWhenNotStoreOK: true,\n        compressMsgBodyOverHowmuch: 10,\n      }, config));\n      await producer.ready();\n      consumer = new Consumer(Object.assign({\n        httpclient,\n        rebalanceInterval: 1000,\n        isBroadcast: true,\n      }, config));\n      await consumer.ready();\n\n      consumer.subscribe(TOPIC, async msg => {\n        console.log('message receive ------------> ', msg.body.toString());\n      });\n    });\n\n    after(async () => {\n      await producer.close();\n      await consumer.close();\n    });\n\n    afterEach(mm.restore);\n\n    // it.skip('it should create topic ok', function*() {\n    //   yield producer.createTopic('TBW102', 'XXX', 8);\n    // });\n\n    it('should select another broker if one failed', async () => {\n      mm(producer, 'sendKernelImpl', async () => {\n        mm.restore();\n        return {\n          sendStatus: 'FLUSH_DISK_TIMEOUT',\n        };\n      });\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello TagA !!! ' // body\n      );\n\n      let sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n\n      mm(producer, 'sendKernelImpl', async () => {\n        mm.restore();\n        const err = new Error('mock err');\n        err.name = 'MQClientException';\n        err.code = 205;\n        throw err;\n      });\n      sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n    });\n\n    it('should tryToFindTopicPublishInfo if brokerAddr not found', async () => {\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello TagA !!! ' // body\n      );\n\n      let sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n\n      mm(producer._mqClient, 'findBrokerAddressInPublish', () => {\n        mm.restore();\n        return null;\n      });\n      sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n\n      mm(producer._mqClient, 'findBrokerAddressInPublish', () => {\n        return null;\n      });\n\n      let isError = false;\n      try {\n        await producer.send(msg);\n      } catch (err) {\n        isError = true;\n        assert(/The broker\\[.+\\] not exist/i.test(err.message));\n        assert(err.name === 'MQClientException');\n      }\n      assert(isError);\n      mm.restore();\n\n      isError = false;\n      try {\n        await producer.send(new Message('%RETRY%' + TOPIC, // topic\n          'TagA', // tag\n          'Hello TagA !!! ' // body\n        ));\n      } catch (err) {\n        isError = true;\n        assert(err.name === 'MQClientException');\n      }\n      assert(isError);\n    });\n\n    it('should emit error if compress failed', done => {\n      mm(utils, 'compress', function() {\n        throw new Error('mock error');\n      });\n\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello TagA !!!   sds' // body\n      );\n      producer.on('error', err => {\n        assert(err.name === 'MetaQCompressError');\n        done();\n      });\n      (async () => {\n        try {\n          const sendResult = await producer.send(msg);\n          assert(sendResult && sendResult.msgId);\n          console.log('over');\n        } catch (err) {\n          console.log(err);\n        }\n      })();\n    });\n\n    it('should updateProcessQueueTableInRebalance ok', async () => {\n      await sleep(3000);\n      await consumer.rebalanceByTopic(TOPIC);\n      const size = consumer.processQueueTable.size;\n      assert(size > 0);\n\n      const key = Array.from(consumer.processQueueTable.keys())[0];\n      const obj = consumer.processQueueTable.get(key);\n      const processQueue = obj.processQueue;\n      processQueue.lastPullTimestamp = 10000;\n\n      await consumer.rebalanceByTopic(TOPIC);\n      assert(consumer.processQueueTable.size === size);\n\n      await consumer.updateProcessQueueTableInRebalance(TOPIC, []);\n      assert(consumer.processQueueTable.size === 0);\n      await consumer.updateProcessQueueTableInRebalance(MixAll.getRetryTopic(consumer.consumerGroup), []);\n      assert(consumer.processQueueTable.size === 0);\n    });\n\n    it('should computePullFromWhere ok', async () => {\n      mm(consumer._offsetStore, 'readOffset', async () => {\n        return -1;\n      });\n      mm(consumer, 'consumeFromWhere', 'CONSUME_FROM_LAST_OFFSET');\n\n      let offset = await consumer.computePullFromWhere({\n        topic: '%RETRY%__',\n      });\n      assert(offset === 0);\n\n      mm(consumer, 'consumeFromWhere', 'CONSUME_FROM_TIMESTAMP');\n      mm(consumer._mqClient, 'maxOffset', async () => {\n        return 1000;\n      });\n      offset = await consumer.computePullFromWhere({\n        topic: '%RETRY%__',\n      });\n      assert(offset === 1000);\n\n      mm.error(consumer._mqClient, 'maxOffset');\n      offset = await consumer.computePullFromWhere({\n        topic: '%RETRY%__',\n      });\n      assert(offset === -1);\n\n      mm(consumer._offsetStore, 'readOffset', async () => {\n        return 100;\n      });\n      offset = await consumer.computePullFromWhere({\n        topic: 'TP',\n      });\n      assert(offset === 100);\n\n      mm(consumer, 'consumeFromWhere', 'CONSUME_FROM_FIRST_OFFSET');\n      offset = await consumer.computePullFromWhere({\n        topic: 'TP',\n      });\n      assert(offset === 100);\n\n      mm(consumer, 'consumeFromWhere', 'NOT_EXISTS');\n      offset = await consumer.computePullFromWhere({\n        topic: 'TP',\n      });\n      assert(offset === -1);\n    });\n\n    it('should support namespace', () => {\n      mm(producer, 'namespace', 'xxx');\n      mm(consumer, 'namespace', 'xxx');\n\n      assert(consumer.consumerGroup === `xxx%${config.consumerGroup}`);\n      assert(producer.producerGroup === `xxx%${config.producerGroup}`);\n\n      assert(consumer.formatTopic(`%RETRY%${consumer.consumerGroup}`) === `%RETRY%${consumer.consumerGroup}`);\n      assert(producer.formatTopic('TEST_TOPIC') === 'xxx%TEST_TOPIC');\n      assert(consumer.formatTopic('TEST_TOPIC') === 'xxx%TEST_TOPIC');\n    });\n  });\n\n  // 广播消费\n  describe('broadcast', () => {\n    [\n      'CONSUME_FROM_LAST_OFFSET_AND_FROM_MIN_WHEN_BOOT_FIRST',\n      'CONSUME_FROM_MIN_OFFSET',\n      'CONSUME_FROM_MAX_OFFSET',\n      'CONSUME_FROM_LAST_OFFSET',\n      'CONSUME_FROM_FIRST_OFFSET',\n      'CONSUME_FROM_TIMESTAMP',\n    ].forEach(consumeFromWhere => {\n      let consumer;\n      let producer;\n      describe(`consumeFromWhere => ${consumeFromWhere}`, () => {\n        before(() => {\n          return rimraf(localOffsetStoreDir);\n        });\n        before(async () => {\n          consumer = new Consumer(Object.assign({\n            httpclient,\n            consumeFromWhere,\n            isBroadcast: true,\n            persistent: true,\n            rebalanceInterval: 2000,\n          }, config));\n          producer = new Producer(Object.assign({\n            httpclient,\n          }, config));\n\n          await consumer.ready();\n          await producer.ready();\n        });\n\n        after(async () => {\n          await producer.close();\n          const originFn = consumer._offsetStore.persistAll;\n          mm(consumer._offsetStore, 'persistAll', async mqs => {\n            assert(mqs && mqs.length);\n            return await originFn.call(consumer._offsetStore, mqs);\n          });\n          await consumer.close();\n        });\n\n        afterEach(mm.restore);\n\n        it('should subscribe message ok', async () => {\n          let msgId;\n          const received = new Set();\n          consumer.subscribe(TOPIC, 'TagA', async msg => {\n            console.log('message receive ------------> ', msg.msgId, msg.body.toString());\n            assert(msg.tags !== 'TagB');\n            received.add(msg.msgId);\n            if (msgId && received.has(msgId)) {\n              assert(msg.body.toString() === 'Hello MetaQ !!! ');\n              consumer.emit('TagA');\n            }\n          });\n\n          await sleep(5000);\n\n          let msg = new Message(TOPIC, // topic\n            'TagB', // tag\n            'Hello MetaQ !!! ' // body\n          );\n          let sendResult = await producer.send(msg);\n          assert(sendResult && sendResult.msgId);\n          msg = new Message(TOPIC, // topic\n            'TagA', // tag\n            'Hello MetaQ !!! ' // body\n          );\n          sendResult = await producer.send(msg);\n          assert(sendResult && sendResult.msgId);\n          msgId = sendResult.msgId;\n          console.log('send message success,', sendResult.msgId);\n\n          if (!received.has(msgId)) {\n            await consumer.await('TagA');\n          }\n        });\n\n        it.skip('should viewMessage ok', async () => {\n          const msg = new Message(TOPIC, // topic\n            'TagA', // tag\n            'Hello MetaQ !!! ' // body\n          );\n          const sendResult = await producer.send(msg);\n          assert(sendResult && sendResult.msgId);\n\n          await sleep(3000);\n\n          const message = await consumer.viewMessage(sendResult.msgId);\n          assert(message.body.toString() === 'Hello MetaQ !!! ');\n        });\n      });\n    });\n  });\n\n  // 集群消费\n  describe('cluster', () => {\n    let consumer;\n    let producer;\n    beforeEach(async () => {\n      consumer = new Consumer(Object.assign({\n        httpclient,\n        isBroadcast: false,\n      }, config));\n      await consumer.ready();\n      producer = new Producer(Object.assign({\n        httpclient,\n      }, config));\n      await producer.ready();\n    });\n\n    afterEach(async () => {\n      await consumer.close();\n      await producer.close();\n    });\n\n    afterEach(mm.restore);\n\n    it('should subscribe message ok', async () => {\n      await sleep(3000);\n\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello MetaQ !!! ' // body\n      );\n      const sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n\n      const msgId = sendResult.msgId;\n      console.log(sendResult);\n\n      await new Promise(r => {\n        consumer.subscribe(TOPIC, '*', async msg => {\n          if (msg.msgId === msgId) {\n            assert(msg.body.toString() === 'Hello MetaQ !!! ');\n            r();\n          }\n        });\n      });\n    });\n\n    it.skip('should subscribe message with SQL92 expression type ok', async () => {\n      // 公有云未开启 SQL 过滤\n      await sleep(3000);\n\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello MetaQ !!! ' // body\n      );\n      msg.properties.a = '1';\n      const sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n\n      const msgId = sendResult.msgId;\n      console.log(sendResult);\n\n      await new Promise(r => {\n        consumer.subscribe(TOPIC, {\n          expressionType: 'SQL92',\n          subString: 'a IS NOT NULL',\n        }, async msg => {\n          if (msg.msgId === msgId) {\n            assert(msg.body.toString() === 'Hello MetaQ !!! ');\n            r();\n          }\n        });\n      });\n    });\n  });\n\n  describe('process exception', () => {\n    let consumer;\n    let producer;\n    const logger = {\n      info() {},\n      warn() {},\n      error(...args) {\n        console.error(...args);\n      },\n      debug() {},\n    };\n    beforeEach(async () => {\n      consumer = new Consumer(Object.assign({\n        httpclient,\n        logger,\n        rebalanceInterval: 2000,\n        maxReconsumeTimes: 2,\n      }, config));\n      producer = new Producer(Object.assign({\n        httpclient,\n        logger,\n      }, config));\n      await consumer.ready();\n      await producer.ready();\n    });\n\n    afterEach(async () => {\n      await producer.close();\n      await consumer.close();\n    });\n\n    it('should not correctTags if process queue not empty', () => {\n      let done = false;\n      mm(consumer._offsetStore, 'updateOffset', () => {\n        done = true;\n      });\n      consumer.correctTagsOffset({\n        processQueue: {\n          msgCount: 0,\n        },\n      });\n      assert(done);\n      done = true;\n      mm.restore();\n      mm(consumer._offsetStore, 'updateOffset', () => {\n        done = false;\n      });\n      consumer.correctTagsOffset({\n        processQueue: {\n          msgCount: 1,\n        },\n      });\n      assert(done);\n      mm.restore();\n    });\n\n    it('should retry(consume later) if process failed', async () => {\n      let msgId;\n      consumer.subscribe(TOPIC, '*', async msg => {\n        console.warn('message receive ------------> ', msg.body.toString());\n        if (msg.msgId === msgId || msg.originMessageId === msgId) {\n          assert(msg.body.toString() === 'Hello MetaQ !!! ');\n          if (msg.reconsumeTimes === 0) {\n            throw new Error('mock error');\n          }\n          consumer.emit('*');\n        }\n      });\n\n      await sleep(5000);\n\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello MetaQ !!! ' // body\n      );\n      const sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n      msgId = sendResult.msgId;\n      await consumer.await('*');\n    });\n\n    it('broker should drop message if reconsumeTimes gt maxReconsumeTimes', async () => {\n      let msgId;\n      let reconsumeTimes = 0;\n\n      const randomNumber = Math.floor(Math.random() * 100) + 1;\n\n      const msgBody = 'Hello MetaQ !!!' + randomNumber;\n\n      consumer.subscribe(TOPIC, '*', async msg => {\n        console.warn('message receive ------------> ', msg.body.toString(), msg.reconsumeTimes);\n        if (msg.msgId === msgId || msg.originMessageId && (msg.originMessageId === msgId)) {\n          assert(msg.body.toString() === msgBody);\n          reconsumeTimes = msg.reconsumeTimes;\n          throw new Error('mock error');\n        }\n      });\n\n      await sleep(5000);\n\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        msgBody // body\n      );\n      const sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n      msgId = sendResult.msgId;\n\n      await sleep(60000);\n\n      assert(reconsumeTimes === 2);\n    });\n\n    it('should retry(retry message) if process failed', async () => {\n      let msgId;\n      mm(consumer._mqClient, 'consumerSendMessageBack', async () => {\n        throw new Error('mock error');\n      });\n      consumer.subscribe(TOPIC, '*', async msg => {\n        console.warn('message receive ------------> ', msg.msgId, msg.originMessageId, msg.body.toString());\n        if (msg.msgId === msgId || msg.originMessageId === msgId) {\n          assert(msg.body.toString() === 'Hello MetaQ !!! ');\n          if (msg.reconsumeTimes === 0) {\n            throw new Error('mock error');\n          }\n          consumer.emit('*');\n        }\n      });\n\n      await sleep(5000);\n\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello MetaQ !!! ' // body\n      );\n      const sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n      msgId = sendResult.msgId;\n\n      console.log('msgId -->', msgId);\n      await consumer.await('*');\n    });\n\n    it('should retry if process return ACTION_RETRY', async () => {\n      let msgId;\n      consumer.subscribe(TOPIC, '*', async msg => {\n        console.warn('message receive ------------> ', msg.body.toString());\n        if (msg.msgId === msgId || msg.originMessageId === msgId) {\n          assert(msg.body.toString() === 'retry');\n          if (msg.reconsumeTimes === 0) {\n            return Consumer.ACTION_RETRY;\n          }\n          if (msg.reconsumeTimes === 1) {\n            consumer.emit('*');\n          }\n        }\n      });\n\n      await sleep(5000);\n\n      const msg = new Message(TOPIC, // topic\n        'TagRetry', // tag\n        'retry' // body\n      );\n      const sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n      msgId = sendResult.msgId;\n      await consumer.await('*');\n    });\n\n    it('should retry(fall to local) if process failed', async () => {\n      let msgId;\n      mm(consumer, 'sendMessageBack', async () => {\n        throw new Error('mock error');\n      });\n      consumer.subscribe(TOPIC, '*', async msg => {\n        console.warn('message receive ------------> ', msg.body.toString());\n        if (msg.msgId === msgId) {\n          assert(msg.body.toString() === 'Hello MetaQ !!! ');\n          if (msg.reconsumeTimes === 0) {\n            throw new Error('mock error');\n          }\n          consumer.emit('*');\n        }\n      });\n\n      await sleep(5000);\n\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello MetaQ !!! ' // body\n      );\n      const sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n      msgId = sendResult.msgId;\n      await consumer.await('*');\n    });\n  });\n\n  describe('flow control', () => {\n    let consumer;\n    let producer;\n    before(async () => {\n      await rimraf(localOffsetStoreDir);\n      consumer = new Consumer(Object.assign({\n        httpclient,\n        isBroadcast: true,\n        rebalanceInterval: 2000,\n        pullThresholdForQueue: 1,\n        consumeFromWhere: 'CONSUME_FROM_FIRST_OFFSET',\n        pullTimeDelayMillsWhenFlowControl: 5000,\n      }, config));\n      producer = new Producer(Object.assign({\n        httpclient,\n      }, config));\n      await consumer.ready();\n      await producer.ready();\n    });\n\n    after(async () => {\n      await producer.close();\n      await consumer.close();\n    });\n\n    it('should retry if process failed', async () => {\n      let count = 20;\n      while (count--) {\n        const msg = new Message(TOPIC, // topic\n          'TagA', // tag\n          'Hello MetaQ !!! ' // body\n        );\n        const sendResult = await producer.send(msg);\n        assert(sendResult && sendResult.msgId);\n      }\n\n      consumer.subscribe(TOPIC, '*', async (msg, mq, pq) => {\n        console.log('message receive ------------> ', msg.body.toString());\n\n        const msgCount = pq.msgCount;\n        await sleep(10000);\n\n        // 不再拉取\n        try {\n          console.info('----------------> origin msgCount %d, current msgCount %d', msgCount, pq.msgCount);\n          assert(msgCount === 1 || pq.msgCount === msgCount);\n          consumer.emit('over');\n        } catch (err) {\n          consumer.emit('error', err);\n        }\n      });\n\n      await Promise.race([\n        consumer.await('over'),\n        consumer.await('error'),\n      ]);\n    });\n  });\n\n  describe('delay consume message', () => {\n    let producer;\n    let consumer;\n    let consumeTime = 0;\n    // 允许的误差时间\n    const deviationTime = 4000;\n\n    before(async () => {\n      producer = new Producer(Object.assign({\n        httpclient,\n      }, config));\n      await producer.ready();\n      consumer = new Consumer(Object.assign({\n        httpclient,\n      }, config));\n      await consumer.ready();\n    });\n\n    after(async () => {\n      await producer.close();\n      await consumer.close();\n    });\n\n    it.skip('should receive message with specified time', async () => {\n      const delayTime = 10000;\n\n      const body = 'hello delay message at ' + Date.now();\n      const msg = new Message(config.topic, 'TagDelay', body);\n      const produceTime = Date.now();\n      msg.setStartDeliverTime(produceTime + delayTime);\n      const result = await producer.send(msg);\n      console.log(result);\n\n      consumer.subscribe(config.topic, 'TagDelay', async msg => {\n        console.log('message receive ------------> ', msg.msgId, msg.body.toString());\n        if (body === msg.body.toString()) {\n          consumeTime = Date.now();\n          consumer.emit('consumed');\n        }\n      });\n\n      await consumer.await('consumed');\n      assert(consumeTime - produceTime <= delayTime + deviationTime && consumeTime - produceTime >= delayTime - deviationTime);\n    });\n  });\n});\n"
  },
  {
    "path": "test/index_namesrv.test.js",
    "content": "'use strict';\n\nconst mm = require('mm');\nconst path = require('path');\nconst osenv = require('osenv');\nconst assert = require('assert');\nconst httpclient = require('urllib');\nconst utils = require('../lib/utils');\nconst Message = require('../').Message;\nconst Consumer = require('../').Consumer;\nconst Producer = require('../').Producer;\nconst sleep = require('mz-modules/sleep');\nconst rimraf = require('mz-modules/rimraf');\nconst rawConfig = require('../example/config');\nconst config = Object.assign({\n  nameSrv: '112.124.141.191:80',\n}, rawConfig);\nconst MixAll = require('../lib/mix_all');\n\nconst TOPIC = config.topic;\n\nconst localOffsetStoreDir = path.join(osenv.home(), '.rocketmq_offsets_node');\n\ndescribe('test/index_namesrv.test.js', () => {\n  describe('API', () => {\n    let producer;\n    let consumer;\n    before(async () => {\n      producer = new Producer(Object.assign({\n        httpclient,\n        retryAnotherBrokerWhenNotStoreOK: true,\n        compressMsgBodyOverHowmuch: 10,\n      }, config));\n      await producer.ready();\n      consumer = new Consumer(Object.assign({\n        httpclient,\n        rebalanceInterval: 1000,\n        isBroadcast: true,\n      }, config));\n      await consumer.ready();\n\n      consumer.subscribe(TOPIC, async msg => {\n        console.log('message receive ------------> ', msg.body.toString());\n      });\n    });\n\n    after(async () => {\n      await producer.close();\n      await consumer.close();\n    });\n\n    afterEach(mm.restore);\n\n    // it.skip('it should create topic ok', function*() {\n    //   yield producer.createTopic('TBW102', 'XXX', 8);\n    // });\n\n    it('should select another broker if one failed', async () => {\n      mm(producer, 'sendKernelImpl', async () => {\n        mm.restore();\n        return {\n          sendStatus: 'FLUSH_DISK_TIMEOUT',\n        };\n      });\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello TagA !!! ' // body\n      );\n\n      let sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n\n      mm(producer, 'sendKernelImpl', async () => {\n        mm.restore();\n        const err = new Error('mock err');\n        err.name = 'MQClientException';\n        err.code = 205;\n        throw err;\n      });\n      sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n    });\n\n    it('should tryToFindTopicPublishInfo if brokerAddr not found', async () => {\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello TagA !!! ' // body\n      );\n\n      let sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n\n      mm(producer._mqClient, 'findBrokerAddressInPublish', () => {\n        mm.restore();\n        return null;\n      });\n      sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n\n      mm(producer._mqClient, 'findBrokerAddressInPublish', () => {\n        return null;\n      });\n\n      let isError = false;\n      try {\n        await producer.send(msg);\n      } catch (err) {\n        isError = true;\n        assert(/The broker\\[.+\\] not exist/i.test(err.message));\n        assert(err.name === 'MQClientException');\n      }\n      assert(isError);\n      mm.restore();\n\n      isError = false;\n      try {\n        await producer.send(new Message('%RETRY%' + TOPIC, // topic\n          'TagA', // tag\n          'Hello TagA !!! ' // body\n        ));\n      } catch (err) {\n        isError = true;\n        assert(err.name === 'MQClientException');\n      }\n      assert(isError);\n    });\n\n    it('should emit error if compress failed', done => {\n      mm(utils, 'compress', function() {\n        throw new Error('mock error');\n      });\n\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello TagA !!!   sds' // body\n      );\n      producer.on('error', err => {\n        assert(err.name === 'MetaQCompressError');\n        done();\n      });\n      (async () => {\n        try {\n          const sendResult = await producer.send(msg);\n          assert(sendResult && sendResult.msgId);\n          console.log('over');\n        } catch (err) {\n          console.log(err);\n        }\n      })();\n    });\n\n    it('should updateProcessQueueTableInRebalance ok', async () => {\n      await sleep(3000);\n      await consumer.rebalanceByTopic(TOPIC);\n      const size = consumer.processQueueTable.size;\n      assert(size > 0);\n\n      const key = Array.from(consumer.processQueueTable.keys())[0];\n      const obj = consumer.processQueueTable.get(key);\n      const processQueue = obj.processQueue;\n      processQueue.lastPullTimestamp = 10000;\n\n      await consumer.rebalanceByTopic(TOPIC);\n      assert(consumer.processQueueTable.size === size);\n\n      await consumer.updateProcessQueueTableInRebalance(TOPIC, []);\n      assert(consumer.processQueueTable.size === 0);\n      await consumer.updateProcessQueueTableInRebalance(MixAll.getRetryTopic(consumer.consumerGroup), []);\n      assert(consumer.processQueueTable.size === 0);\n    });\n\n    it('should computePullFromWhere ok', async () => {\n      mm(consumer._offsetStore, 'readOffset', async () => {\n        return -1;\n      });\n      mm(consumer, 'consumeFromWhere', 'CONSUME_FROM_LAST_OFFSET');\n\n      let offset = await consumer.computePullFromWhere({\n        topic: '%RETRY%__',\n      });\n      assert(offset === 0);\n\n      mm(consumer, 'consumeFromWhere', 'CONSUME_FROM_TIMESTAMP');\n      mm(consumer._mqClient, 'maxOffset', async () => {\n        return 1000;\n      });\n      offset = await consumer.computePullFromWhere({\n        topic: '%RETRY%__',\n      });\n      assert(offset === 1000);\n\n      mm.error(consumer._mqClient, 'maxOffset');\n      offset = await consumer.computePullFromWhere({\n        topic: '%RETRY%__',\n      });\n      assert(offset === -1);\n\n      mm(consumer._offsetStore, 'readOffset', async () => {\n        return 100;\n      });\n      offset = await consumer.computePullFromWhere({\n        topic: 'TP',\n      });\n      assert(offset === 100);\n\n      mm(consumer, 'consumeFromWhere', 'CONSUME_FROM_FIRST_OFFSET');\n      offset = await consumer.computePullFromWhere({\n        topic: 'TP',\n      });\n      assert(offset === 100);\n\n      mm(consumer, 'consumeFromWhere', 'NOT_EXISTS');\n      offset = await consumer.computePullFromWhere({\n        topic: 'TP',\n      });\n      assert(offset === -1);\n    });\n  });\n\n  // 广播消费\n  describe('broadcast', () => {\n    [\n      'CONSUME_FROM_LAST_OFFSET_AND_FROM_MIN_WHEN_BOOT_FIRST',\n      'CONSUME_FROM_MIN_OFFSET',\n      'CONSUME_FROM_MAX_OFFSET',\n      'CONSUME_FROM_LAST_OFFSET',\n      'CONSUME_FROM_FIRST_OFFSET',\n      'CONSUME_FROM_TIMESTAMP',\n    ].forEach(consumeFromWhere => {\n      let consumer;\n      let producer;\n      describe(`consumeFromWhere => ${consumeFromWhere}`, () => {\n        before(() => {\n          return rimraf(localOffsetStoreDir);\n        });\n        before(async () => {\n          consumer = new Consumer(Object.assign({\n            httpclient,\n            consumeFromWhere,\n            isBroadcast: true,\n            persistent: true,\n            rebalanceInterval: 2000,\n          }, config));\n          producer = new Producer(Object.assign({\n            httpclient,\n          }, config));\n\n          await consumer.ready();\n          await producer.ready();\n        });\n\n        after(async () => {\n          await producer.close();\n          const originFn = consumer._offsetStore.persistAll;\n          mm(consumer._offsetStore, 'persistAll', async mqs => {\n            assert(mqs && mqs.length);\n            return await originFn.call(consumer._offsetStore, mqs);\n          });\n          await consumer.close();\n        });\n\n        afterEach(mm.restore);\n\n        it('should subscribe message ok', async () => {\n          let msgId;\n          const received = new Set();\n          consumer.subscribe(TOPIC, 'TagA', async msg => {\n            console.log('message receive ------------> ', msg.msgId, msg.body.toString());\n            assert(msg.tags !== 'TagB');\n            received.add(msg.msgId);\n            if (msgId && received.has(msgId)) {\n              assert(msg.body.toString() === 'Hello MetaQ !!! ');\n              consumer.emit('TagA');\n            }\n          });\n\n          await sleep(5000);\n\n          let msg = new Message(TOPIC, // topic\n            'TagB', // tag\n            'Hello MetaQ !!! ' // body\n          );\n          let sendResult = await producer.send(msg);\n          assert(sendResult && sendResult.msgId);\n          msg = new Message(TOPIC, // topic\n            'TagA', // tag\n            'Hello MetaQ !!! ' // body\n          );\n          sendResult = await producer.send(msg);\n          assert(sendResult && sendResult.msgId);\n          msgId = sendResult.msgId;\n          console.log('send message success,', sendResult.msgId);\n\n          if (!received.has(msgId)) {\n            await consumer.await('TagA');\n          }\n        });\n\n        it.skip('should viewMessage ok', async () => {\n          const msg = new Message(TOPIC, // topic\n            'TagA', // tag\n            'Hello MetaQ !!! ' // body\n          );\n          const sendResult = await producer.send(msg);\n          assert(sendResult && sendResult.msgId);\n\n          await sleep(3000);\n\n          const message = await consumer.viewMessage(sendResult.msgId);\n          assert(message.body.toString() === 'Hello MetaQ !!! ');\n        });\n      });\n    });\n  });\n\n  // 集群消费\n  describe('cluster', () => {\n    let consumer;\n    let producer;\n    before(async () => {\n      consumer = new Consumer(Object.assign({\n        httpclient,\n        isBroadcast: false,\n      }, config));\n      await consumer.ready();\n      producer = new Producer(Object.assign({\n        httpclient,\n      }, config));\n      await producer.ready();\n    });\n\n    after(async () => {\n      await consumer.close();\n      await producer.close();\n    });\n\n    afterEach(mm.restore);\n\n    it('should subscribe message ok', async () => {\n      await sleep(3000);\n\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello MetaQ !!! ' // body\n      );\n      const sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n\n      const msgId = sendResult.msgId;\n      console.log(sendResult);\n\n      await new Promise(r => {\n        consumer.subscribe(TOPIC, '*', async msg => {\n          if (msg.msgId === msgId) {\n            assert(msg.body.toString() === 'Hello MetaQ !!! ');\n            r();\n          }\n        });\n      });\n    });\n  });\n\n  describe('process exception', () => {\n    let consumer;\n    let producer;\n    beforeEach(async () => {\n      consumer = new Consumer(Object.assign({\n        httpclient,\n        rebalanceInterval: 2000,\n      }, config));\n      producer = new Producer(Object.assign({\n        httpclient,\n      }, config));\n      await consumer.ready();\n      await producer.ready();\n    });\n\n    afterEach(async () => {\n      await producer.close();\n      await consumer.close();\n    });\n\n    it('should not correctTags if process queue not empty', () => {\n      let done = false;\n      mm(consumer._offsetStore, 'updateOffset', () => {\n        done = true;\n      });\n      consumer.correctTagsOffset({\n        processQueue: {\n          msgCount: 0,\n        },\n      });\n      assert(done);\n      done = true;\n      mm.restore();\n      mm(consumer._offsetStore, 'updateOffset', () => {\n        done = false;\n      });\n      consumer.correctTagsOffset({\n        processQueue: {\n          msgCount: 1,\n        },\n      });\n      assert(done);\n      mm.restore();\n    });\n\n    it('should retry(consume later) if process failed', async () => {\n      let msgId;\n      consumer.subscribe(TOPIC, '*', async msg => {\n        console.warn('message receive ------------> ', msg.body.toString());\n        if (msg.msgId === msgId || msg.originMessageId === msgId) {\n          assert(msg.body.toString() === 'Hello MetaQ !!! ');\n          if (msg.reconsumeTimes === 0) {\n            throw new Error('mock error');\n          }\n          consumer.emit('*');\n        }\n      });\n\n      await sleep(5000);\n\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello MetaQ !!! ' // body\n      );\n      const sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n      msgId = sendResult.msgId;\n      await consumer.await('*');\n    });\n\n    it('should retry(retry message) if process failed', async () => {\n      let msgId;\n      mm(consumer._mqClient, 'consumerSendMessageBack', async () => {\n        throw new Error('mock error');\n      });\n      consumer.subscribe(TOPIC, '*', async msg => {\n        console.warn('message receive ------------> ', msg.body.toString());\n        if (msg.msgId === msgId || msg.originMessageId === msgId) {\n          assert(msg.body.toString() === 'Hello MetaQ !!! ');\n          if (msg.reconsumeTimes === 0) {\n            throw new Error('mock error');\n          }\n          consumer.emit('*');\n        }\n      });\n\n      await sleep(5000);\n\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello MetaQ !!! ' // body\n      );\n      const sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n      msgId = sendResult.msgId;\n      await consumer.await('*');\n    });\n\n    it('should retry(fall to local) if process failed', async () => {\n      let msgId;\n      mm(consumer, 'sendMessageBack', async () => {\n        throw new Error('mock error');\n      });\n      consumer.subscribe(TOPIC, '*', async msg => {\n        console.warn('message receive ------------> ', msg.body.toString());\n        if (msg.msgId === msgId) {\n          assert(msg.body.toString() === 'Hello MetaQ !!! ');\n          if (msg.reconsumeTimes === 0) {\n            throw new Error('mock error');\n          }\n          consumer.emit('*');\n        }\n      });\n\n      await sleep(5000);\n\n      const msg = new Message(TOPIC, // topic\n        'TagA', // tag\n        'Hello MetaQ !!! ' // body\n      );\n      const sendResult = await producer.send(msg);\n      assert(sendResult && sendResult.msgId);\n      msgId = sendResult.msgId;\n      await consumer.await('*');\n    });\n  });\n\n  describe('flow control', () => {\n    let consumer;\n    let producer;\n    before(async () => {\n      await rimraf(localOffsetStoreDir);\n      consumer = new Consumer(Object.assign({\n        httpclient,\n        isBroadcast: true,\n        rebalanceInterval: 2000,\n        pullThresholdForQueue: 1,\n        consumeFromWhere: 'CONSUME_FROM_FIRST_OFFSET',\n        pullTimeDelayMillsWhenFlowControl: 5000,\n      }, config));\n      producer = new Producer(Object.assign({\n        httpclient,\n      }, config));\n      await consumer.ready();\n      await producer.ready();\n    });\n\n    after(async () => {\n      await producer.close();\n      await consumer.close();\n    });\n\n    it('should retry if process failed', async () => {\n      let count = 20;\n      while (count--) {\n        const msg = new Message(TOPIC, // topic\n          'TagA', // tag\n          'Hello MetaQ !!! ' // body\n        );\n        const sendResult = await producer.send(msg);\n        assert(sendResult && sendResult.msgId);\n      }\n\n      consumer.subscribe(TOPIC, '*', async (msg, mq, pq) => {\n        console.log('message receive ------------> ', msg.body.toString());\n\n        const msgCount = pq.msgCount;\n        await sleep(10000);\n\n        // 不再拉取\n        try {\n          console.info('----------------> origin msgCount %d, current msgCount %d', msgCount, pq.msgCount);\n          assert(msgCount === 1 || pq.msgCount === msgCount);\n          consumer.emit('over');\n        } catch (err) {\n          consumer.emit('error', err);\n        }\n      });\n\n      await Promise.race([\n        consumer.await('over'),\n        consumer.await('error'),\n      ]);\n    });\n  });\n});\n"
  },
  {
    "path": "test/message/message_decoder.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst ByteBuffer = require('byte');\nconst utils = require('../utils');\nconst Message = require('../../lib/message/message');\nconst MessageDecoder = require('../../lib/message/message_decoder');\n\nconst NAME_VALUE_SEPARATOR = String.fromCharCode(1);\nconst PROPERTY_SEPARATOR = String.fromCharCode(2);\nconst SYSTEM_PROP_KEY_STARTDELIVERTIME = '__STARTDELIVERTIME';\n\ndescribe('test/message/message_decoder.test.js', function() {\n  it('should decode message ok', function() {\n    const buf = ByteBuffer.wrap(utils.bytes('message.bin'));\n    const message = MessageDecoder.decode(buf);\n    assert(message);\n    assert(message.msgId === '0ADA91A6000029CC0000006500E59F3E');\n    assert.deepEqual(message.body, Buffer.from('{\"room\":\"1\",\"msg\":\"1\"}'));\n    assert(message.tags === 'TagA');\n    assert(!message.keys);\n    assert(message.delayTimeLevel === 0);\n    assert(!message.waitStoreMsgOK);\n    assert(!message.buyerId);\n    assert(message.topic === 'TopicTest');\n  });\n\n  it('should decode compress message ok', function() {\n    const buf = ByteBuffer.wrap(utils.bytes('message_compress.bin'));\n    const message = MessageDecoder.decode(buf);\n    assert(message);\n    assert(message.msgId === '0ADA91A6000029CC000000661A586D98');\n    assert.deepEqual(message.body, Buffer.from('Hello MetaQ !!!'));\n    assert(message.tags, 'TagA');\n    assert(!message.keys);\n    assert(message.delayTimeLevel === 0);\n    assert(!message.waitStoreMsgOK);\n    assert(!message.buyerId);\n    assert(message.topic === 'TopicTest');\n  });\n\n  it('should decode message and not read body ok', function() {\n    const buf = ByteBuffer.wrap(utils.bytes('message.bin'));\n    const message = MessageDecoder.decode(buf, false);\n    assert(message);\n    assert(message.msgId === '0ADA91A6000029CC0000006500E59F3E');\n    assert(!message.body);\n    assert(message.tags === 'TagA');\n    assert(!message.keys);\n    assert(message.delayTimeLevel === 0);\n    assert(!message.waitStoreMsgOK);\n    assert(!message.buyerId);\n    assert(message.topic === 'TopicTest');\n  });\n\n  it('should batch decode message ok', function() {\n    const buf = ByteBuffer.wrap(utils.bytes('batch_message.bin'));\n    const messages = MessageDecoder.decodes(buf);\n    assert(messages.length === 3);\n    assert(messages[0].msgId === '0ADA91A6000029CC000000659560A2AA');\n    assert.deepEqual(messages[0].body, Buffer.from('Hello MetaQ !!!'));\n    assert(messages[1].msgId === '0ADA91A6000029CC000000659560D349');\n    assert(messages[2].msgId === '0ADA91A6000029CC000000659560D578');\n  });\n\n  it('should decodeMessageId ok', function() {\n    const data = MessageDecoder.decodeMessageId('0ADA91A6000029CC0000006504009B6F');\n    assert(data.address === '10.218.145.166:10700');\n    assert(data.offset.toString() === '433858845551');\n  });\n\n  it('should messageProperties2String ok', function() {\n    assert(MessageDecoder.messageProperties2String({\n      foo: 'bar',\n      xxx: 'yyy',\n      a: '中文',\n    }) === `foo${NAME_VALUE_SEPARATOR}bar${PROPERTY_SEPARATOR}xxx${NAME_VALUE_SEPARATOR}yyy${PROPERTY_SEPARATOR}a${NAME_VALUE_SEPARATOR}中文${PROPERTY_SEPARATOR}`);\n  });\n\n  it('should create message ok', function() {\n    const message = new Message('fake_topic', 'fake body');\n    message.tags = 'TagB';\n    assert(message.properties.TAGS === 'TagB');\n    message.keys = [ 'xxx', 'yyy' ];\n    assert(message.properties.KEYS === 'xxx yyy');\n    message.delayTimeLevel = 1000;\n    assert(message.delayTimeLevel === 1000);\n    assert(message.properties.DELAY === '1000');\n    message.waitStoreMsgOK = true;\n    assert(message.properties.WAIT === 'true');\n    message.buyerId = '123';\n    assert(message.properties.BUYER_ID === '123');\n  });\n\n  it('should create deliver time message ok', function() {\n    const deliverTime = Date.now() + 3000;\n    const message = new Message('fake_topic', 'fake body');\n    message.setStartDeliverTime(deliverTime);\n    assert(message.getStartDeliverTime() === deliverTime);\n    assert(message.properties[SYSTEM_PROP_KEY_STARTDELIVERTIME] === deliverTime);\n  });\n});\n"
  },
  {
    "path": "test/mq_client.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst httpclient = require('urllib');\nconst ClientConfig = require('../lib/client_config');\nconst MQClient = require('../lib/mq_client');\nconst { consumerGroup, producerGroup, topic } = require('../example/config');\n\ndescribe('test/mq_client.test.js', () => {\n  const config = new ClientConfig(Object.assign({ httpclient }, require('../example/config')));\n\n  let client;\n  before(async () => {\n    client = MQClient.getAndCreateMQClient(config);\n    client.unregisterProducer('CLIENT_INNER_PRODUCER');\n    return client.ready();\n  });\n\n  after(() => client.close());\n\n  it('should mqclient is singleton', () => {\n    assert(MQClient.getAndCreateMQClient(config) === client);\n  });\n\n  it('should registerConsumer ok', async () => {\n    client.registerConsumer(consumerGroup, {\n      subscriptions: new Map(),\n      updateTopicSubscribeInfo() {},\n      isSubscribeTopicNeedUpdate() {},\n      doRebalance() {},\n    });\n    assert(client._consumerTable.size === 1);\n    client.registerConsumer(consumerGroup, {});\n    assert(client._consumerTable.size === 1);\n    await client.unregisterConsumer(consumerGroup);\n    assert(client._consumerTable.size === 0);\n  });\n\n  it('should registerProducer ok', async () => {\n    client.registerProducer(producerGroup, {\n      publishTopicList: new Map(),\n      updateTopicPublishInfo() {},\n      isPublishTopicNeedUpdate() {},\n    });\n    assert(client._producerTable.size === 1);\n    client.registerProducer(producerGroup, {});\n    assert(client._producerTable.size === 1);\n    await client.unregisterProducer(producerGroup);\n    assert(client._producerTable.size === 0);\n  });\n\n  it('should not close if there are producer or consumer', async () => {\n    client.registerConsumer(consumerGroup, {\n      subscriptions: new Map(),\n      updateTopicSubscribeInfo() {},\n      isSubscribeTopicNeedUpdate() {},\n      doRebalance() {},\n    });\n    await client.close();\n    await client.unregisterConsumer(consumerGroup);\n\n    client.registerProducer(producerGroup, {\n      publishTopicList: new Map(),\n      updateTopicPublishInfo() {},\n      isPublishTopicNeedUpdate() {},\n    });\n    await client.close();\n    await client.unregisterProducer(producerGroup);\n  });\n\n  it('should updateAllTopicRouterInfo ok', async () => {\n    const subscriptions = new Map();\n    subscriptions.set(topic, {\n      topic,\n      subString: '*',\n      classFilterMode: false,\n      tagsSet: [],\n      codeSet: [],\n      subVersion: Date.now(),\n    });\n    client.registerConsumer(consumerGroup, {\n      subscriptions,\n      updateTopicSubscribeInfo() {},\n      isSubscribeTopicNeedUpdate() {},\n      doRebalance() {},\n    });\n    client.registerConsumer('xxx', null);\n    client.registerProducer(producerGroup, {\n      publishTopicList: [ topic ],\n      updateTopicPublishInfo() {},\n      isPublishTopicNeedUpdate() {},\n    });\n    client.registerProducer('xxx', null);\n\n    await client.updateAllTopicRouterInfo();\n\n    await client.unregisterConsumer(consumerGroup);\n    await client.unregisterProducer(producerGroup);\n    try {\n      await client.unregisterConsumer('xxx');\n    } catch (err) {\n      assert(err.message.includes('xxx not created'));\n    }\n    await client.unregisterProducer('xxx');\n  });\n\n  it('should updateTopicRouteInfoFromNameServer ok', async () => {\n    client.registerConsumer(config.consumerGroup, {\n      subscriptions: new Map(),\n      updateTopicSubscribeInfo() {},\n      isSubscribeTopicNeedUpdate() {},\n      doRebalance() {},\n    });\n    client.registerProducer(config.producerGroup, {\n      publishTopicList: [ topic ],\n      updateTopicPublishInfo() {},\n      isPublishTopicNeedUpdate() {},\n    });\n    await client.updateTopicRouteInfoFromNameServer(topic);\n    let topicRouteData = client._topicRouteTable.get(topic);\n    assert(topicRouteData);\n    assert(topicRouteData.brokerDatas.length > 0);\n    assert(client._brokerAddrTable.size > 0);\n\n    await client.unregisterConsumer(config.consumerGroup);\n    await client.unregisterProducer(config.producerGroup);\n\n    await client.updateTopicRouteInfoFromNameServer(topic, true, {\n      createTopicKey: topic,\n      defaultTopicQueueNums: 8,\n    });\n\n    topicRouteData = client._topicRouteTable.get(topic);\n    assert(topicRouteData);\n    assert(topicRouteData.brokerDatas.length > 0);\n    assert(client._brokerAddrTable.size > 0);\n  });\n\n  it('should topicRouteData2TopicPublishInfo ok', function() {\n    let info = client._topicRouteData2TopicPublishInfo(topic, {\n      orderTopicConf: 'xxx:8;yyy:8',\n    });\n    assert(info.orderTopic);\n    assert(info.messageQueueList.length === 16);\n\n    info = client._topicRouteData2TopicPublishInfo(topic, {\n      brokerDatas: [{\n        brokerAddrs: {\n          0: '10.10.10.10:1000',\n        },\n        brokerName: 'xxx',\n      }],\n      filterServerTable: {},\n      queueDatas: [{\n        brokerName: 'xxx',\n        perm: 6,\n        readQueueNums: 8,\n        topicSynFlag: 0,\n        writeQueueNums: 8,\n      }],\n    });\n    assert(!info.orderTopic);\n    assert(info.messageQueueList.length === 8);\n\n    info = client._topicRouteData2TopicPublishInfo(topic, {\n      brokerDatas: [{\n        brokerAddrs: {\n          1: '10.10.10.10:1000',\n        },\n        brokerName: 'yyy',\n      }],\n      filterServerTable: {},\n      queueDatas: [{\n        brokerName: 'yyy',\n        perm: 6,\n        readQueueNums: 8,\n        topicSynFlag: 0,\n        writeQueueNums: 8,\n      }],\n    });\n    assert(!info.orderTopic);\n    assert(info.messageQueueList.length === 0);\n  });\n\n  it('should cleanOfflineBroker ok', function() {\n    client._brokerAddrTable.set('fake-broker', {\n      0: '127.0.0.1:10910',\n    });\n    client._cleanOfflineBroker();\n    assert(!client._brokerAddrTable.has('fake-broker'));\n  });\n\n  it('should sendHeartbeatToAllBroker ok', async () => {\n    await client.sendHeartbeatToAllBroker();\n    const subscriptions = new Map();\n    subscriptions.set(config.consumerGroup, {\n      topic,\n      subString: '*',\n      classFilterMode: false,\n      tagsSet: [],\n      codeSet: [],\n      subVersion: Date.now(),\n    });\n    client.registerConsumer(config.consumerGroup, {\n      subscriptions,\n      updateTopicSubscribeInfo() {},\n      isSubscribeTopicNeedUpdate() {},\n      doRebalance() {},\n    });\n    client.registerProducer(config.producerGroup, {\n      publishTopicList: [ topic ],\n      updateTopicPublishInfo() {},\n      isPublishTopicNeedUpdate() {},\n    });\n    await client.updateTopicRouteInfoFromNameServer(topic);\n\n    const topicRouteData = client._topicRouteTable.get(topic);\n    assert(topicRouteData);\n    assert(topicRouteData.brokerDatas.length > 0);\n    assert(client._brokerAddrTable.size > 0);\n\n    await client.sendHeartbeatToAllBroker();\n    await client.unregisterConsumer(config.consumerGroup);\n    await client.unregisterProducer(config.producerGroup);\n  });\n\n  it('should persistAllConsumerOffset & doRebalance ok', async () => {\n    client.registerConsumer(config.consumerGroup, {\n      //\n      async persistConsumerOffset() { console.log('persistConsumerOffset'); },\n      async doRebalance() { console.log('doRebalance'); },\n    });\n    await client.persistAllConsumerOffset();\n    await client.doRebalance();\n    // await this.client.notifyConsumerIdsChanged();\n    await client.unregisterConsumer(config.consumerGroup);\n  });\n\n  // it('should pull message ok', function(done) {\n  //   done = pedding(done, 2);\n  //   const request = {\n  //     consumerGroup: 'please_rename_unique_group_name_1',\n  //   };\n  //   let i = 0;\n  //   this.client.registerConsumer('please_rename_unique_group_name_1', {\n  //     pullMessage: function(pullRequest) {\n  //       pullRequest.should.eql(request);\n  //       if (++i === 2) {\n  //         this.client.unregisterConsumer('please_rename_unique_group_name_1');\n  //       }\n  //       done();\n  //     }.bind(this),\n  //   });\n  //   this.client.executePullRequestImmediately(request);\n  //   this.client.executePullRequestLater(request, 100);\n  // });\n});\n"
  },
  {
    "path": "test/mq_client_api.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst address = require('address');\nconst httpclient = require('urllib');\nconst config = require('../example/config');\nconst MQClientAPI = require('../lib/mq_client_api');\n\nconst TOPIC = config.topic;\n\ndescribe('test/mq_client_api.test.js', function() {\n  let client;\n  before(() => {\n    client = new MQClientAPI(Object.assign({ httpclient }, Object.assign({}, config, {\n      nameSrv: [ config.nameSrv ],\n    })));\n    return client.ready();\n  });\n\n  after(() => client.close());\n\n  it('should getProjectGroupByIp ok', () => {\n    return client.getProjectGroupByIp(address.ip(), 3000);\n  });\n\n  it('should getDefaultTopicRouteInfoFromNameServer ok', async () => {\n    const res = await client.getDefaultTopicRouteInfoFromNameServer(TOPIC, 3000);\n    assert(res);\n  });\n\n  it('should getDefaultTopicRouteInfoFromNameServer for exception', async () => {\n    let isError = false;\n    try {\n      await client.getDefaultTopicRouteInfoFromNameServer('NOT_EXIST_TOPIC', 3000);\n    } catch (err) {\n      isError = true;\n      assert(err.name === 'MQClientException');\n    }\n    assert(isError);\n  });\n\n  it('should getTopicRouteInfoFromNameServer ok', async () => {\n    const res = await client.getTopicRouteInfoFromNameServer(TOPIC, 3000);\n    assert(res);\n  });\n\n  it('should getTopicRouteInfoFromNameServer ok if old one is closed', async () => {\n    client._namesrvAddrList.unshift('1.2.3.4:80', '2.3.4.5:80');\n    client._namesrvAddrList.push('6.7.8.9:80');\n    const res = await client.getTopicRouteInfoFromNameServer(TOPIC, 3000);\n    assert(res);\n  });\n\n  it('should getTopicRouteInfoFromNameServer ok if old one is closed empty list', async () => {\n    client._namesrvAddrList = [];\n    let isError = false;\n    try {\n      await client.getTopicRouteInfoFromNameServer(TOPIC, 3000);\n    } catch (err) {\n      isError = true;\n      assert(err.name === 'MQClientException');\n    }\n    assert(isError);\n  });\n\n});\n"
  },
  {
    "path": "test/protocol/command/opaque_generator.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst OpaqueGenerator = require('../../../lib/protocol/command/opaque_generator');\n\ndescribe('test/protocol/command/opaque_generator.test.js', function() {\n\n  it('should get current & next opaque', function() {\n    OpaqueGenerator.resetOpaque();\n    assert(OpaqueGenerator.getNextOpaque() === 1);\n    assert(OpaqueGenerator.getCurrentOpaque() === 1);\n  });\n});\n"
  },
  {
    "path": "test/protocol/command/remoting_command.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst RequestCode = require('../../../lib/protocol/request_code');\nconst ResponseCode = require('../../../lib/protocol/response_code');\nconst RemotingCommand = require('../../../lib/protocol/command/remoting_command');\n\ndescribe('test/protocol/command/remoting_command.test.js', function() {\n\n  it('should create request command ok', function() {\n    const command = RemotingCommand.createRequestCommand(RequestCode.GET_KV_CONFIG_BY_VALUE, {\n      namespace: 'PROJECT_CONFIG',\n      key: '192.168.1.103',\n    });\n    assert(command.type === 'REQUEST_COMMAND');\n    assert(!command.isResponseType);\n    assert(!command.isOnewayRPC);\n    command.markOnewayRPC();\n    assert(command.isOnewayRPC);\n\n    command.makeCustomHeaderToNet();\n    assert.deepEqual(command.decodeCommandCustomHeader(), {\n      namespace: 'PROJECT_CONFIG',\n      key: '192.168.1.103',\n    });\n  });\n\n  it('should create response command ok', function() {\n    const command = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, 1);\n    assert(command.type === 'RESPONSE_COMMAND');\n    assert(command.isResponseType);\n    assert(!command.isOnewayRPC);\n    assert(command.remark === 'not set any response code');\n  });\n\n  it('should encode command ok', function() {\n    const request = RemotingCommand.createRequestCommand(RequestCode.GET_KV_CONFIG_BY_VALUE, {\n      namespace: 'PROJECT_CONFIG',\n      key: '192.168.1.103',\n    });\n    request.opaque = 1;\n    assert.deepEqual(request.encode(), Buffer.from('00000085000000817b22636f6465223a3231372c226c616e6775616765223a224a415641222c226f7061717565223a312c22666c6167223a302c2276657273696f6e223a3132312c226578744669656c6473223a7b226e616d657370616365223a2250524f4a4543545f434f4e464947222c226b6579223a223139322e3136382e312e313033227d7d', 'hex'));\n    const response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, 1);\n    assert.deepEqual(response.encode(), Buffer.from('00000076000000727b22636f6465223a302c226c616e6775616765223a224a415641222c226f7061717565223a312c22666c6167223a312c2276657273696f6e223a3132312c2272656d61726b223a226e6f742073657420616e7920726573706f6e736520636f6465222c226578744669656c6473223a7b7d7d', 'hex'));\n  });\n\n  it('should decode command ok', function() {\n    const request = RemotingCommand.decode(Buffer.from('00000084000000807b22636f6465223a3231372c226c616e6775616765223a224a415641222c226f7061717565223a312c22666c6167223a302c2276657273696f6e223a37382c226578744669656c6473223a7b226e616d657370616365223a2250524f4a4543545f434f4e464947222c226b6579223a223139322e3136382e312e313033227d7d', 'hex'));\n    assert(request.opaque === 1);\n    assert(request.code === RequestCode.GET_KV_CONFIG_BY_VALUE);\n    assert.deepEqual(request.extFields, {\n      namespace: 'PROJECT_CONFIG',\n      key: '192.168.1.103',\n    });\n    const response = RemotingCommand.decode(Buffer.from('00000066000000627b22636f6465223a302c226c616e6775616765223a224a415641222c226f7061717565223a312c22666c6167223a312c2276657273696f6e223a37382c2272656d61726b223a226e6f742073657420616e7920726573706f6e736520636f6465227d', 'hex'));\n    assert(response.opaque === 1);\n    assert(response.code === ResponseCode.SUCCESS);\n    assert(response.remark === 'not set any response code');\n  });\n});\n"
  },
  {
    "path": "test/remoting_client.test.js",
    "content": "'use strict';\n\nconst mm = require('mm');\nconst assert = require('assert');\nconst httpclient = require('urllib');\nconst config = require('../example/config');\nconst RemotingClient = require('../lib/remoting_client');\nconst RequestCode = require('../lib/protocol/request_code');\nconst ResponseCode = require('../lib/protocol/response_code');\nconst RemotingCommand = require('../lib/protocol/command/remoting_command');\n\ndescribe('test/remoting_client.test.js', function() {\n  let client;\n  before(() => {\n    client = new RemotingClient(Object.assign({ httpclient }, config));\n    return client.ready();\n  });\n\n  afterEach(mm.restore);\n\n  after(() => client.close());\n\n  it('should create & ready ok', async () => {\n    const client = new RemotingClient(Object.assign({ httpclient }, config));\n    await client.ready();\n    assert(client._namesrvAddrList.length > 0);\n    await client.close();\n  });\n\n  it('should invoke ok', async () => {\n    await client.ready();\n    const res = await client.invoke(null, new RemotingCommand({\n      code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n      customHeader: {\n        topic: 'NOT_EXISTS',\n      },\n    }), 5000);\n    assert(res);\n    assert(res.code === ResponseCode.TOPIC_NOT_EXIST);\n  });\n\n  it('should invoke for namesrv with retry', async () => {\n    let retryCount = 0;\n    mm(client, '_namesrvAddrList', [ 0, 0 ]);\n    mm(client, 'invoke', async () => {\n      retryCount++;\n      mm.restore();\n      throw new Error('invoke fail');\n    });\n    const res = await client.invokeForNameSrvAtLeastOnce(new RemotingCommand({\n      code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n      customHeader: {\n        topic: 'NOT_EXISTS',\n      },\n    }), 5000);\n    assert(retryCount === 1);\n    assert(res);\n    assert(res.code === ResponseCode.TOPIC_NOT_EXIST);\n  });\n\n  it('should invoke oneway ok', async () => {\n    await client.invokeOneway(null, new RemotingCommand({\n      code: RequestCode.GET_KV_CONFIG_BY_VALUE,\n      customHeader: {\n        namespace: 'PROJECT_CONFIG',\n        key: '127.0.0.1',\n      },\n    }));\n  });\n\n  it('should close ok', async () => {\n    const client = new RemotingClient(Object.assign({ httpclient }, config));\n    await client.ready();\n    const res = await client.invoke(null, new RemotingCommand({\n      code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n      customHeader: {\n        topic: 'NOT_EXISTS',\n      },\n    }), 5000);\n\n    assert(res);\n    assert(res.code === ResponseCode.TOPIC_NOT_EXIST);\n\n    await client.close();\n    await client.close();\n  });\n\n  it('should invoke ok after close', async () => {\n    const client = new RemotingClient(Object.assign({ httpclient }, config));\n    await client.ready();\n    const res = await client.invoke(null, new RemotingCommand({\n      code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n      customHeader: {\n        topic: 'NOT_EXISTS',\n      },\n    }), 5000);\n    assert(res);\n    assert(res.code === ResponseCode.TOPIC_NOT_EXIST);\n\n    await client.close();\n\n    await client.invokeOneway(null, new RemotingCommand({\n      code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n      customHeader: {\n        topic: 'NOT_EXISTS',\n      },\n    }));\n    await client.invoke(null, new RemotingCommand({\n      code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n      customHeader: {\n        topic: 'NOT_EXISTS',\n      },\n    }), 5000);\n  });\n\n  it('should call multi times ok', async () => {\n    await client.invoke(null, new RemotingCommand({\n      code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n      customHeader: {\n        topic: 'NOT_EXISTS',\n      },\n    }), 5000);\n    await client.invoke(null, new RemotingCommand({\n      code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n      customHeader: {\n        topic: 'NOT_EXISTS',\n      },\n    }), 5000);\n    await client.invoke(null, new RemotingCommand({\n      code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n      customHeader: {\n        topic: 'NOT_EXISTS',\n      },\n    }), 5000);\n    await client.invokeOneway(null, new RemotingCommand({\n      code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n      customHeader: {\n        topic: 'NOT_EXISTS',\n      },\n    }));\n    await client.invokeOneway(null, new RemotingCommand({\n      code: RequestCode.GET_ROUTEINTO_BY_TOPIC,\n      customHeader: {\n        topic: 'NOT_EXISTS',\n      },\n    }));\n  });\n\n  // it('should not create two channel with same address', function() {\n  //   const address = client._namesrvAddrList[0];\n  //   assert(client.getAndCreateChannel(address).clientId === client.getAndCreateChannel(address).clientId);\n  // });\n\n  it('should create a new channel if old one is closed', function() {\n    const address = client._namesrvAddrList[0];\n    const channel = client.getAndCreateChannel(address);\n    channel.close();\n    assert(client.getAndCreateChannel(address).clientId !== channel.clientId);\n  });\n\n  it('should support unitName', async () => {\n    mm(httpclient, 'request', async url => {\n      assert(url.includes('-xxx?nofix=1'));\n      return {\n        status: 200,\n        data: '127.0.0.1:9876;127.0.0.2:9876',\n      };\n    });\n    const client = new RemotingClient(Object.assign({ httpclient, unitName: 'xxx' }, config));\n    await client.ready();\n\n    assert(client._namesrvAddrList, [\n      '127.0.0.1:9876',\n      '127.0.0.2:9876',\n    ]);\n  });\n});\n"
  },
  {
    "path": "test/store/local_file.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst httpclient = require('urllib');\nconst MQClient = require('../../lib/mq_client');\nconst ClientConfig = require('../../lib/client_config');\nconst MessageQueue = require('../../lib/message_queue');\nconst LocalFileOffsetStore = require('../../lib/store/local_file');\n\ndescribe('test/store/local_file.test.js', function() {\n\n  before(async () => {\n    const client = new MQClient(new ClientConfig({\n      instanceName: Date.now() + '',\n      httpclient,\n    }));\n    await client.ready();\n    this.store = new LocalFileOffsetStore(client, 'please_rename_unique_group_name_1');\n  });\n\n  it('should load ok', () => {\n    return this.store.load();\n  });\n\n  it('should updateOffset ok', () => {\n    const mq = new MessageQueue('TopicTest_1', 'taobaodaily-04', 1);\n    this.store.updateOffset(mq, 1000, true);\n    assert(this.store.offsetTable.get('[topic=\"TopicTest_1\", brokerName=\"taobaodaily-04\", queueId=\"1\"]') === 1000);\n    this.store.updateOffset(null, 1000);\n  });\n\n  it('should readOffset ok', async () => {\n    const mq = new MessageQueue('TopicTest_1', 'taobaodaily-04', 1);\n    this.store.updateOffset(mq, 1000, true);\n    let offset = await this.store.readOffset(mq, 'READ_FROM_MEMORY');\n    assert(offset === 1000);\n\n    offset = await this.store.readOffset(mq, 'READ_FROM_STORE');\n    assert(typeof offset === 'number');\n\n    offset = await this.store.readOffset(null, 'READ_FROM_STORE');\n    assert(offset === -1);\n\n    const mq1 = new MessageQueue('TopicTest_1', 'xxx', 1);\n    offset = await this.store.readOffset(mq1, 'READ_FROM_STORE');\n    assert(typeof offset === 'number');\n    assert(offset === -1);\n\n    const mq2 = new MessageQueue('TopicTest_1', 'yyy', 1);\n    offset = await this.store.readOffset(mq2, 'MEMORY_FIRST_THEN_STORE');\n    assert(typeof offset === 'number');\n    assert(offset === -1);\n  });\n\n  it('should persistAll ok', async () => {\n    const mq = new MessageQueue('TopicTest_1', 'taobaodaily-04', 1);\n    this.store.updateOffset(mq, 1000);\n    await this.store.persistAll([ mq ]);\n    await this.store.persistAll([]);\n    await this.store.persistAll(null);\n  });\n\n  it('should persist ok', async () => {\n    const mq = new MessageQueue('TopicTest_1', 'taobaodaily-04', 1);\n    this.store.updateOffset(mq, 1000);\n    await this.store.persist(mq);\n    const mq1 = new MessageQueue('TopicTest_1', 'zzz', 1);\n    await this.store.persist(mq1);\n  });\n\n  it('should removeOffset ok', () => {\n    const mq = new MessageQueue('TopicTest_1', 'taobaodaily-04', 1);\n    this.store.updateOffset(mq, 1000);\n    this.store.removeOffset(mq);\n    assert(!this.store.offsetTable.has(mq.key));\n  });\n});\n"
  },
  {
    "path": "test/store/local_memory.test.js",
    "content": "'use strict';\n\nconst mm = require('mm');\nconst assert = require('assert');\nconst MessageQueue = require('../../lib/message_queue');\nconst ReadOffsetType = require('../../lib/store/read_offset_type');\nconst LocalMemoryOffsetStore = require('../../lib/store/local_memory');\n\ndescribe('test/store/local_file.test.js', () => {\n  let offsetStore;\n  const groupName = 'S_appname_service';\n  before(() => {\n    offsetStore = new LocalMemoryOffsetStore({\n      logger: console,\n    }, groupName);\n  });\n  beforeEach(mm.restore);\n\n  it('should updateOffset & readOffset ok', async () => {\n    const mq = new MessageQueue('TopicTest_1', 'taobaodaily-04', 1);\n    offsetStore.updateOffset(mq, 1000, false);\n    let offset = await offsetStore.readOffset(mq, ReadOffsetType.MEMORY_FIRST_THEN_STORE);\n    assert(offset === 1000);\n    offset = await offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_MEMORY);\n    assert(offset === 1000);\n    offset = await offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE);\n    assert(offset === 1000);\n\n    await offsetStore.persist(mq);\n    offset = await offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE);\n    assert(offset === 1000);\n\n    const mq2 = new MessageQueue('TopicTest_1', 'taobaodaily-04', 2);\n    await offsetStore.persistAll([ mq, mq2 ]);\n    offset = await offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE);\n    assert(offset === 1000);\n  });\n\n  it('should updateOffset increaseOnly', async () => {\n    const mq = new MessageQueue('TopicTest_1', 'taobaodaily-04', 1);\n    offsetStore.updateOffset(mq, 1000, false);\n    let offset = await offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_MEMORY);\n    assert(offset === 1000);\n\n    offsetStore.updateOffset();\n    offsetStore.updateOffset(mq, 999, true);\n    offset = await offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_MEMORY);\n    assert(offset === 1000);\n\n    offsetStore.updateOffset(mq, 999, false);\n    offset = await offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_MEMORY);\n    assert(offset === 999);\n\n    offset = await offsetStore.readOffset(null, ReadOffsetType.READ_FROM_MEMORY);\n    assert(offset === -1);\n    offset = await offsetStore.readOffset(null, ReadOffsetType.MEMORY_FIRST_THEN_STORE);\n    assert(offset === -1);\n    offset = await offsetStore.readOffset(null, ReadOffsetType.READ_FROM_STORE);\n    assert(offset === -1);\n    offset = await offsetStore.readOffset(mq);\n    assert(offset === -1);\n  });\n\n  it('should load ok', async () => {\n    const mq = new MessageQueue('TopicTest_1', 'taobaodaily-04', 1);\n    offsetStore.updateOffset(mq, 1000, false);\n\n    const offsetStore2 = new LocalMemoryOffsetStore({\n      logger: console,\n    }, groupName);\n    let offset = await offsetStore2.readOffset(mq, ReadOffsetType.READ_FROM_MEMORY);\n    assert(offset === -1);\n    await offsetStore2.load();\n    offset = await offsetStore2.readOffset(mq, ReadOffsetType.READ_FROM_MEMORY);\n    assert(offset === -1);\n\n    offsetStore.removeOffset(mq);\n    offset = await offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_MEMORY);\n    assert(offset === -1);\n    offset = await offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE);\n    assert(offset === -1);\n  });\n});\n"
  },
  {
    "path": "test/store/remote_broker.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst httpclient = require('urllib');\nconst MQClient = require('../../lib/mq_client');\nconst config = require('../../example/config');\nconst ClientConfig = require('../../lib/client_config');\nconst MessageQueue = require('../../lib/message_queue');\nconst RemoteBrokerOffsetStore = require('../../lib/store/remote_broker');\n\nconst TOPIC = config.topic;\nconst consumerGroup = config.consumerGroup;\n\ndescribe('test/store/remote_broker.test.js', function() {\n  let client;\n  let brokerName;\n  before(async () => {\n    client = new MQClient(new ClientConfig(Object.assign({ httpclient }, config)));\n    await client.ready();\n    const routerInfoData = await client.getDefaultTopicRouteInfoFromNameServer(TOPIC, 3000);\n    brokerName = routerInfoData.brokerDatas[0].brokerName;\n    this.store = new RemoteBrokerOffsetStore(client, consumerGroup);\n  });\n\n  after(() => client.close());\n\n  it('should load ok', () => this.store.load());\n\n  it('should updateOffset ok', () => {\n    const mq = new MessageQueue(TOPIC, brokerName, 1);\n    this.store.updateOffset(mq, 1000);\n    assert(this.store.offsetTable.get(`[topic=\"${TOPIC}\", brokerName=\"${brokerName}\", queueId=\"1\"]`) === 1000);\n    this.store.updateOffset(null, 1000);\n  });\n\n  it('should readOffset ok', async () => {\n    const mq = new MessageQueue(TOPIC, brokerName, 1);\n    this.store.updateOffset(mq, 1000);\n    let offset = await this.store.readOffset(mq, 'READ_FROM_MEMORY');\n    assert(offset === 1000);\n\n    offset = await this.store.readOffset(mq, 'READ_FROM_STORE');\n    assert(typeof offset === 'number');\n\n    offset = await this.store.readOffset(null, 'READ_FROM_STORE');\n    assert(offset === -1);\n\n    const mq1 = new MessageQueue(TOPIC, 'xxx', 1);\n    offset = await this.store.readOffset(mq1, 'READ_FROM_STORE');\n    assert(typeof offset === 'number');\n    assert(offset === -1);\n\n    const mq2 = new MessageQueue(TOPIC, 'yyy', 1);\n    offset = await this.store.readOffset(mq2, 'MEMORY_FIRST_THEN_STORE');\n    assert(typeof offset === 'number');\n    assert(offset === -1);\n  });\n\n  it('should persistAll ok', async () => {\n    const mq = new MessageQueue(TOPIC, brokerName, 1);\n    this.store.updateOffset(mq, 1000);\n    await this.store.persistAll([ mq ]);\n    await this.store.persistAll([]);\n    await this.store.persistAll(null);\n  });\n\n  it('should persist ok', async () => {\n    const mq = new MessageQueue(TOPIC, brokerName, 1);\n    this.store.updateOffset(mq, 1000);\n    await this.store.persist(mq);\n    const mq1 = new MessageQueue(TOPIC, 'zzz', 1);\n    await this.store.persist(mq1);\n  });\n\n  it('should removeOffset ok', () => {\n    const mq = new MessageQueue(TOPIC, brokerName, 1);\n    this.store.updateOffset(mq, 1000);\n    this.store.removeOffset(mq);\n    assert(!this.store.offsetTable.has(mq.key));\n  });\n});\n"
  },
  {
    "path": "test/utils/index.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst util = require('../../lib/utils/index');\n\ndescribe('test/utils/index.test.js', () => {\n  it('should parseDate ok', () => {\n    const d = util.parseDate('2018112000000');\n    assert.equal(d.getTime() - d.getTimezoneOffset() * 60 * 1000, 1542672000000);\n  });\n\n  it('should getRetryTopic ok', () => {\n    const msg = {\n      retryTopic: 'xxx',\n    };\n    util.resetRetryTopic(msg, 'yyy');\n    assert.equal(msg.retryTopic, 'xxx');\n  });\n});\n"
  },
  {
    "path": "test/utils/message_sys_flag.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst MessageSysFlag = require('../../lib/utils/message_sys_flag');\n\ndescribe('test/utils/message_sys_flag.test.js', () => {\n\n  it('should getTransactionValue ok', () => {\n    assert(MessageSysFlag.getTransactionValue(7) === 4);\n    assert(MessageSysFlag.getTransactionValue(6) === 4);\n    assert(MessageSysFlag.getTransactionValue(15) === 12);\n    assert(MessageSysFlag.getTransactionValue(100) === 4);\n  });\n\n  it('should resetTransactionValue ok', () => {\n    assert(MessageSysFlag.getTransactionValue(MessageSysFlag.resetTransactionValue(7, 0)) === 0);\n    assert(MessageSysFlag.getTransactionValue(MessageSysFlag.resetTransactionValue(7, 4)) === 4);\n    assert(MessageSysFlag.getTransactionValue(MessageSysFlag.resetTransactionValue(7, 8)) === 8);\n    assert(MessageSysFlag.getTransactionValue(MessageSysFlag.resetTransactionValue(7, 12)) === 12);\n  });\n\n  it('should clearCompressedFlag ok', () => {\n    assert(MessageSysFlag.clearCompressedFlag(7) === 6);\n    assert(MessageSysFlag.clearCompressedFlag(6) === 6);\n  });\n});\n"
  },
  {
    "path": "test/utils/pull_sys_flag.test.js",
    "content": "'use strict';\n\nconst assert = require('assert');\nconst PullSysFlag = require('../../lib/utils/pull_sys_flag');\n\ndescribe('test/utils/pull_sys_flag.test.js', () => {\n\n  it('should buildSysFlag ok', () => {\n    assert(PullSysFlag.buildSysFlag(true, true, true, true) === 15);\n    assert(PullSysFlag.buildSysFlag(false, true, true, true) === 14);\n    assert(PullSysFlag.buildSysFlag(false, false, true, true) === 12);\n    assert(PullSysFlag.buildSysFlag(false, false, false, true) === 8);\n    assert(PullSysFlag.buildSysFlag(false, false, false, false) === 0);\n\n    assert(PullSysFlag.buildSysFlag(false, true, true, true) ===\n      PullSysFlag.clearCommitOffsetFlag(PullSysFlag.buildSysFlag(true, true, true, true)));\n\n    assert(PullSysFlag.hasCommitOffsetFlag(PullSysFlag.buildSysFlag(true, true, true, true)));\n    assert(!PullSysFlag.hasCommitOffsetFlag(PullSysFlag.buildSysFlag(false, true, true, true)));\n\n    assert(PullSysFlag.hasSuspendFlag(PullSysFlag.buildSysFlag(true, true, true, true)));\n    assert(!PullSysFlag.hasSuspendFlag(PullSysFlag.buildSysFlag(false, false, true, true)));\n\n    assert(PullSysFlag.hasSubscriptionFlag(PullSysFlag.buildSysFlag(true, true, true, true)));\n    assert(!PullSysFlag.hasSubscriptionFlag(PullSysFlag.buildSysFlag(false, true, false, true)));\n\n    assert(PullSysFlag.hasClassFilterFlag(PullSysFlag.buildSysFlag(true, true, true, true)));\n    assert(!PullSysFlag.hasClassFilterFlag(PullSysFlag.buildSysFlag(false, true, true, false)));\n  });\n});\n"
  },
  {
    "path": "test/utils.js",
    "content": "'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\nconst fixtures = path.join(__dirname, 'fixtures');\n\nexports.bytes = name => {\n  return fs.readFileSync(path.join(fixtures, name));\n};\n\nexports.write = (name, buf) => {\n  fs.writeFileSync(path.join(fixtures, name), buf);\n};\n"
  }
]