[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ncharset = utf-8\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug report\ndescription: Template for bug reports\ntitle: '[Bug]: '\nlabels:\n  - bug\nbody:\n  - type: input\n    attributes:\n      label: MQTTjs Version\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Broker\n      description: >-\n        The broker you are using (Aedes, Mosca, mosquitto, RabbitMQ, HiveMQ\n        etc...)\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: Environment\n      options:\n        - NodeJS\n        - Browser\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Description\n      description: A clear and concise description of the problem\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Minimal Reproduction\n      description: provide steps to reproduce the problem\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Debug logs\n      description: Provide logs to help diagnose your issue\n      placeholder: >-\n        On NodeJS simply run your application using `DEBUG=mqttjs*` env var, on\n        browsers you should enable `verbose` level in console or you can set\n        `log` option to `console.log.bind(console)`\n    validations:\n      required: true\n  - type: markdown\n    attributes:\n      value: >-\n        This template was generated with [Issue Forms\n        Creator](https://issue-forms-creator.netlify.app)\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: ❓ Ask a Question\n    url: https://github.com/mqttjs/MQTT.js/discussions/new\n    about: If you have any other questions, ask them here."
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug report\ndescription: Template for bug reports\ntitle: '[Bug]: '\nlabels:\n  - bug\nbody:\n  - type: input\n    attributes:\n      label: MQTTjs Version\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Broker\n      description: >-\n        The broker you are using (Aedes, Mosca, mosquitto, RabbitMQ, HiveMQ\n        etc...)\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: Environment\n      options:\n        - NodeJS\n        - Browser\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Description\n      description: A clear and concise description of the problem\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Minimal Reproduction\n      description: provide steps to reproduce the problem\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Debug logs\n      description: Provide logs to help diagnose your issue\n      placeholder: >-\n        On NodeJS simply run your application using `DEBUG=mqttjs*` env var, on\n        browsers you should enable `verbose` level in console or you can set\n        `log` option to `console.log.bind(console)`\n    validations:\n      required: true\n  - type: markdown\n    attributes:\n      value: >-\n        This template was generated with [Issue Forms\n        Creator](https://issue-forms-creator.netlify.app)\n"
  },
  {
    "path": ".github/workflows/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: ❓ Ask a Question\n    url: https://github.com/mqttjs/MQTT.js/discussions/new\n    about: If you have any other questions, ask them here."
  },
  {
    "path": ".github/workflows/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/browser-tests.yml",
    "content": "name: Browser Tests\n\non:\n  workflow_dispatch:      \n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  browser:\n    runs-on: ubuntu-latest\n    container: mcr.microsoft.com/playwright:v1.57.0-noble # https://playwright.dev/docs/ci-intro#via-containers\n    strategy:\n      matrix:\n        node-version: [22.x]\n      fail-fast: false\n\n    steps:\n    - uses: actions/checkout@v6\n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v6\n      with:\n        node-version: ${{ matrix.node-version }}\n        cache: 'npm'\n\n    - name: Install Dependencies\n      run: npm ci\n\n    - name: Test Browser\n      # only run on latest node version, no reason to run on all\n      timeout-minutes: 2\n      env:\n        HOME: /root\n      run: |\n        npm run unit-test:browser\n  \n\n\n"
  },
  {
    "path": ".github/workflows/electron-tests.yml",
    "content": "name: Electron Tests\n\non:\n  workflow_dispatch:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  browser:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [22.x]\n      fail-fast: false\n\n    steps:\n    - uses: actions/checkout@v6\n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v6\n      with:\n        node-version: ${{ matrix.node-version }}\n        cache: 'npm'\n\n    - name: Install Dependencies\n      run: npm ci\n\n    - name: Build MQTT.JS\n      run: npm run build\n\n    - name: Run headless test\n      run: cd electron-test && npm i && xvfb-run npm run wdio\n"
  },
  {
    "path": ".github/workflows/mqttjs-test.yml",
    "content": "name: MQTT.js Tests\n\non:\n  workflow_dispatch:\n    inputs:\n      logLevel:\n        description: 'Debug Filter'     \n        required: true\n        default: 'mqttjs*'\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node-version: [22.x, 24.x]\n      fail-fast: false\n\n    steps:\n    - uses: actions/checkout@v6\n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v6\n      with:\n        node-version: ${{ matrix.node-version }}\n        cache: 'npm'\n\n    - name: Install Dependencies\n      run: npm ci\n    \n    - name: Lint\n      if: matrix.node-version == '22.x'\n      # only run on latest node version, no reason to run on all\n      run: |\n        npm run lint\n\n    - name: Test NodeJS\n      run: npm run test:node\n      timeout-minutes: 5\n      env:\n        CI: true\n        DEBUG: \"${{ runner.debug == '1' && 'mqttjs:*' || '' }}\"\n\n    # upload coverage to Codecov\n    # https://app.codecov.io/gh/mqttjs/MQTT.js\n    - name: Upload coverage to Codecov\n      uses: codecov/codecov-action@v5\n      with:\n        directory: ./coverage/\n        fail_ci_if_error: true\n        flags: unittests\n        name: codecov-mqttjs\n        token: ${{ secrets.CODECOV_TOKEN }}\n        verbose: true\n    \n    - name: Upload test results to Codecov\n      if: ${{ !cancelled() }}\n      uses: codecov/test-results-action@v1\n      with:\n        files: ./junit.xml\n        fail_ci_if_error: false\n        flags: unittests-results\n        name: codecov-mqttjs-test-results\n        token: ${{ secrets.CODECOV_TOKEN }}\n        verbose: true\n\n"
  },
  {
    "path": ".github/workflows/release-it.yml",
    "content": "# #########################################################################\n# Creates a new release using `workflow_dispatch` event trigger with `type`\n# as input to describe the type of release to create\nname: 'Release-it: Create a new release on demand'\n\non:\n  workflow_dispatch:\n    inputs:\n      type:\n        description: 'Type/Options. `major --preRelease=beta`, `--preRelease`, `major`, `patch`, `minor` or `major`'\n        required: false\n        default: 'patch'\n\njobs:\n  release:\n    permissions:\n      contents: write\n      id-token: write\n    runs-on: [ubuntu-latest]\n    strategy:\n      matrix:\n        node-version: [22.x]\n    steps:\n      - name: Checkout main\n        uses: actions/checkout@v6\n        with:\n          ref: 'main'\n          fetch-depth: 0 # fetch all commits history to create the changelog\n          token: ${{ secrets.GH_TOKEN }}\n\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node-version }}\n          cache: 'npm'\n\n      - name: Upgrade npm for OIDC support (requires npm >= 11.5.1)\n        run: npm install -g npm@latest\n\n      - name: Install dependencies\n        run: npm ci\n      \n      - name: Initialize Git user\n        run: |\n          git config --global user.email \"${{ github.actor }}@users.noreply.github.com }}\"\n          git config --global user.name \"${{ github.actor }}\"\n      \n      - name: Initialize NPM config\n        run: |\n          npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN\n        env:\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}\n\n      - name: Make the release\n        env:\n          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}\n          NPM_TOKEN: ${{ secrets.NPM_TOKEN }} \n        run: |\n          # When all commits since the latest major tag should be added to the changelog, use --git.tagExclude='*[-]*'\n          npx release-it ${{github.event.inputs.type}} --git.tagExclude='*[-]*' --ci --verbose\n"
  },
  {
    "path": ".github/workflows/semantic-pr.yml",
    "content": "name: \"Semantic PR Title\"\n\non:\n  pull_request_target:\n    types:\n      - opened\n      - edited\n      - synchronize\n\npermissions:\n  pull-requests: read\n\njobs:\n  main:\n    name: Validate PR title\n    runs-on: ubuntu-latest\n    steps:\n      - uses: amannn/action-semantic-pull-request@v6\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Close inactive issues\non:\n  schedule:\n    - cron: \"30 1 * * *\"\n\njobs:\n  close-stale:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: write\n    steps:\n      - uses: actions/stale@v10\n        with:\n          days-before-issue-stale: 365\n          days-before-issue-close: 14\n          stale-issue-label: \"stale\"\n          stale-issue-message: |\n            This is an automated message to let you know that this issue has\n            gone 365 days without any activity. In order to ensure that we work\n            on issues that still matter, this issue will be closed in 14 days.\n\n            If this issue is still important, you can simply comment with a\n            \"bump\" to keep it open.\n\n            Thank you for your contribution.\n          close-issue-message: \"This issue was automatically closed due to inactivity.\"\n          days-before-pr-stale: -1\n          days-before-pr-close: -1\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          exempt-all-assignees: true\n          exempt-all-milestones: true\n          ascending: true\n          operations-per-run: 300\n          debug-only: false\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ncerts/*\n[._]*.s[a-w][a-z]\n[._]s[a-w][a-z]\n*.un~\nSession.vim\n.netrwhist\n*~\nnpm-debug.log\ndist/\nyarn.lock\ncoverage\n.nyc_output\n.idea/*\ntest/typescript/.idea/*\ntest/typescript/*.js\ntest/typescript/*.map\n# VS Code stuff\n**/typings/**\n.vscode/\n.npmrc\njunit.xml\n\n/build/\n\n# macOS stuff\n.DS_Store\n.DS_Store?\n"
  },
  {
    "path": ".prettierignore",
    "content": "*.md\nREADME.md\n/types/\n/examples/\n/doc/\n/dist/\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "module.exports = {\n  semi: false,\n  singleQuote: true,\n  useTabs: true,\n  tabWidth: 4,\n  endOfLine: \"lf\",\n};\n"
  },
  {
    "path": ".release-it.json",
    "content": "{\n  \"$schema\": \"https://unpkg.com/release-it@19/schema/release-it.json\",\n  \"github\": {\n    \"release\": true\n  },\n  \"git\": {\n    \"tagName\": \"v${version}\",\n    \"commitMessage\": \"chore(release): ${version}\",\n    \"requireBranch\": \"main\"\n  },\n  \"hooks\": {\n    \"before:init\": [\n      \"npm run test\"\n    ]\n  },\n  \"npm\": {\n    \"publish\": true,\n    \"skipChecks\": true\n  },\n  \"plugins\": {\n    \"@release-it/conventional-changelog\": {\n      \"preset\": \"angular\",\n      \"infile\": \"CHANGELOG.md\"\n    }\n  }\n}\n"
  },
  {
    "path": "CHANGELOG.OLD.md",
    "content": "# Release History\n\n## 4.3.7\n\n### PR\n\nfix: regression from #1401 and allow CI test failures to break gitthub workflow (#1443)\n\n## 4.3.6\n\n### PR\n\nchore: update CI\n\nfix(browser): require buffer (#1420)\n\nfix(types): connect function proper overloads (#1416)\n\n## 4.3.5\n\n### PR\n\nfix(drain-leak): fix regression introduced in #1301 (#1401)\n\n## 4.3.4\n\n### PR\n\nfix(dependency): migrate LruMap from collections to lru-cache (#1396)\n\n## 4.3.3\n\n### PR\n\nfix(publish): call callback when messageId available (#1393)\n\nfix: remove collections.js depdendency from number-allocator (#1394)\n\n### PR\n\nfix(dependencies): update collections (#1386)\n\n## 4.3.2\n\n### PR\n\nfix(dependencies): update collections (#1386)\n\n## 4.3.1\n\n### PR\n\nfix(dependencies): remove babel-eslint and snazzy (#1383)\n\n## 4.3.0\n\n### PR\n\nrefined topic alias support (#1301)\n\nfix security vulnerability in ws stream (#1307)\n\nskip TLS SNI if host is IP address (#1311)\n\nupdate readme about vNext discussions (#1328)\n\nupdate readme sample (#1331)\n\nadd support for ALPN TLS extension (#1332)\n\nalign onConnectCallback with specs expecting connack packet (#1333)\n\nfix resubscribe messageId allocate twice (#1337)\n\nrework examples to be a bit more specific (#1352)\n\nreadme typo fixed (#1353)\n\nfix(typescript): use correct version of @types/ws (#1358)\n\nfix(type): fix push properties types (#1359)\n\nfix: audit dev dependencies (#1374)\n\nfix(type): add properties type for IClientSubscribeOptions (#1378)\n\nfeat(client): auth handler for enhanced auth (#1380)\n\n## 4.2.8\n\n### PR\n\nFix ws vulnerability and typescript bug (#1292)\n\n## 4.2.7\n\n### PR\n\n#1287 - Fix production vulnerabilities (#1289)\n\n#1215 - Add missing 'duplexify' dependency (#1266)\n\nImprove type definition for 'wsOptions' (#1256)\n\nImprove Typescript Declaratiosn for userProperties (#1249)\n\n#1235 - Call the end on the WebSocket stream when WebSocket close event is emitted. (#1239)\n\n#1201 - Uncaught TypeError: net.createConnection is not a function. (#1236)\n\nImprove Documentation for Browserify (#1224)\n\n## v4.2.6 and Below\n\nThe release history has been documented in the GitHub releases and tags historically. \n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n# [5.15.0](/compare/v5.14.1...v5.15.0) (2026-02-02)\n\n\n### Features\n\n* exported topic validation functions for external use (#2036) 396e02a, closes #2036\n\n## [5.14.1](https://github.com/mqttjs/MQTT.js/compare/v5.14.0...v5.14.1) (2025-09-04)\n\n\n### Bug Fixes\n\n* **connect/ws:** ensure proxy stream is writable ([#2024](https://github.com/mqttjs/MQTT.js/issues/2024)) ([fe45405](https://github.com/mqttjs/MQTT.js/commit/fe454056d5b54718b1b3c90611026da4c908faef)), closes [#1914](https://github.com/mqttjs/MQTT.js/issues/1914)\n* isWebWorkerEnv check for Deno environment ([#2023](https://github.com/mqttjs/MQTT.js/issues/2023)) ([a6e74ad](https://github.com/mqttjs/MQTT.js/commit/a6e74add5e41cf9d2a3b62357e0c0531a128e7cd))\n\n# [5.14.0](https://github.com/mqttjs/MQTT.js/compare/v5.13.3...v5.14.0) (2025-07-30)\n\n## [5.13.3](https://github.com/mqttjs/MQTT.js/compare/v5.13.2...v5.13.3) (2025-07-18)\n\n## [5.13.2](https://github.com/mqttjs/MQTT.js/compare/v5.13.1...v5.13.2) (2025-07-07)\n\n\n### Bug Fixes\n\n* move back @types/ws from dev to deps ([#2008](https://github.com/mqttjs/MQTT.js/issues/2008)) ([959b132](https://github.com/mqttjs/MQTT.js/commit/959b132a492f43902deb994d119d07f15f955390)), closes [#1991](https://github.com/mqttjs/MQTT.js/issues/1991)\n\n## [5.13.1](https://github.com/mqttjs/MQTT.js/compare/v5.13.0...v5.13.1) (2025-06-03)\n\n# [5.13.0](https://github.com/mqttjs/MQTT.js/compare/v5.12.1...v5.13.0) (2025-05-09)\n\n\n### Features\n\n* add `subscribeBatchSize` option to split subscribe packets for AWS IoT Core ([#1995](https://github.com/mqttjs/MQTT.js/issues/1995)) ([6b719c8](https://github.com/mqttjs/MQTT.js/commit/6b719c8cd11397bb13f06c36b3fa5a3840befacb))\n\n## [5.12.1](https://github.com/mqttjs/MQTT.js/compare/v5.12.0...v5.12.1) (2025-05-06)\n\n\n### Bug Fixes\n\n* add `@typescript-eslint/consistent-type-imports` rule and normalised all imports ([640cd3b](https://github.com/mqttjs/MQTT.js/commit/640cd3b9bd7d9a7d8ef59fa3fdd57a3a1c3ba345))\n\n# [5.12.0](https://github.com/mqttjs/MQTT.js/compare/v5.11.1...v5.12.0) (2025-04-28)\n\n\n### Features\n\n* **exports:** add react-native entry point to package.json ([#1988](https://github.com/mqttjs/MQTT.js/issues/1988)) ([622d3d8](https://github.com/mqttjs/MQTT.js/commit/622d3d8e7d8149bbb2ee03a01254e0ab9bf42e6c))\n\n## [5.11.1](https://github.com/mqttjs/MQTT.js/compare/v5.11.0...v5.11.1) (2025-04-23)\n\n\n### Bug Fixes\n\n* **deps:** removed unused 'reinterval' dependency and move two @type/ dependencies to devDependencies. ([bb8c694](https://github.com/mqttjs/MQTT.js/commit/bb8c6947edc4d85d77f13f8c06df7a979a270a7f))\n\n# [5.11.0](https://github.com/mqttjs/MQTT.js/compare/v5.10.4...v5.11.0) (2025-04-10)\n\n\n### Bug Fixes\n\n* correct return type of `validateTopis` to include `null` ([#1979](https://github.com/mqttjs/MQTT.js/issues/1979)) ([935784a](https://github.com/mqttjs/MQTT.js/commit/935784aa8235fd9f38ac17820edf357ed7d5ab75))\n\n\n### Features\n\n* add intrinsic support for SOCKS proxies ([#1966](https://github.com/mqttjs/MQTT.js/issues/1966)) ([ee0fcce](https://github.com/mqttjs/MQTT.js/commit/ee0fcce4bd3e1af318b0144b9e0928c571c02173))\n\n## [5.10.4](https://github.com/mqttjs/MQTT.js/compare/v5.10.3...v5.10.4) (2025-02-26)\n\n\n### Bug Fixes\n\n* **browser:** bump readable-stream@4.7.0 to fix BigInt issues ([#1963](https://github.com/mqttjs/MQTT.js/issues/1963)) ([9749891](https://github.com/mqttjs/MQTT.js/commit/9749891137c0eb57f20c13e17196bccbdec1029e))\n* **browser:** prevent error stream.push() after EOF ([#1932](https://github.com/mqttjs/MQTT.js/issues/1932)) ([df2e8fe](https://github.com/mqttjs/MQTT.js/commit/df2e8febcccd4574a046887e612113a85900b86d))\n* **electron-test:** webdriver session not created ([#1972](https://github.com/mqttjs/MQTT.js/issues/1972)) ([57691cf](https://github.com/mqttjs/MQTT.js/commit/57691cf9a088dee69e0dea637ae5742c6dfae833))\n* move protocol \"clean\" after merge of parsedOptions ([#1965](https://github.com/mqttjs/MQTT.js/issues/1965)) ([1c19ca0](https://github.com/mqttjs/MQTT.js/commit/1c19ca0d0a810ee9a925d17552e9d84f70aa718b))\n\n## [5.10.3](https://github.com/mqttjs/MQTT.js/compare/v5.10.2...v5.10.3) (2024-11-26)\n\n\n### Bug Fixes\n\n* add option to reconnect if connack has an error code ([#1948](https://github.com/mqttjs/MQTT.js/issues/1948)) ([fa19586](https://github.com/mqttjs/MQTT.js/commit/fa19586cf0482b007e136e013cf7c9a423b882eb))\n\n## [5.10.2](https://github.com/mqttjs/MQTT.js/compare/v5.10.1...v5.10.2) (2024-11-13)\n\n\n### Features\n\n* **electron-test:** move third hosted broker to self hosted broker ([#1926](https://github.com/mqttjs/MQTT.js/issues/1926)) ([1ca3f9e](https://github.com/mqttjs/MQTT.js/commit/1ca3f9e37f6ef7fe86307e292042c6542ca35241))\n\n## [5.10.1](https://github.com/mqttjs/MQTT.js/compare/v5.10.0...v5.10.1) (2024-08-28)\n\n\n### Bug Fixes\n\n* **browser:** handle `Blob` payloads ([#1930](https://github.com/mqttjs/MQTT.js/issues/1930)) ([86b7959](https://github.com/mqttjs/MQTT.js/commit/86b795983d86847e1da334fd0d30cbd80f92b540))\n\n# [5.10.0](https://github.com/mqttjs/MQTT.js/compare/v5.9.1...v5.10.0) (2024-08-14)\n\n\n### Bug Fixes\n\n* **test:** close all open connections in abstract_client test ([#1917](https://github.com/mqttjs/MQTT.js/issues/1917)) ([661c30a](https://github.com/mqttjs/MQTT.js/commit/661c30aecb8d7531fc052a7770519267067840fb))\n* **types:** unsubscribe options type ([#1921](https://github.com/mqttjs/MQTT.js/issues/1921)) ([18a357c](https://github.com/mqttjs/MQTT.js/commit/18a357ce98c2e7ae053afefaf7c56a0d3a8e62b7))\n\n\n### Features\n\n* add `suback` packet to subscribe callback ([#1923](https://github.com/mqttjs/MQTT.js/issues/1923)) ([93f4482](https://github.com/mqttjs/MQTT.js/commit/93f4482570b6e96d81a5466ea94c3fd7308ff31c))\n* add unsubscribe ack packet to the unsubscribe callback ([#1922](https://github.com/mqttjs/MQTT.js/issues/1922)) ([8bcf304](https://github.com/mqttjs/MQTT.js/commit/8bcf3042a9133acf8d266d73bc67153a69660e05))\n\n## [5.9.1](https://github.com/mqttjs/MQTT.js/compare/v5.9.0...v5.9.1) (2024-08-01)\n\n\n### Bug Fixes\n\n* **browser:** ensure proxy is defined ([ffc9805](https://github.com/mqttjs/MQTT.js/commit/ffc9805a51adf88bded6a1af1c0f66004e9e0f08))\n* **browser:** prevent error `stream.push() after EOF` ([#1915](https://github.com/mqttjs/MQTT.js/issues/1915)) ([b5cc835](https://github.com/mqttjs/MQTT.js/commit/b5cc835fed9bd624c20d5f4f42b15c3cfa4b3fbe)), closes [#1914](https://github.com/mqttjs/MQTT.js/issues/1914)\n* **test:** close open connections ([#1911](https://github.com/mqttjs/MQTT.js/issues/1911)) ([053a7be](https://github.com/mqttjs/MQTT.js/commit/053a7be91f93a0a27c63ca5ed488d9206fdec960))\n\n# [5.9.0](https://github.com/mqttjs/MQTT.js/compare/v5.8.1...v5.9.0) (2024-07-26)\n\n\n### Bug Fixes\n\n* tets hang up ([#1906](https://github.com/mqttjs/MQTT.js/issues/1906)) ([c462530](https://github.com/mqttjs/MQTT.js/commit/c462530d2ec0b61a20cc43f188254bf2b403787a))\n* **types:** add connectAsync overload signature with allowRetries ([#1909](https://github.com/mqttjs/MQTT.js/issues/1909)) ([6b278dc](https://github.com/mqttjs/MQTT.js/commit/6b278dca5a5b82b07835344f3c129ddd5b73e6e8))\n\n\n### Features\n\n* add `forceNativeWebSocket` client option ([#1910](https://github.com/mqttjs/MQTT.js/issues/1910)) ([103d172](https://github.com/mqttjs/MQTT.js/commit/103d1721d68952e536a3704a05a569c95f0a1987)), closes [#1796](https://github.com/mqttjs/MQTT.js/issues/1796) [#1895](https://github.com/mqttjs/MQTT.js/issues/1895)\n\n## [5.8.1](https://github.com/mqttjs/MQTT.js/compare/v5.8.0...v5.8.1) (2024-07-18)\n\n\n### Bug Fixes\n\n* connect after client.end not working ([#1902](https://github.com/mqttjs/MQTT.js/issues/1902)) ([fbe5294](https://github.com/mqttjs/MQTT.js/commit/fbe52949b47378768fd325f01682a766a5965dfe))\n* reschedule pings problem ([#1904](https://github.com/mqttjs/MQTT.js/issues/1904)) ([8e14d3e](https://github.com/mqttjs/MQTT.js/commit/8e14d3eac01f4fcccfc1ee657e0158d0644951ce))\n\n# [5.8.0](https://github.com/mqttjs/MQTT.js/compare/v5.7.3...v5.8.0) (2024-07-05)\n\n\n### Features\n\n* add compatibility with txiki.js ([#1895](https://github.com/mqttjs/MQTT.js/issues/1895)) ([37b08c9](https://github.com/mqttjs/MQTT.js/commit/37b08c99fead5282e38b851ce1006f09521b038c))\n* allow to pass custom timer for keepalive manager ([#1896](https://github.com/mqttjs/MQTT.js/issues/1896)) ([ee81184](https://github.com/mqttjs/MQTT.js/commit/ee811844d07365ca98721be90c4e1c2c1d8623b9))\n\n## [5.7.3](https://github.com/mqttjs/MQTT.js/compare/v5.7.2...v5.7.3) (2024-06-26)\n\n\n### Bug Fixes\n\n* **wechat:** do not ignore path with `wx` protocol ([#1894](https://github.com/mqttjs/MQTT.js/issues/1894)) ([300c0b4](https://github.com/mqttjs/MQTT.js/commit/300c0b4dc5a37d8594a4cb1af5836c095b4d823c)), closes [#1892](https://github.com/mqttjs/MQTT.js/issues/1892)\n\n## [5.7.2](https://github.com/mqttjs/MQTT.js/compare/v5.7.1...v5.7.2) (2024-06-19)\n\n\n### Bug Fixes\n\n* **security:** bump ws@8.17.1 and other audit issues ([#1891](https://github.com/mqttjs/MQTT.js/issues/1891)) ([096baaa](https://github.com/mqttjs/MQTT.js/commit/096baaaa882627554efd4bc9985ce8a5f2dfda5e))\n\n## [5.7.1](https://github.com/mqttjs/MQTT.js/compare/v5.7.0...v5.7.1) (2024-06-18)\n\n\n### Bug Fixes\n\n* suback Error Codes Handling ([#1887](https://github.com/mqttjs/MQTT.js/issues/1887)) ([2a98e5e](https://github.com/mqttjs/MQTT.js/commit/2a98e5e878cad632fe86e738144688ee2b14a7dd))\n\n# [5.7.0](https://github.com/mqttjs/MQTT.js/compare/v5.6.2...v5.7.0) (2024-05-28)\n\n\n### Features\n\n* add `unixSocket` option and `+unix` suffix support to protocol ([#1874](https://github.com/mqttjs/MQTT.js/issues/1874)) ([1004c78](https://github.com/mqttjs/MQTT.js/commit/1004c78db7d6763f21c98fa3db2f12e688ca33ff))\n\n## [5.6.2](https://github.com/mqttjs/MQTT.js/compare/v5.6.1...v5.6.2) (2024-05-23)\n\n\n### Bug Fixes\n\n* prevent url.parse to set `path` option ([#1871](https://github.com/mqttjs/MQTT.js/issues/1871)) ([de0174f](https://github.com/mqttjs/MQTT.js/commit/de0174f033367dde352d1eff339064e704f610e1)), closes [#1870](https://github.com/mqttjs/MQTT.js/issues/1870)\n\n## [5.6.1](https://github.com/mqttjs/MQTT.js/compare/v5.6.0...v5.6.1) (2024-05-17)\n\n\n### Bug Fixes\n\n* update is-browser.ts to account `undefined` navigator ([#1868](https://github.com/mqttjs/MQTT.js/issues/1868)) ([0111a7a](https://github.com/mqttjs/MQTT.js/commit/0111a7af4b71f2a973a712a1f0df6574660d6ec0)), closes [/github.com/mqttjs/MQTT.js/commit/6a03d29b86dc4fe8eae04eaf0f9fc661f1c3d1ea#commitcomment-142114121](https://github.com//github.com/mqttjs/MQTT.js/commit/6a03d29b86dc4fe8eae04eaf0f9fc661f1c3d1ea/issues/commitcomment-142114121) [/github.com/mqttjs/MQTT.js/pull/1868#pullrequestreview-2062507553](https://github.com//github.com/mqttjs/MQTT.js/pull/1868/issues/pullrequestreview-2062507553)\n\n# [5.6.0](https://github.com/mqttjs/MQTT.js/compare/v5.5.6...v5.6.0) (2024-05-13)\n\n\n### Features\n\n* keepalive manager ([#1865](https://github.com/mqttjs/MQTT.js/issues/1865)) ([bad160a](https://github.com/mqttjs/MQTT.js/commit/bad160af2a7b76a5159652e6d3757e7798337261))\n\n## [5.5.6](https://github.com/mqttjs/MQTT.js/compare/v5.5.5...v5.5.6) (2024-05-13)\n\n\n### Bug Fixes\n\n* do not shift pings on 'publish' packets ([#1866](https://github.com/mqttjs/MQTT.js/issues/1866)) ([e4d4663](https://github.com/mqttjs/MQTT.js/commit/e4d4663bcd5f87399b9d7bf101b364cda1c48d0e)), closes [#1863](https://github.com/mqttjs/MQTT.js/issues/1863) [#1861](https://github.com/mqttjs/MQTT.js/issues/1861)\n* **electron:** detect electron context ([#1856](https://github.com/mqttjs/MQTT.js/issues/1856)) ([6a03d29](https://github.com/mqttjs/MQTT.js/commit/6a03d29b86dc4fe8eae04eaf0f9fc661f1c3d1ea))\n* **ws:** ignored `host` option and default hostname in browser ([c6580a6](https://github.com/mqttjs/MQTT.js/commit/c6580a6685821c60a4595986227dba2a615b9958)), closes [#1730](https://github.com/mqttjs/MQTT.js/issues/1730)\n\n## [5.5.5](https://github.com/mqttjs/MQTT.js/compare/v5.5.4...v5.5.5) (2024-04-30)\n\n\n### Bug Fixes\n\n* keepalive issues ([#1855](https://github.com/mqttjs/MQTT.js/issues/1855)) ([4f242f4](https://github.com/mqttjs/MQTT.js/commit/4f242f47bc8568299f04bade8aa4d1d11b939912))\n\n## [5.5.4](https://github.com/mqttjs/MQTT.js/compare/v5.5.3...v5.5.4) (2024-04-26)\n\n\n### Bug Fixes\n\n* allow to use unix sockets in connect ([#1852](https://github.com/mqttjs/MQTT.js/issues/1852)) ([22c97b5](https://github.com/mqttjs/MQTT.js/commit/22c97b5f7536e3e36317c3b28dc0d70557b820ac)), closes [#1040](https://github.com/mqttjs/MQTT.js/issues/1040)\n* **react-native:** process.nextTick is not a function error ([#1849](https://github.com/mqttjs/MQTT.js/issues/1849)) ([f62e207](https://github.com/mqttjs/MQTT.js/commit/f62e207def81b174af83d2e9525cddd1ce960fc3))\n\n## [5.5.3](https://github.com/mqttjs/MQTT.js/compare/v5.5.2...v5.5.3) (2024-04-19)\n\n\n### Bug Fixes\n\n* possible race condition in ping timer ([#1848](https://github.com/mqttjs/MQTT.js/issues/1848)) ([0b7d687](https://github.com/mqttjs/MQTT.js/commit/0b7d687282e6342d5276946dfd4c4d1e0a66ba47)), closes [#1845](https://github.com/mqttjs/MQTT.js/issues/1845)\n* wrong mqttjs version printed ([#1847](https://github.com/mqttjs/MQTT.js/issues/1847)) ([a24cf14](https://github.com/mqttjs/MQTT.js/commit/a24cf14654cb0fa74da1be2671dfaf57071fec40))\n\n## [5.5.2](https://github.com/mqttjs/MQTT.js/compare/v5.5.1...v5.5.2) (2024-04-12)\n\n\n### Bug Fixes\n\n* **react-native:** error Cannot create URL for blob ([#1840](https://github.com/mqttjs/MQTT.js/issues/1840)) ([fc8fafb](https://github.com/mqttjs/MQTT.js/commit/fc8fafbdf5e01edc487192393293b944e77f5920))\n\n## [5.5.1](https://github.com/mqttjs/MQTT.js/compare/v5.5.0...v5.5.1) (2024-04-10)\n\n\n### Bug Fixes\n\n* **browser:** uncaught error when stream is destroyed with error ([380f286](https://github.com/mqttjs/MQTT.js/commit/380f286d46f1c3d7a64c7bd851bbe8d84b797074)), closes [#1839](https://github.com/mqttjs/MQTT.js/issues/1839)\n\n# [5.5.0](https://github.com/mqttjs/MQTT.js/compare/v5.4.0...v5.5.0) (2024-03-18)\n\n\n### Bug Fixes\n\n* **browser:** force closing client doesn't destroy websocket correctly ([#1820](https://github.com/mqttjs/MQTT.js/issues/1820)) ([f9b1204](https://github.com/mqttjs/MQTT.js/commit/f9b1204d7e0a04bb809be6205091fd89281b1e73)), closes [#1817](https://github.com/mqttjs/MQTT.js/issues/1817)\n* expose mqttjs version on `MqttClient.VERSION` ([#1821](https://github.com/mqttjs/MQTT.js/issues/1821)) ([50776a7](https://github.com/mqttjs/MQTT.js/commit/50776a74c73c188f67faf399af90cfe0957a0e1f))\n\n\n### Features\n\n* `timerVariant` option to choose between native and worker timers ([#1818](https://github.com/mqttjs/MQTT.js/issues/1818)) ([547519d](https://github.com/mqttjs/MQTT.js/commit/547519daa8353a2d8a7fe9e4ae715601570b085f))\n\n# [5.4.0](https://github.com/mqttjs/MQTT.js/compare/v5.3.6...v5.4.0) (2024-03-13)\n\n\n### Bug Fixes\n\n* add keepalive test in webworker ([#1807](https://github.com/mqttjs/MQTT.js/issues/1807)) ([8697b06](https://github.com/mqttjs/MQTT.js/commit/8697b06cae3265422620c38b76126381502a9c17))\n* improve some flaky tests ([#1801](https://github.com/mqttjs/MQTT.js/issues/1801)) ([78e8f13](https://github.com/mqttjs/MQTT.js/commit/78e8f139ee0ad61e752421b9e594bea742af9745))\n* print MQTTjs version and environment on constructor ([#1816](https://github.com/mqttjs/MQTT.js/issues/1816)) ([c0a6668](https://github.com/mqttjs/MQTT.js/commit/c0a666887ec313ee82142a825166e5b1d2e668bb))\n* some others flaky tests ([#1808](https://github.com/mqttjs/MQTT.js/issues/1808)) ([f988058](https://github.com/mqttjs/MQTT.js/commit/f9880588244ac35c945302fad474f6c47f27acbc))\n* update worker-timers from 7.0.78 to 7.1.4 ([#1813](https://github.com/mqttjs/MQTT.js/issues/1813)) ([2b75186](https://github.com/mqttjs/MQTT.js/commit/2b751861f2af7b914c3eb84265fb8474428045ec)), closes [#1802](https://github.com/mqttjs/MQTT.js/issues/1802)\n* wrong default export for browser ([#1800](https://github.com/mqttjs/MQTT.js/issues/1800)) ([6237f45](https://github.com/mqttjs/MQTT.js/commit/6237f45f3f455b1b6ae7d339fc8a56a5eff91dc2))\n\n\n### Features\n\n* emit `Keepalive timeout` error and speed up tests using fake timers ([#1798](https://github.com/mqttjs/MQTT.js/issues/1798)) ([5d9bf10](https://github.com/mqttjs/MQTT.js/commit/5d9bf1004ba76098d4ae315fa7a4b44a9d26750b))\n\n## [5.3.6](https://github.com/mqttjs/MQTT.js/compare/v5.3.5...v5.3.6) (2024-02-26)\n\n\n### Bug Fixes\n\n* **browser:** add `navigator` polifilly for wechat mini ([#1796](https://github.com/mqttjs/MQTT.js/issues/1796)) ([c26908a](https://github.com/mqttjs/MQTT.js/commit/c26908a242fa1f573689b03f554bb95d83e61c84)), closes [#1789](https://github.com/mqttjs/MQTT.js/issues/1789)\n* emit `error` event on connack timeout ([#1781](https://github.com/mqttjs/MQTT.js/issues/1781)) ([56e6e23](https://github.com/mqttjs/MQTT.js/commit/56e6e23c0fb775bfd16edf04d6b28f6bbcf05023))\n\n## [5.3.5](https://github.com/mqttjs/MQTT.js/compare/v5.3.4...v5.3.5) (2024-01-23)\n\n\n### Bug Fixes\n\n* bump help-me version to fix vulnerability in glob/inflight ([#1773](https://github.com/mqttjs/MQTT.js/issues/1773)) ([72f99dc](https://github.com/mqttjs/MQTT.js/commit/72f99dcb33b016bced8a2c03ac857c3940ddcda3))\n* keepalive causes a reconnect loop when connection is lost ([#1779](https://github.com/mqttjs/MQTT.js/issues/1779)) ([3da5e84](https://github.com/mqttjs/MQTT.js/commit/3da5e84a158985cbe7bdf60d3a9744b71d98bb56)), closes [#1778](https://github.com/mqttjs/MQTT.js/issues/1778)\n\n## [5.3.4](https://github.com/mqttjs/MQTT.js/compare/v5.3.3...v5.3.4) (2023-12-22)\n\n\n### Bug Fixes\n\n* leaked `close` listener in `startStreamProcess` loop ([#1759](https://github.com/mqttjs/MQTT.js/issues/1759)) ([0c10ef6](https://github.com/mqttjs/MQTT.js/commit/0c10ef680ccc34bbe49948d414f36879d816e4e0))\n* typo in `client.ts` ([#1763](https://github.com/mqttjs/MQTT.js/issues/1763)) ([e3528ac](https://github.com/mqttjs/MQTT.js/commit/e3528ac32d9dc165f8f1238397bd4d02e1990279))\n\n## [5.3.3](https://github.com/mqttjs/MQTT.js/compare/v5.3.2...v5.3.3) (2023-12-05)\n\n\n### Bug Fixes\n\n* don't use worker timers in worker and add web worker tests ([#1755](https://github.com/mqttjs/MQTT.js/issues/1755)) ([38fb6ae](https://github.com/mqttjs/MQTT.js/commit/38fb6ae16073ce31e38dbc1e41a155ad98e04dcc))\n* improve worker tests ([#1757](https://github.com/mqttjs/MQTT.js/issues/1757)) ([4facb18](https://github.com/mqttjs/MQTT.js/commit/4facb18dd9f81bb6af437a6257960e6e878349ad))\n\n## [5.3.2](https://github.com/mqttjs/MQTT.js/compare/v5.3.1...v5.3.2) (2023-12-04)\n\n\n### Bug Fixes\n\n* **browser:** use worker timers to prevent unexpected client close ([#1753](https://github.com/mqttjs/MQTT.js/issues/1753)) ([35448f3](https://github.com/mqttjs/MQTT.js/commit/35448f386687030e7b68bd88f5f4852fbb833c9d))\n* catch all socket errors ([#1752](https://github.com/mqttjs/MQTT.js/issues/1752)) ([a50e85c](https://github.com/mqttjs/MQTT.js/commit/a50e85ccf780621cdf2fd0a0bfcf5575a590f173))\n* prop `window` is not defined in web worker ([#1749](https://github.com/mqttjs/MQTT.js/issues/1749)) ([6591404](https://github.com/mqttjs/MQTT.js/commit/6591404b38c73550157e22f3e57683a634bb919c))\n\n## [5.3.1](https://github.com/mqttjs/MQTT.js/compare/v5.3.0...v5.3.1) (2023-11-28)\n\n\n### Bug Fixes\n\n* improve environment detection in is-browser utility ([#1744](https://github.com/mqttjs/MQTT.js/issues/1744)) ([b094142](https://github.com/mqttjs/MQTT.js/commit/b09414285d5c27cf76a9ff72cbb5ffe8ecec3981))\n* typescript compile error ([2655feb](https://github.com/mqttjs/MQTT.js/commit/2655feb7a182c53bfa5ea7321b4e1a6d5b031311)), closes [#1746](https://github.com/mqttjs/MQTT.js/issues/1746)\n\n# [5.3.0](https://github.com/mqttjs/MQTT.js/compare/v5.2.2...v5.3.0) (2023-11-18)\n\n\n### Features\n\n* **browser:** websockets improvements and bundle optimizations ([#1732](https://github.com/mqttjs/MQTT.js/issues/1732)) ([0928f85](https://github.com/mqttjs/MQTT.js/commit/0928f8575a7b4c717fbd960c802e1dc41b436d0e))\n\n## [5.2.2](https://github.com/mqttjs/MQTT.js/compare/v5.2.1...v5.2.2) (2023-11-14)\n\n\n### Bug Fixes\n\n* add default export ([#1740](https://github.com/mqttjs/MQTT.js/issues/1740)) ([fdb498f](https://github.com/mqttjs/MQTT.js/commit/fdb498fe7ebbdf2be0d1fbcb897f093d4fa40d05))\n\n## [5.2.1](https://github.com/mqttjs/MQTT.js/compare/v5.2.0...v5.2.1) (2023-11-10)\n\n\n### Bug Fixes\n\n* make `import mqtt from 'mqtt'` work in browsers ([#1734](https://github.com/mqttjs/MQTT.js/issues/1734)) ([80e29a9](https://github.com/mqttjs/MQTT.js/commit/80e29a9dc6bb6ad51a7ac968361a550bc1da68cb))\n\n# [5.2.0](https://github.com/mqttjs/MQTT.js/compare/v5.1.4...v5.2.0) (2023-11-09)\n\n\n### Features\n\n* esm version `dist/mqtt.esm.js` and replace `browserify` with `esbuild` ([#1731](https://github.com/mqttjs/MQTT.js/issues/1731)) ([3d6c3be](https://github.com/mqttjs/MQTT.js/commit/3d6c3be60eae8416dbfea1d15a826c0b5fc52c45))\n\n## [5.1.4](https://github.com/mqttjs/MQTT.js/compare/v5.1.3...v5.1.4) (2023-10-30)\n\n\n### Bug Fixes\n\n* crash with React Native ([#1724](https://github.com/mqttjs/MQTT.js/issues/1724)) ([f6123f2](https://github.com/mqttjs/MQTT.js/commit/f6123f22a11a4eb4c34c874b47056cea7ef264a6))\n* unambiguously detect web workers ([#1728](https://github.com/mqttjs/MQTT.js/issues/1728)) ([e44368c](https://github.com/mqttjs/MQTT.js/commit/e44368c0d7541d005ad668d5d44d080e29ca5778))\n\n## [5.1.3](https://github.com/mqttjs/MQTT.js/compare/v5.1.2...v5.1.3) (2023-10-20)\n\n\n### Bug Fixes\n\n* add all `EventListener` methods to `TypedEventEmitter` interface ([#1718](https://github.com/mqttjs/MQTT.js/issues/1718)) ([b96882a](https://github.com/mqttjs/MQTT.js/commit/b96882a7e5ff2869badbbd34c9b2e1ac51c25d2a))\n\n## [5.1.2](https://github.com/mqttjs/MQTT.js/compare/v5.1.1...v5.1.2) (2023-10-10)\n\n\n### Bug Fixes\n\n* detect web worker ([#1711](https://github.com/mqttjs/MQTT.js/issues/1711)) ([a75a467](https://github.com/mqttjs/MQTT.js/commit/a75a467e3524aef1d6038ed4ed14ab0407c146cb))\n\n## [5.1.1](https://github.com/mqttjs/MQTT.js/compare/v5.1.0...v5.1.1) (2023-10-09)\n\n\n### Bug Fixes\n\n* restore nodejs 16 compatibility ([a347c0d](https://github.com/mqttjs/MQTT.js/commit/a347c0d81ff800c1469d8497542a8c5973b59e33)), closes [#1710](https://github.com/mqttjs/MQTT.js/issues/1710)\n\n# [5.1.0](https://github.com/mqttjs/MQTT.js/compare/v5.0.5...v5.1.0) (2023-10-04)\n\n\n### Bug Fixes\n\n* **types:** import type error ([#1705](https://github.com/mqttjs/MQTT.js/issues/1705)) ([0960b68](https://github.com/mqttjs/MQTT.js/commit/0960b68f9b612640318931e971d7a715f0945bdd))\n\n\n### Features\n\n* custom websocket support ([#1696](https://github.com/mqttjs/MQTT.js/issues/1696)) ([d6fd3a8](https://github.com/mqttjs/MQTT.js/commit/d6fd3a8316642a17ff1e90b4d6c9d4656c3831e5))\n\n## [5.0.5](https://github.com/mqttjs/MQTT.js/compare/v5.0.4...v5.0.5) (2023-09-08)\n\n\n### Bug Fixes\n\n* publish/subscribe/unsubscribe types and missing types exports ([#1688](https://github.com/mqttjs/MQTT.js/issues/1688)) ([2df6af7](https://github.com/mqttjs/MQTT.js/commit/2df6af717a7458eff1bf69be026734c973ade0a6))\n\n## [5.0.4](https://github.com/mqttjs/MQTT.js/compare/v5.0.3...v5.0.4) (2023-08-31)\n\n\n### Bug Fixes\n\n* export js file in dist folder ([#1596](https://github.com/mqttjs/MQTT.js/issues/1596)) ([#1677](https://github.com/mqttjs/MQTT.js/issues/1677)) ([cbe0dc6](https://github.com/mqttjs/MQTT.js/commit/cbe0dc6be52bb3a5a9fa1f5b390973bf57f9da47))\n* move exported types out of dev dependencies ([#1676](https://github.com/mqttjs/MQTT.js/issues/1676)) ([844e4ff](https://github.com/mqttjs/MQTT.js/commit/844e4ff6a75911e0d5f5fad75341ffc04eed1b15))\n* set default value false for reconnecting in constructor ([#1674](https://github.com/mqttjs/MQTT.js/issues/1674)) ([#1678](https://github.com/mqttjs/MQTT.js/issues/1678)) ([312b57b](https://github.com/mqttjs/MQTT.js/commit/312b57ba982209d874d65a0857a019991a2f9b0d))\n\n## [5.0.3](https://github.com/mqttjs/MQTT.js/compare/v5.0.2...v5.0.3) (2023-08-16)\n\n\n### Bug Fixes\n\n* browser detection ([183b35a](https://github.com/mqttjs/MQTT.js/commit/183b35aa3ed98fbbcbea6805994ef7c3cc8ee616)), closes [#1671](https://github.com/mqttjs/MQTT.js/issues/1671)\n* close and end callbacks not executed in the WeChat mini program ([#1664](https://github.com/mqttjs/MQTT.js/issues/1664)) ([15ff607](https://github.com/mqttjs/MQTT.js/commit/15ff607f4c938d0e7a23c99413db7496cae12e48))\n\n## [5.0.2](https://github.com/mqttjs/MQTT.js/compare/v5.0.1...v5.0.2) (2023-08-03)\n\n\n### Bug Fixes\n\n* **cli:** cli commands not working ([#1660](https://github.com/mqttjs/MQTT.js/issues/1660)) ([1bea132](https://github.com/mqttjs/MQTT.js/commit/1bea132e97eeeb7187525dcf7417761388919075))\n* import mqtt correctly in test ([8f15557](https://github.com/mqttjs/MQTT.js/commit/8f15557d0c4e455f91c96df5793d32451f1601d3))\n* **tests:** abstract store test types ([0ddd097](https://github.com/mqttjs/MQTT.js/commit/0ddd0976bb8dd7dc1d434e8bb954d440ba653fb2))\n\n## [5.0.1](https://github.com/mqttjs/MQTT.js/compare/v5.0.0...v5.0.1) (2023-07-31)\n\n\n### Bug Fixes\n\n* resubscribe when no session present ([#895](https://github.com/mqttjs/MQTT.js/issues/895)) ([#1650](https://github.com/mqttjs/MQTT.js/issues/1650)) ([37acda6](https://github.com/mqttjs/MQTT.js/commit/37acda655e202025373311624e19589ae7ef5970))\n* **types:** wrong `incomingStore` and `outgoingStore` ([8133eba](https://github.com/mqttjs/MQTT.js/commit/8133eba152e81ed77e6aa18eb2cc351c3c901aa8))\n\n# [5.0.0](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0) (2023-07-25)\n\n\n### Bug Fixes\n\n* help message for client ID param for sub command is incorrect ([#1643](https://github.com/mqttjs/MQTT.js/issues/1643)) ([8521888](https://github.com/mqttjs/MQTT.js/commit/85218884728da85b626de6af0ac0bc9c26045f43))\n* **types:** better streamBuilder types ([247e187](https://github.com/mqttjs/MQTT.js/commit/247e187b22e4ae916d1d89013e617b19688914dc))\n* **types:** better types ([#1645](https://github.com/mqttjs/MQTT.js/issues/1645)) ([0f29bff](https://github.com/mqttjs/MQTT.js/commit/0f29bffb7e9088a1388139dcae04bb8731debc38))\n* use explicit `connect` packet and infer types from `mqtt-packet` ([#1646](https://github.com/mqttjs/MQTT.js/issues/1646)) ([2a49ed3](https://github.com/mqttjs/MQTT.js/commit/2a49ed324e330deb5ca2ba8044b9196fc411ab8a))\n\n\n### Features\n\n* promises support ([#1644](https://github.com/mqttjs/MQTT.js/issues/1644)) ([d02e176](https://github.com/mqttjs/MQTT.js/commit/d02e17697f351b5fc2ed6d2cf689cbe40b829b9d))\n\n\n\n# [5.0.0-beta.4](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0) (2023-07-21)\n\n\n\n# [5.0.0-beta.3](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0) (2023-07-19)\n\n\n### Bug Fixes\n\n* make tests more reliable ([#1534](https://github.com/mqttjs/MQTT.js/issues/1534)) ([1076143](https://github.com/mqttjs/MQTT.js/commit/1076143a7ed6b07b91ded9985cc9a0bbb5a84da4))\n* problem with publish callback invoked twice ([#1635](https://github.com/mqttjs/MQTT.js/issues/1635)) ([79b23a8](https://github.com/mqttjs/MQTT.js/commit/79b23a8f76abaceec67f063b6da0ee57a2c60697))\n* **types:** subscribe definition ([#1527](https://github.com/mqttjs/MQTT.js/issues/1527)) ([debb7d9](https://github.com/mqttjs/MQTT.js/commit/debb7d93c17f5b68704c160ccd88e7e1db87d92d))\n\n\n* chore!: remove unused deps, convert to ES2015 class (#1633) ([d71b000](https://github.com/mqttjs/MQTT.js/commit/d71b000773e4954c9a2ecbf4f750dac58017ef1a)), closes [#1633](https://github.com/mqttjs/MQTT.js/issues/1633)\n\n\n### BREAKING CHANGES\n\n* when creating an `MqttClient` instance `new` is now required\n\n\n\n# [5.0.0-beta.2](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0) (2023-07-03)\n\n\n### Bug Fixes\n\n* browser tests not working ([#1628](https://github.com/mqttjs/MQTT.js/issues/1628)) ([8775fcd](https://github.com/mqttjs/MQTT.js/commit/8775fcdad952b39fa4b79dbe912ca42033be030a))\n* setImmediate polyfill ([#1626](https://github.com/mqttjs/MQTT.js/issues/1626)) ([0ed0754](https://github.com/mqttjs/MQTT.js/commit/0ed0754b95b92df51ed49ae63058b31fdba1d415))\n\n\n### Features\n\n* option to disable `writeCache` and fix leak in subscriptions ([#1622](https://github.com/mqttjs/MQTT.js/issues/1622)) ([c8aa654](https://github.com/mqttjs/MQTT.js/commit/c8aa6540dbf68ffb0d88c287e2c862b28d3fb6e6)), closes [#1535](https://github.com/mqttjs/MQTT.js/issues/1535) [#1151](https://github.com/mqttjs/MQTT.js/issues/1151)\n\n\n\n# [5.0.0-beta.1](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0) (2023-06-29)\n\n\n### Bug Fixes\n\n* `_storeProcessing` staying true after outStore got emptied ([#1492](https://github.com/mqttjs/MQTT.js/issues/1492)) ([f3f7be7](https://github.com/mqttjs/MQTT.js/commit/f3f7be76199115a622fde2590d44b1bb0cf57d41))\n* consistency, used `this` instead of `that` ([#1618](https://github.com/mqttjs/MQTT.js/issues/1618)) ([800825b](https://github.com/mqttjs/MQTT.js/commit/800825bf619d83ef713a5b2fa1533bbf6ccac872))\n* prevent store message on store when it's restored ([#1255](https://github.com/mqttjs/MQTT.js/issues/1255)) ([8d68c8c](https://github.com/mqttjs/MQTT.js/commit/8d68c8c3e38aede52741a06838933011a6fccc43))\n\n\n\n# [5.0.0-beta.0](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0) (2023-06-27)\n\n\n### Bug Fixes\n\n* add missing export of UniqueMessageIdProvider and DefaultMessageIdProvider ([#1572](https://github.com/mqttjs/MQTT.js/issues/1572)) ([aa2e0ad](https://github.com/mqttjs/MQTT.js/commit/aa2e0ad49aadf333141f18cb85d2582abb8e19fc))\n* IS_BROWSER check is now safer and more agnostic about the bundler ([#1571](https://github.com/mqttjs/MQTT.js/issues/1571)) ([b48b4b4](https://github.com/mqttjs/MQTT.js/commit/b48b4b4e79690c96033ea2df387c11f3bc26bf6a))\n* **test:** `topicAliasMaximum` tests ([#1612](https://github.com/mqttjs/MQTT.js/issues/1612)) ([f1e5518](https://github.com/mqttjs/MQTT.js/commit/f1e5518150ea45067b87104abd9fed64ec13a48c))\n* topicAliasMaximum under must be under Connect properties ([#1519](https://github.com/mqttjs/MQTT.js/issues/1519)) ([3b2e1cb](https://github.com/mqttjs/MQTT.js/commit/3b2e1cb7c4bf33ff66bcd1cc3091790a9635f19a))\n* **types:** missing null declaration for error in subscription callback ([#1589](https://github.com/mqttjs/MQTT.js/issues/1589)) ([afc067b](https://github.com/mqttjs/MQTT.js/commit/afc067be2ca83990209b6176adec06f9a4c76a2c))\n* **types:** topic alias controls and password ([#1509](https://github.com/mqttjs/MQTT.js/issues/1509)) ([85c9341](https://github.com/mqttjs/MQTT.js/commit/85c9341bba2676cfd069ec38a1a7cfda71647b68))\n\n\n* chore!: drop support for node 12-14 (#1615) ([a2cbf61](https://github.com/mqttjs/MQTT.js/commit/a2cbf61c2a051a5ee69a50e00688e8ace79e7ef5)), closes [#1615](https://github.com/mqttjs/MQTT.js/issues/1615)\n\n\n### BREAKING CHANGES\n\n* Dropped support for NodeJS 12-14\n\n# [5.0.0-beta.4](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0-beta.4) (2023-07-21)\n\n\n\n# [5.0.0-beta.3](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0-beta.4) (2023-07-19)\n\n\n### Bug Fixes\n\n* make tests more reliable ([#1534](https://github.com/mqttjs/MQTT.js/issues/1534)) ([1076143](https://github.com/mqttjs/MQTT.js/commit/1076143a7ed6b07b91ded9985cc9a0bbb5a84da4))\n* problem with publish callback invoked twice ([#1635](https://github.com/mqttjs/MQTT.js/issues/1635)) ([79b23a8](https://github.com/mqttjs/MQTT.js/commit/79b23a8f76abaceec67f063b6da0ee57a2c60697))\n* **types:** subscribe definition ([#1527](https://github.com/mqttjs/MQTT.js/issues/1527)) ([debb7d9](https://github.com/mqttjs/MQTT.js/commit/debb7d93c17f5b68704c160ccd88e7e1db87d92d))\n\n\n* chore!: remove unused deps, convert to ES2015 class (#1633) ([d71b000](https://github.com/mqttjs/MQTT.js/commit/d71b000773e4954c9a2ecbf4f750dac58017ef1a)), closes [#1633](https://github.com/mqttjs/MQTT.js/issues/1633)\n\n\n### BREAKING CHANGES\n\n* when creating an `MqttClient` instance `new` is now required\n\n\n\n# [5.0.0-beta.2](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0-beta.4) (2023-07-03)\n\n\n### Bug Fixes\n\n* browser tests not working ([#1628](https://github.com/mqttjs/MQTT.js/issues/1628)) ([8775fcd](https://github.com/mqttjs/MQTT.js/commit/8775fcdad952b39fa4b79dbe912ca42033be030a))\n* setImmediate polyfill ([#1626](https://github.com/mqttjs/MQTT.js/issues/1626)) ([0ed0754](https://github.com/mqttjs/MQTT.js/commit/0ed0754b95b92df51ed49ae63058b31fdba1d415))\n\n\n### Features\n\n* option to disable `writeCache` and fix leak in subscriptions ([#1622](https://github.com/mqttjs/MQTT.js/issues/1622)) ([c8aa654](https://github.com/mqttjs/MQTT.js/commit/c8aa6540dbf68ffb0d88c287e2c862b28d3fb6e6)), closes [#1535](https://github.com/mqttjs/MQTT.js/issues/1535) [#1151](https://github.com/mqttjs/MQTT.js/issues/1151)\n\n\n\n# [5.0.0-beta.1](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0-beta.4) (2023-06-29)\n\n\n### Bug Fixes\n\n* `_storeProcessing` staying true after outStore got emptied ([#1492](https://github.com/mqttjs/MQTT.js/issues/1492)) ([f3f7be7](https://github.com/mqttjs/MQTT.js/commit/f3f7be76199115a622fde2590d44b1bb0cf57d41))\n* consistency, used `this` instead of `that` ([#1618](https://github.com/mqttjs/MQTT.js/issues/1618)) ([800825b](https://github.com/mqttjs/MQTT.js/commit/800825bf619d83ef713a5b2fa1533bbf6ccac872))\n* prevent store message on store when it's restored ([#1255](https://github.com/mqttjs/MQTT.js/issues/1255)) ([8d68c8c](https://github.com/mqttjs/MQTT.js/commit/8d68c8c3e38aede52741a06838933011a6fccc43))\n\n\n\n# [5.0.0-beta.0](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0-beta.4) (2023-06-27)\n\n\n### Bug Fixes\n\n* add missing export of UniqueMessageIdProvider and DefaultMessageIdProvider ([#1572](https://github.com/mqttjs/MQTT.js/issues/1572)) ([aa2e0ad](https://github.com/mqttjs/MQTT.js/commit/aa2e0ad49aadf333141f18cb85d2582abb8e19fc))\n* IS_BROWSER check is now safer and more agnostic about the bundler ([#1571](https://github.com/mqttjs/MQTT.js/issues/1571)) ([b48b4b4](https://github.com/mqttjs/MQTT.js/commit/b48b4b4e79690c96033ea2df387c11f3bc26bf6a))\n* **test:** `topicAliasMaximum` tests ([#1612](https://github.com/mqttjs/MQTT.js/issues/1612)) ([f1e5518](https://github.com/mqttjs/MQTT.js/commit/f1e5518150ea45067b87104abd9fed64ec13a48c))\n* topicAliasMaximum under must be under Connect properties ([#1519](https://github.com/mqttjs/MQTT.js/issues/1519)) ([3b2e1cb](https://github.com/mqttjs/MQTT.js/commit/3b2e1cb7c4bf33ff66bcd1cc3091790a9635f19a))\n* **types:** missing null declaration for error in subscription callback ([#1589](https://github.com/mqttjs/MQTT.js/issues/1589)) ([afc067b](https://github.com/mqttjs/MQTT.js/commit/afc067be2ca83990209b6176adec06f9a4c76a2c))\n* **types:** topic alias controls and password ([#1509](https://github.com/mqttjs/MQTT.js/issues/1509)) ([85c9341](https://github.com/mqttjs/MQTT.js/commit/85c9341bba2676cfd069ec38a1a7cfda71647b68))\n\n\n* chore!: drop support for node 12-14 (#1615) ([a2cbf61](https://github.com/mqttjs/MQTT.js/commit/a2cbf61c2a051a5ee69a50e00688e8ace79e7ef5)), closes [#1615](https://github.com/mqttjs/MQTT.js/issues/1615)\n\n\n### BREAKING CHANGES\n\n* Dropped support for NodeJS 12-14\n\n# [5.0.0-beta.3](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0-beta.3) (2023-07-19)\n\n\n### Bug Fixes\n\n* make tests more reliable ([#1534](https://github.com/mqttjs/MQTT.js/issues/1534)) ([1076143](https://github.com/mqttjs/MQTT.js/commit/1076143a7ed6b07b91ded9985cc9a0bbb5a84da4))\n* problem with publish callback invoked twice ([#1635](https://github.com/mqttjs/MQTT.js/issues/1635)) ([79b23a8](https://github.com/mqttjs/MQTT.js/commit/79b23a8f76abaceec67f063b6da0ee57a2c60697))\n* **types:** subscribe definition ([#1527](https://github.com/mqttjs/MQTT.js/issues/1527)) ([debb7d9](https://github.com/mqttjs/MQTT.js/commit/debb7d93c17f5b68704c160ccd88e7e1db87d92d))\n\n\n* chore!: remove unused deps, convert to ES2015 class (#1633) ([d71b000](https://github.com/mqttjs/MQTT.js/commit/d71b000773e4954c9a2ecbf4f750dac58017ef1a)), closes [#1633](https://github.com/mqttjs/MQTT.js/issues/1633)\n\n\n### BREAKING CHANGES\n\n* when creating an `MqttClient` instance `new` is now required\n\n\n\n# [5.0.0-beta.2](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0-beta.3) (2023-07-03)\n\n\n### Bug Fixes\n\n* browser tests not working ([#1628](https://github.com/mqttjs/MQTT.js/issues/1628)) ([8775fcd](https://github.com/mqttjs/MQTT.js/commit/8775fcdad952b39fa4b79dbe912ca42033be030a))\n* setImmediate polyfill ([#1626](https://github.com/mqttjs/MQTT.js/issues/1626)) ([0ed0754](https://github.com/mqttjs/MQTT.js/commit/0ed0754b95b92df51ed49ae63058b31fdba1d415))\n\n\n### Features\n\n* option to disable `writeCache` and fix leak in subscriptions ([#1622](https://github.com/mqttjs/MQTT.js/issues/1622)) ([c8aa654](https://github.com/mqttjs/MQTT.js/commit/c8aa6540dbf68ffb0d88c287e2c862b28d3fb6e6)), closes [#1535](https://github.com/mqttjs/MQTT.js/issues/1535) [#1151](https://github.com/mqttjs/MQTT.js/issues/1151)\n\n\n\n# [5.0.0-beta.1](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0-beta.3) (2023-06-29)\n\n\n### Bug Fixes\n\n* `_storeProcessing` staying true after outStore got emptied ([#1492](https://github.com/mqttjs/MQTT.js/issues/1492)) ([f3f7be7](https://github.com/mqttjs/MQTT.js/commit/f3f7be76199115a622fde2590d44b1bb0cf57d41))\n* consistency, used `this` instead of `that` ([#1618](https://github.com/mqttjs/MQTT.js/issues/1618)) ([800825b](https://github.com/mqttjs/MQTT.js/commit/800825bf619d83ef713a5b2fa1533bbf6ccac872))\n* prevent store message on store when it's restored ([#1255](https://github.com/mqttjs/MQTT.js/issues/1255)) ([8d68c8c](https://github.com/mqttjs/MQTT.js/commit/8d68c8c3e38aede52741a06838933011a6fccc43))\n\n\n\n# [5.0.0-beta.0](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0-beta.3) (2023-06-27)\n\n\n### Bug Fixes\n\n* add missing export of UniqueMessageIdProvider and DefaultMessageIdProvider ([#1572](https://github.com/mqttjs/MQTT.js/issues/1572)) ([aa2e0ad](https://github.com/mqttjs/MQTT.js/commit/aa2e0ad49aadf333141f18cb85d2582abb8e19fc))\n* IS_BROWSER check is now safer and more agnostic about the bundler ([#1571](https://github.com/mqttjs/MQTT.js/issues/1571)) ([b48b4b4](https://github.com/mqttjs/MQTT.js/commit/b48b4b4e79690c96033ea2df387c11f3bc26bf6a))\n* **test:** `topicAliasMaximum` tests ([#1612](https://github.com/mqttjs/MQTT.js/issues/1612)) ([f1e5518](https://github.com/mqttjs/MQTT.js/commit/f1e5518150ea45067b87104abd9fed64ec13a48c))\n* topicAliasMaximum under must be under Connect properties ([#1519](https://github.com/mqttjs/MQTT.js/issues/1519)) ([3b2e1cb](https://github.com/mqttjs/MQTT.js/commit/3b2e1cb7c4bf33ff66bcd1cc3091790a9635f19a))\n* **types:** missing null declaration for error in subscription callback ([#1589](https://github.com/mqttjs/MQTT.js/issues/1589)) ([afc067b](https://github.com/mqttjs/MQTT.js/commit/afc067be2ca83990209b6176adec06f9a4c76a2c))\n* **types:** topic alias controls and password ([#1509](https://github.com/mqttjs/MQTT.js/issues/1509)) ([85c9341](https://github.com/mqttjs/MQTT.js/commit/85c9341bba2676cfd069ec38a1a7cfda71647b68))\n\n\n* chore!: drop support for node 12-14 (#1615) ([a2cbf61](https://github.com/mqttjs/MQTT.js/commit/a2cbf61c2a051a5ee69a50e00688e8ace79e7ef5)), closes [#1615](https://github.com/mqttjs/MQTT.js/issues/1615)\n\n\n### BREAKING CHANGES\n\n* Dropped support for NodeJS 12-14\n\n# [5.0.0-beta.2](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0-beta.2) (2023-07-03)\n\n\n### Bug Fixes\n\n* browser tests not working ([#1628](https://github.com/mqttjs/MQTT.js/issues/1628)) ([8775fcd](https://github.com/mqttjs/MQTT.js/commit/8775fcdad952b39fa4b79dbe912ca42033be030a))\n* setImmediate polyfill ([#1626](https://github.com/mqttjs/MQTT.js/issues/1626)) ([0ed0754](https://github.com/mqttjs/MQTT.js/commit/0ed0754b95b92df51ed49ae63058b31fdba1d415))\n\n\n### Features\n\n* option to disable `writeCache` and fix leak in subscriptions ([#1622](https://github.com/mqttjs/MQTT.js/issues/1622)) ([c8aa654](https://github.com/mqttjs/MQTT.js/commit/c8aa6540dbf68ffb0d88c287e2c862b28d3fb6e6)), closes [#1535](https://github.com/mqttjs/MQTT.js/issues/1535) [#1151](https://github.com/mqttjs/MQTT.js/issues/1151)\n\n\n\n# [5.0.0-beta.1](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0-beta.2) (2023-06-29)\n\n\n### Bug Fixes\n\n* `_storeProcessing` staying true after outStore got emptied ([#1492](https://github.com/mqttjs/MQTT.js/issues/1492)) ([f3f7be7](https://github.com/mqttjs/MQTT.js/commit/f3f7be76199115a622fde2590d44b1bb0cf57d41))\n* consistency, used `this` instead of `that` ([#1618](https://github.com/mqttjs/MQTT.js/issues/1618)) ([800825b](https://github.com/mqttjs/MQTT.js/commit/800825bf619d83ef713a5b2fa1533bbf6ccac872))\n* prevent store message on store when it's restored ([#1255](https://github.com/mqttjs/MQTT.js/issues/1255)) ([8d68c8c](https://github.com/mqttjs/MQTT.js/commit/8d68c8c3e38aede52741a06838933011a6fccc43))\n\n\n\n# [5.0.0-beta.0](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0-beta.2) (2023-06-27)\n\n\n### Bug Fixes\n\n* add missing export of UniqueMessageIdProvider and DefaultMessageIdProvider ([#1572](https://github.com/mqttjs/MQTT.js/issues/1572)) ([aa2e0ad](https://github.com/mqttjs/MQTT.js/commit/aa2e0ad49aadf333141f18cb85d2582abb8e19fc))\n* IS_BROWSER check is now safer and more agnostic about the bundler ([#1571](https://github.com/mqttjs/MQTT.js/issues/1571)) ([b48b4b4](https://github.com/mqttjs/MQTT.js/commit/b48b4b4e79690c96033ea2df387c11f3bc26bf6a))\n* **test:** `topicAliasMaximum` tests ([#1612](https://github.com/mqttjs/MQTT.js/issues/1612)) ([f1e5518](https://github.com/mqttjs/MQTT.js/commit/f1e5518150ea45067b87104abd9fed64ec13a48c))\n* topicAliasMaximum under must be under Connect properties ([#1519](https://github.com/mqttjs/MQTT.js/issues/1519)) ([3b2e1cb](https://github.com/mqttjs/MQTT.js/commit/3b2e1cb7c4bf33ff66bcd1cc3091790a9635f19a))\n* **types:** missing null declaration for error in subscription callback ([#1589](https://github.com/mqttjs/MQTT.js/issues/1589)) ([afc067b](https://github.com/mqttjs/MQTT.js/commit/afc067be2ca83990209b6176adec06f9a4c76a2c))\n* **types:** topic alias controls and password ([#1509](https://github.com/mqttjs/MQTT.js/issues/1509)) ([85c9341](https://github.com/mqttjs/MQTT.js/commit/85c9341bba2676cfd069ec38a1a7cfda71647b68))\n\n\n* chore!: drop support for node 12-14 (#1615) ([a2cbf61](https://github.com/mqttjs/MQTT.js/commit/a2cbf61c2a051a5ee69a50e00688e8ace79e7ef5)), closes [#1615](https://github.com/mqttjs/MQTT.js/issues/1615)\n\n\n### BREAKING CHANGES\n\n* Dropped support for NodeJS 12-14\n\n# [5.0.0-beta.1](https://github.com/mqttjs/MQTT.js/compare/v5.0.0-beta.0...v5.0.0-beta.1) (2023-06-29)\n\n\n### Bug Fixes\n\n* `_storeProcessing` staying true after outStore got emptied ([#1492](https://github.com/mqttjs/MQTT.js/issues/1492)) ([f3f7be7](https://github.com/mqttjs/MQTT.js/commit/f3f7be76199115a622fde2590d44b1bb0cf57d41))\n* consistency, used `this` instead of `that` ([#1618](https://github.com/mqttjs/MQTT.js/issues/1618)) ([800825b](https://github.com/mqttjs/MQTT.js/commit/800825bf619d83ef713a5b2fa1533bbf6ccac872))\n* prevent store message on store when it's restored ([#1255](https://github.com/mqttjs/MQTT.js/issues/1255)) ([8d68c8c](https://github.com/mqttjs/MQTT.js/commit/8d68c8c3e38aede52741a06838933011a6fccc43))\n\n\n\n# [5.0.0-beta.0](https://github.com/mqttjs/MQTT.js/compare/v4.3.7...v5.0.0-beta.0) (2023-06-27)\n\n\n### Bug Fixes\n\n* add missing export of UniqueMessageIdProvider and DefaultMessageIdProvider ([#1572](https://github.com/mqttjs/MQTT.js/issues/1572)) ([aa2e0ad](https://github.com/mqttjs/MQTT.js/commit/aa2e0ad49aadf333141f18cb85d2582abb8e19fc))\n* IS_BROWSER check is now safer and more agnostic about the bundler ([#1571](https://github.com/mqttjs/MQTT.js/issues/1571)) ([b48b4b4](https://github.com/mqttjs/MQTT.js/commit/b48b4b4e79690c96033ea2df387c11f3bc26bf6a))\n* **test:** `topicAliasMaximum` tests ([#1612](https://github.com/mqttjs/MQTT.js/issues/1612)) ([f1e5518](https://github.com/mqttjs/MQTT.js/commit/f1e5518150ea45067b87104abd9fed64ec13a48c))\n* topicAliasMaximum under must be under Connect properties ([#1519](https://github.com/mqttjs/MQTT.js/issues/1519)) ([3b2e1cb](https://github.com/mqttjs/MQTT.js/commit/3b2e1cb7c4bf33ff66bcd1cc3091790a9635f19a))\n* **types:** missing null declaration for error in subscription callback ([#1589](https://github.com/mqttjs/MQTT.js/issues/1589)) ([afc067b](https://github.com/mqttjs/MQTT.js/commit/afc067be2ca83990209b6176adec06f9a4c76a2c))\n* **types:** topic alias controls and password ([#1509](https://github.com/mqttjs/MQTT.js/issues/1509)) ([85c9341](https://github.com/mqttjs/MQTT.js/commit/85c9341bba2676cfd069ec38a1a7cfda71647b68))\n\n\n* chore!: drop support for node 12-14 (#1615) ([a2cbf61](https://github.com/mqttjs/MQTT.js/commit/a2cbf61c2a051a5ee69a50e00688e8ace79e7ef5)), closes [#1615](https://github.com/mqttjs/MQTT.js/issues/1615)\n\n\n### BREAKING CHANGES\n\n* Dropped support for NodeJS 12-14\n\n\n\n## [4.3.7](https://github.com/mqttjs/MQTT.js/compare/v4.3.6...v4.3.7) (2022-03-14)\n\n\n### Bug Fixes\n\n* fix regression from [#1401](https://github.com/mqttjs/MQTT.js/issues/1401) and allow CI test failures to break gitthub workflow ([#1443](https://github.com/mqttjs/MQTT.js/issues/1443)) ([accd78e](https://github.com/mqttjs/MQTT.js/commit/accd78e38aa82c8cc1ea04029e56494276776c87))\n\n\n\n## [4.3.6](https://github.com/mqttjs/MQTT.js/compare/v4.3.5...v4.3.6) (2022-02-17)\n\n\n### Bug Fixes\n\n* buffer is not defined in browser ([#1420](https://github.com/mqttjs/MQTT.js/issues/1420)) ([f5ab1b5](https://github.com/mqttjs/MQTT.js/commit/f5ab1b5d2a04813178fb478a7e345c0acf544258))\n* **types:** connect function proper overloads for its parameters ([#1416](https://github.com/mqttjs/MQTT.js/issues/1416)) ([28c4040](https://github.com/mqttjs/MQTT.js/commit/28c4040c21246710f7ea3e161bc4145ba916c0de))\n\n\n\n## [4.3.5](https://github.com/mqttjs/MQTT.js/compare/v4.3.4...v4.3.5) (2022-02-07)\n\n\n### Bug Fixes\n\n* **sendPacket:** drain leak ([#1401](https://github.com/mqttjs/MQTT.js/issues/1401)) ([7ec4b8f](https://github.com/mqttjs/MQTT.js/commit/7ec4b8fd602e220f50693cb83f082dab764ed3f2))\n\n\n\n## [4.3.4](https://github.com/mqttjs/MQTT.js/compare/v4.3.3...v4.3.4) (2022-01-06)\n\n\n### Bug Fixes\n\n* migrate LruMap from collections to lru-cache. ([#1396](https://github.com/mqttjs/MQTT.js/issues/1396)) ([5c67037](https://github.com/mqttjs/MQTT.js/commit/5c670370c603f09ee25fbaba961156f59eaee1a2))\n\n\n\n## [4.3.3](https://github.com/mqttjs/MQTT.js/compare/v4.3.2...v4.3.3) (2022-01-05)\n\n\n### Bug Fixes\n\n* remove collections.js dependency from number-allocator. ([#1394](https://github.com/mqttjs/MQTT.js/issues/1394)) ([ee75c32](https://github.com/mqttjs/MQTT.js/commit/ee75c322d6f31a3279f5a7f15ee4122760b1cc94)), closes [#1392](https://github.com/mqttjs/MQTT.js/issues/1392)\n\n\n\n## [4.3.2](https://github.com/mqttjs/MQTT.js/compare/v4.3.1...v4.3.2) (2021-12-29)\n\n\n### Bug Fixes\n\n* **dependency:** Updated collections.js related package version. ([#1386](https://github.com/mqttjs/MQTT.js/issues/1386)) ([df89a2e](https://github.com/mqttjs/MQTT.js/commit/df89a2edf4fa15d3f8d56cd0b8290f9ddde7ceb8))\n\n\n\n## [4.3.1](https://github.com/mqttjs/MQTT.js/compare/v4.3.0...v4.3.1) (2021-12-24)\n\n\n### Bug Fixes\n\n* **dependencies:** remove babel-eslint and update snazzy ([#1383](https://github.com/mqttjs/MQTT.js/issues/1383)) ([66d43d4](https://github.com/mqttjs/MQTT.js/commit/66d43d4f33e6af405468c94112f3d1361af773dc))\n\n\n\n# [4.3.0](https://github.com/mqttjs/MQTT.js/compare/v4.2.8...v4.3.0) (2021-12-22)\n\n\n### Bug Fixes\n\n* **client:** Refined Topic Alias support. (Implement [#1300](https://github.com/mqttjs/MQTT.js/issues/1300)) ([#1301](https://github.com/mqttjs/MQTT.js/issues/1301)) ([c92b877](https://github.com/mqttjs/MQTT.js/commit/c92b877292d314e3e0b5d8f84b7f4b68a266aba2))\n* **README:** typo Support ([#1353](https://github.com/mqttjs/MQTT.js/issues/1353)) ([c424426](https://github.com/mqttjs/MQTT.js/commit/c424426cd6345eba1f8016335839a667b3928e40))\n* **resubscribe:** message id allocate twice ([#1337](https://github.com/mqttjs/MQTT.js/issues/1337)) ([7466819](https://github.com/mqttjs/MQTT.js/commit/7466819d62a5db554e41bf75e939a90f0dc46fe6))\n* **tls:** Skip TLS SNI if host is IP address ([#1311](https://github.com/mqttjs/MQTT.js/issues/1311)) ([2679952](https://github.com/mqttjs/MQTT.js/commit/2679952587a0e3e1b5fcbfd6b11fca72c65fba95))\n* **type:** add properties type for IClientSubscribeOptions ([#1378](https://github.com/mqttjs/MQTT.js/issues/1378)) ([8de9394](https://github.com/mqttjs/MQTT.js/commit/8de9394fa9afd61a1e0e726b0fe9d3637ed17cc9))\n* **type:** fix push properties types ([#1359](https://github.com/mqttjs/MQTT.js/issues/1359)) ([cb6bdcb](https://github.com/mqttjs/MQTT.js/commit/cb6bdcb2c6c9e23f87bb24dbd1458eb0509cb02f))\n* types ([#1341](https://github.com/mqttjs/MQTT.js/issues/1341)) ([59fab36](https://github.com/mqttjs/MQTT.js/commit/59fab369d2738edcf62306a67375763d737bc4ad))\n* **typescript:**  OnConnectCallback with specs expecting Connack packet ([#1333](https://github.com/mqttjs/MQTT.js/issues/1333)) ([e3e15c3](https://github.com/mqttjs/MQTT.js/commit/e3e15c3d791615a8fcab46b331678dd5a5a755a0))\n* **typescript:** Use correct version of @types/ws ([#1358](https://github.com/mqttjs/MQTT.js/issues/1358)) ([6581d33](https://github.com/mqttjs/MQTT.js/commit/6581d3340602903d3434a0053eeabe7019595ea2))\n* websocket and typescript ([9979443](https://github.com/mqttjs/MQTT.js/commit/997944380702c17d6b144b499685e591b3178c11))\n* **websockets:** revert URL WHATWG changes ([a3dd38e](https://github.com/mqttjs/MQTT.js/commit/a3dd38ed4374b0baa359430472f34078369ef02c))\n\n\n### Features\n\n* add support for ALPN TLS extension ([#1332](https://github.com/mqttjs/MQTT.js/issues/1332)) ([06f2fd2](https://github.com/mqttjs/MQTT.js/commit/06f2fd2d7666ec462f9f21c3bd19c35797de9083))\n* **client:** auth handler for enhanced auth ([#1380](https://github.com/mqttjs/MQTT.js/issues/1380)) ([d5850b7](https://github.com/mqttjs/MQTT.js/commit/d5850b7ba2653da84d53fcb57e5767e4b9cbb09d))\n\n\n### Reverts\n\n* Revert \"fix: types (#1341)\" (#1344) ([e6672e8](https://github.com/mqttjs/MQTT.js/commit/e6672e80a48db6273af6bde338035d473ee3305a)), closes [#1341](https://github.com/mqttjs/MQTT.js/issues/1341) [#1344](https://github.com/mqttjs/MQTT.js/issues/1344)\n\n\n\n## [4.2.5](https://github.com/mqttjs/MQTT.js/compare/v4.2.4...v4.2.5) (2020-11-12)\n\n\n### Bug Fixes\n\n* **auth opts:** Default to null for false-y values ([#1197](https://github.com/mqttjs/MQTT.js/issues/1197)) ([6a0e50a](https://github.com/mqttjs/MQTT.js/commit/6a0e50a52214f5e3b221d9f3d0bb86c5896e84c1))\n\n\n\n## [4.2.4](https://github.com/mqttjs/MQTT.js/compare/v4.2.3...v4.2.4) (2020-10-29)\n\n\n### Bug Fixes\n\n* **ws:** add all parts of object to opts ([#1194](https://github.com/mqttjs/MQTT.js/issues/1194)) ([6240565](https://github.com/mqttjs/MQTT.js/commit/62405653b33ec5e5e0c8077e3bc9e9ee9a335cbe))\n\n\n\n## [4.2.3](https://github.com/mqttjs/MQTT.js/compare/v4.2.2...v4.2.3) (2020-10-27)\n\n\n### Bug Fixes\n\n* **secure:** do not override password and username ([#1190](https://github.com/mqttjs/MQTT.js/issues/1190)) ([298dbb2](https://github.com/mqttjs/MQTT.js/commit/298dbb2e7e11e390794128b694a40986497b374c))\n\n\n\n## [4.2.2](https://github.com/mqttjs/MQTT.js/compare/v4.2.1...v4.2.2) (2020-10-27)\n\n\n### Bug Fixes\n\n* check if client connected when reconnecting ([#1162](https://github.com/mqttjs/MQTT.js/issues/1162)) ([541f201](https://github.com/mqttjs/MQTT.js/commit/541f201834968eeee5b8599e3b29d8daecd4aac4)), closes [#1152](https://github.com/mqttjs/MQTT.js/issues/1152)\n* replace url.parse by WHATWG URL API ([#1147](https://github.com/mqttjs/MQTT.js/issues/1147)) ([70a247c](https://github.com/mqttjs/MQTT.js/commit/70a247c29e0b05ddd8755e7b9c8c41a4c25b431b)), closes [#1130](https://github.com/mqttjs/MQTT.js/issues/1130)\n* use 'readable-stream' instead of 'stream' ([#1170](https://github.com/mqttjs/MQTT.js/issues/1170)) ([04184e1](https://github.com/mqttjs/MQTT.js/commit/04184e16d349d020a520c0f77391f421a6755816))\n\n\n\n## [4.2.1](https://github.com/mqttjs/MQTT.js/compare/v4.2.0...v4.2.1) (2020-08-24)\n\n\n### Bug Fixes\n\n* **websocket:** browser in ws ([#1145](https://github.com/mqttjs/MQTT.js/issues/1145)) ([40177ca](https://github.com/mqttjs/MQTT.js/commit/40177cac9a7d7e829b21963e1582c3eb9c13f20a))\n\n\n\n# [4.2.0](https://github.com/mqttjs/MQTT.js/compare/v4.1.0...v4.2.0) (2020-08-12)\n\n\n### Bug Fixes\n\n* **browser support:** correct browser detection for webpack ([#1135](https://github.com/mqttjs/MQTT.js/issues/1135)) ([eedc2b2](https://github.com/mqttjs/MQTT.js/commit/eedc2b26cd6063a0b1152432a00f70de5e0b9bae))\n* **browser support:** do not use process.nextTick without check that it exists ([#1136](https://github.com/mqttjs/MQTT.js/issues/1136)) ([963e554](https://github.com/mqttjs/MQTT.js/commit/963e554d3da2e4149c6f99b4fbe3aad6e620b955))\n* **mqtt stores:** improve error handling and tests ([#1133](https://github.com/mqttjs/MQTT.js/issues/1133)) ([9c61419](https://github.com/mqttjs/MQTT.js/commit/9c614192dc7f7be20f715b7236f13e0b60717dce))\n* path for bin files ([#1107](https://github.com/mqttjs/MQTT.js/issues/1107)) ([43cc1d1](https://github.com/mqttjs/MQTT.js/commit/43cc1d1f96e32b022ead3c8ce9c6ff4cbe2c3820))\n* **typescript:** fix payloadFormatIndicator to boolean type ([#1115](https://github.com/mqttjs/MQTT.js/issues/1115)) ([5adb12a](https://github.com/mqttjs/MQTT.js/commit/5adb12a6f73c63e47ff9acd54bbcaef4f11c4baa))\n\n\n### Features\n\n* **mqtt5:** add properties object to publish options ([e8326ce](https://github.com/mqttjs/MQTT.js/commit/e8326ce3baf06a1bcdbd70c33c5178bc06f8959a))\n* **websockets:** websocket-streams to ws ([#1108](https://github.com/mqttjs/MQTT.js/issues/1108)) ([b2c1215](https://github.com/mqttjs/MQTT.js/commit/b2c121511c7437b64724e9f1e89ebcd27e3c2cce))\n\n\n\n# [4.1.0](https://github.com/mqttjs/MQTT.js/compare/v4.0.1...v4.1.0) (2020-05-19)\n\n\n\n## [4.0.1](https://github.com/mqttjs/MQTT.js/compare/v4.0.0...v4.0.1) (2020-05-07)\n\n\n### Reverts\n\n* Revert \"docs: adding client flowchart\" ([ef2d590](https://github.com/mqttjs/MQTT.js/commit/ef2d5907efd5eed14aa3f46a2bf18b42ee0b3687))\n\n\n\n# [4.0.0](https://github.com/mqttjs/MQTT.js/compare/v3.0.0...v4.0.0) (2020-04-27)\n\n\n### Bug Fixes\n\n* remove only ([#1058](https://github.com/mqttjs/MQTT.js/issues/1058)) ([c8ee0e2](https://github.com/mqttjs/MQTT.js/commit/c8ee0e2c2380b87cab4a31a0fcabaab9100d62c7))\n\n\n### Features\n\n* **client:** error handling and test resilience ([#1076](https://github.com/mqttjs/MQTT.js/issues/1076)) ([2e46e08](https://github.com/mqttjs/MQTT.js/commit/2e46e08396f7a854ff53454bd0fa1f1d96b1dd27))\n* connection error handler ([#1053](https://github.com/mqttjs/MQTT.js/issues/1053)) ([3cea393](https://github.com/mqttjs/MQTT.js/commit/3cea393e2608e4c091f6bccdcf2d7bfd703bb98b))\n* support SNI on TLS ([#1055](https://github.com/mqttjs/MQTT.js/issues/1055)) ([f6534c2](https://github.com/mqttjs/MQTT.js/commit/f6534c2d8348afadc91c4d6c636447430be4642b))\n\n\n\n# [3.0.0](https://github.com/mqttjs/MQTT.js/compare/v2.18.8...v3.0.0) (2019-05-27)\n\n\n### Bug Fixes\n\n* delete completed incoming QOS 2 messages ([#893](https://github.com/mqttjs/MQTT.js/issues/893)) ([9a39faa](https://github.com/mqttjs/MQTT.js/commit/9a39faa37a3f12f10610af2b87b5be86375dc402))\n\n\n\n## [2.18.8](https://github.com/mqttjs/MQTT.js/compare/v2.18.7...v2.18.8) (2018-08-30)\n\n\n\n## [2.18.7](https://github.com/mqttjs/MQTT.js/compare/v2.18.6...v2.18.7) (2018-08-26)\n\n\n\n## [2.18.6](https://github.com/mqttjs/MQTT.js/compare/v2.18.5...v2.18.6) (2018-08-25)\n\n\n\n## [2.18.5](https://github.com/mqttjs/MQTT.js/compare/v2.18.4...v2.18.5) (2018-08-23)\n\n\n\n## [2.18.4](https://github.com/mqttjs/MQTT.js/compare/v2.18.3...v2.18.4) (2018-08-22)\n\n\n\n## [2.18.3](https://github.com/mqttjs/MQTT.js/compare/v2.18.2...v2.18.3) (2018-07-19)\n\n\n\n## [2.18.2](https://github.com/mqttjs/MQTT.js/compare/v2.18.1...v2.18.2) (2018-06-28)\n\n\n\n## [2.18.1](https://github.com/mqttjs/MQTT.js/compare/v2.18.0...v2.18.1) (2018-06-12)\n\n\n\n# [2.18.0](https://github.com/mqttjs/MQTT.js/compare/v2.17.0...v2.18.0) (2018-05-12)\n\n\n\n# [2.17.0](https://github.com/mqttjs/MQTT.js/compare/v2.16.0...v2.17.0) (2018-03-25)\n\n\n\n# [2.16.0](https://github.com/mqttjs/MQTT.js/compare/v2.15.3...v2.16.0) (2018-03-01)\n\n\n\n## [2.15.3](https://github.com/mqttjs/MQTT.js/compare/v2.15.2...v2.15.3) (2018-02-16)\n\n\n\n## [2.15.2](https://github.com/mqttjs/MQTT.js/compare/v2.15.1...v2.15.2) (2018-02-08)\n\n\n\n## [2.15.1](https://github.com/mqttjs/MQTT.js/compare/v2.15.0...v2.15.1) (2018-01-09)\n\n\n\n# [2.15.0](https://github.com/mqttjs/MQTT.js/compare/v2.14.0...v2.15.0) (2017-12-09)\n\n\n\n# [2.14.0](https://github.com/mqttjs/MQTT.js/compare/v2.13.1...v2.14.0) (2017-11-04)\n\n\n\n## [2.13.1](https://github.com/mqttjs/MQTT.js/compare/v2.13.0...v2.13.1) (2017-10-16)\n\n\n\n# [2.13.0](https://github.com/mqttjs/MQTT.js/compare/v2.12.1...v2.13.0) (2017-09-12)\n\n\n\n## [2.12.1](https://github.com/mqttjs/MQTT.js/compare/v2.12.0...v2.12.1) (2017-09-08)\n\n\n\n# [2.12.0](https://github.com/mqttjs/MQTT.js/compare/v2.11.0...v2.12.0) (2017-08-18)\n\n\n\n# [2.11.0](https://github.com/mqttjs/MQTT.js/compare/v2.10.0...v2.11.0) (2017-08-03)\n\n\n\n# [2.10.0](https://github.com/mqttjs/MQTT.js/compare/v2.9.3...v2.10.0) (2017-07-31)\n\n\n\n## [2.9.3](https://github.com/mqttjs/MQTT.js/compare/v2.9.2...v2.9.3) (2017-07-25)\n\n\n\n## [2.9.2](https://github.com/mqttjs/MQTT.js/compare/v2.9.1...v2.9.2) (2017-07-21)\n\n\n\n## [2.9.1](https://github.com/mqttjs/MQTT.js/compare/v2.9.0...v2.9.1) (2017-07-06)\n\n\n\n# [2.9.0](https://github.com/mqttjs/MQTT.js/compare/v2.8.2...v2.9.0) (2017-06-16)\n\n\n\n## [2.8.2](https://github.com/mqttjs/MQTT.js/compare/v2.8.1...v2.8.2) (2017-06-06)\n\n\n\n## [2.8.1](https://github.com/mqttjs/MQTT.js/compare/v2.8.0...v2.8.1) (2017-06-03)\n\n\n\n# [2.8.0](https://github.com/mqttjs/MQTT.js/compare/v2.7.2...v2.8.0) (2017-05-26)\n\n\n\n## [2.7.2](https://github.com/mqttjs/MQTT.js/compare/v2.7.0...v2.7.2) (2017-05-15)\n\n\n\n# [2.7.0](https://github.com/mqttjs/MQTT.js/compare/v2.6.2...v2.7.0) (2017-05-01)\n\n\n\n## [2.6.2](https://github.com/mqttjs/MQTT.js/compare/v2.6.1...v2.6.2) (2017-04-10)\n\n\n\n## [2.6.1](https://github.com/mqttjs/MQTT.js/compare/v2.6.0...v2.6.1) (2017-04-09)\n\n\n\n# [2.6.0](https://github.com/mqttjs/MQTT.js/compare/v2.5.2...v2.6.0) (2017-04-07)\n\n\n\n## [2.5.2](https://github.com/mqttjs/MQTT.js/compare/v2.5.1...v2.5.2) (2017-04-03)\n\n\n\n## [2.5.1](https://github.com/mqttjs/MQTT.js/compare/v2.5.0...v2.5.1) (2017-04-01)\n\n\n\n# [2.5.0](https://github.com/mqttjs/MQTT.js/compare/v2.4.0...v2.5.0) (2017-03-18)\n\n\n\n# [2.4.0](https://github.com/mqttjs/MQTT.js/compare/v2.3.1...v2.4.0) (2017-02-14)\n\n\n\n## [2.3.1](https://github.com/mqttjs/MQTT.js/compare/v2.3.0...v2.3.1) (2017-01-30)\n\n\n\n# [2.3.0](https://github.com/mqttjs/MQTT.js/compare/v2.2.1...v2.3.0) (2017-01-23)\n\n\n\n## [2.2.1](https://github.com/mqttjs/MQTT.js/compare/v2.2.0...v2.2.1) (2017-01-07)\n\n\n\n# [2.2.0](https://github.com/mqttjs/MQTT.js/compare/v2.1.3...v2.2.0) (2017-01-04)\n\n\n\n## [2.1.3](https://github.com/mqttjs/MQTT.js/compare/v2.1.2...v2.1.3) (2016-11-17)\n\n\n\n## [2.1.2](https://github.com/mqttjs/MQTT.js/compare/v2.1.1...v2.1.2) (2016-11-17)\n\n\n\n## [2.1.1](https://github.com/mqttjs/MQTT.js/compare/v2.1.0...v2.1.1) (2016-11-13)\n\n\n\n# [2.1.0](https://github.com/mqttjs/MQTT.js/compare/v2.0.1...v2.1.0) (2016-11-13)\n\n\n\n## [2.0.1](https://github.com/mqttjs/MQTT.js/compare/v2.0.0...v2.0.1) (2016-09-26)\n\n\n\n# [2.0.0](https://github.com/mqttjs/MQTT.js/compare/v1.14.1...v2.0.0) (2016-09-15)\n\n\n\n## [1.14.1](https://github.com/mqttjs/MQTT.js/compare/v1.14.0...v1.14.1) (2016-08-25)\n\n\n\n# [1.14.0](https://github.com/mqttjs/MQTT.js/compare/v1.13.0...v1.14.0) (2016-08-17)\n\n\n\n# [1.13.0](https://github.com/mqttjs/MQTT.js/compare/v1.12.0...v1.13.0) (2016-07-25)\n\n\n\n# [1.12.0](https://github.com/mqttjs/MQTT.js/compare/v1.11.2...v1.12.0) (2016-06-25)\n\n\n\n## [1.11.2](https://github.com/mqttjs/MQTT.js/compare/v1.11.1...v1.11.2) (2016-06-17)\n\n\n\n## [1.11.1](https://github.com/mqttjs/MQTT.js/compare/v1.11.0...v1.11.1) (2016-06-16)\n\n\n\n# [1.11.0](https://github.com/mqttjs/MQTT.js/compare/v1.10.0...v1.11.0) (2016-06-04)\n\n\n\n# [1.10.0](https://github.com/mqttjs/MQTT.js/compare/v1.9.0...v1.10.0) (2016-04-27)\n\n\n\n# [1.9.0](https://github.com/mqttjs/MQTT.js/compare/v1.8.0...v1.9.0) (2016-04-25)\n\n\n\n# [1.8.0](https://github.com/mqttjs/MQTT.js/compare/v1.7.5...v1.8.0) (2016-04-10)\n\n\n\n## [1.7.5](https://github.com/mqttjs/MQTT.js/compare/v1.7.4...v1.7.5) (2016-03-18)\n\n\n\n## [0.17.4](https://github.com/mqttjs/MQTT.js/compare/v1.7.3...v0.17.4) (2016-03-18)\n\n\n\n## [1.7.3](https://github.com/mqttjs/MQTT.js/compare/v1.7.2...v1.7.3) (2016-02-27)\n\n\n\n## [1.7.2](https://github.com/mqttjs/MQTT.js/compare/v1.7.1...v1.7.2) (2016-02-18)\n\n\n\n## [1.7.1](https://github.com/mqttjs/MQTT.js/compare/v1.7.0...v1.7.1) (2016-02-09)\n\n\n\n# [1.7.0](https://github.com/mqttjs/MQTT.js/compare/v1.6.3...v1.7.0) (2016-01-22)\n\n\n\n## [1.6.3](https://github.com/mqttjs/MQTT.js/compare/v1.6.2...v1.6.3) (2015-12-23)\n\n\n\n## [1.6.2](https://github.com/mqttjs/MQTT.js/compare/v1.6.1...v1.6.2) (2015-12-20)\n\n\n\n## [1.6.1](https://github.com/mqttjs/MQTT.js/compare/v1.6.0...v1.6.1) (2015-12-10)\n\n\n\n# [1.6.0](https://github.com/mqttjs/MQTT.js/compare/v1.5.0...v1.6.0) (2015-11-28)\n\n\n\n# [1.5.0](https://github.com/mqttjs/MQTT.js/compare/v1.4.3...v1.5.0) (2015-10-26)\n\n\n\n## [1.4.3](https://github.com/mqttjs/MQTT.js/compare/v1.4.2...v1.4.3) (2015-10-02)\n\n\n\n## [1.4.2](https://github.com/mqttjs/MQTT.js/compare/v1.4.1...v1.4.2) (2015-10-02)\n\n\n\n## [1.4.1](https://github.com/mqttjs/MQTT.js/compare/v1.4.0...v1.4.1) (2015-09-15)\n\n\n\n# [1.4.0](https://github.com/mqttjs/MQTT.js/compare/v1.3.5...v1.4.0) (2015-09-02)\n\n\n\n## [1.3.5](https://github.com/mqttjs/MQTT.js/compare/v1.3.4...v1.3.5) (2015-07-12)\n\n\n\n## [1.3.4](https://github.com/mqttjs/MQTT.js/compare/v1.3.3...v1.3.4) (2015-07-07)\n\n\n\n## [1.3.3](https://github.com/mqttjs/MQTT.js/compare/v1.3.2...v1.3.3) (2015-07-03)\n\n\n\n## [1.3.2](https://github.com/mqttjs/MQTT.js/compare/v1.3.1...v1.3.2) (2015-06-26)\n\n\n\n## [1.3.1](https://github.com/mqttjs/MQTT.js/compare/v1.3.0...v1.3.1) (2015-06-22)\n\n\n\n# [1.3.0](https://github.com/mqttjs/MQTT.js/compare/v1.2.1...v1.3.0) (2015-06-11)\n\n\n\n## [1.2.1](https://github.com/mqttjs/MQTT.js/compare/v1.2.0...v1.2.1) (2015-06-08)\n\n\n\n# [1.2.0](https://github.com/mqttjs/MQTT.js/compare/v1.1.5...v1.2.0) (2015-05-21)\n\n\n\n## [1.1.5](https://github.com/mqttjs/MQTT.js/compare/v1.1.4...v1.1.5) (2015-05-15)\n\n\n\n## [1.1.4](https://github.com/mqttjs/MQTT.js/compare/v1.1.3...v1.1.4) (2015-05-10)\n\n\n\n## [1.1.3](https://github.com/mqttjs/MQTT.js/compare/v1.1.2...v1.1.3) (2015-04-06)\n\n\n\n## [1.1.2](https://github.com/mqttjs/MQTT.js/compare/v1.1.1...v1.1.2) (2015-03-16)\n\n\n\n## [1.1.1](https://github.com/mqttjs/MQTT.js/compare/v1.1.0...v1.1.1) (2015-03-12)\n\n\n\n# [1.1.0](https://github.com/mqttjs/MQTT.js/compare/v1.0.11...v1.1.0) (2015-02-28)\n\n\n\n## [1.0.11](https://github.com/mqttjs/MQTT.js/compare/v1.0.10...v1.0.11) (2015-02-28)\n\n\n\n## [1.0.10](https://github.com/mqttjs/MQTT.js/compare/v1.0.9...v1.0.10) (2015-02-15)\n\n\n### Reverts\n\n* Revert \"Use port for protocol when none is provided\" ([ed01032](https://github.com/mqttjs/MQTT.js/commit/ed010327d4ba8370612418ba780ae7ffef66c66e))\n\n\n\n## [1.0.9](https://github.com/mqttjs/MQTT.js/compare/v1.0.8...v1.0.9) (2015-02-13)\n\n\n\n## [1.0.8](https://github.com/mqttjs/MQTT.js/compare/v1.0.7...v1.0.8) (2015-02-06)\n\n\n\n## [1.0.7](https://github.com/mqttjs/MQTT.js/compare/v1.0.6...v1.0.7) (2015-02-01)\n\n\n\n## [1.0.6](https://github.com/mqttjs/MQTT.js/compare/v1.0.5...v1.0.6) (2015-01-29)\n\n\n\n## [1.0.5](https://github.com/mqttjs/MQTT.js/compare/v1.0.4...v1.0.5) (2015-01-22)\n\n\n\n## [1.0.4](https://github.com/mqttjs/MQTT.js/compare/v1.0.3...v1.0.4) (2015-01-22)\n\n\n\n## [1.0.3](https://github.com/mqttjs/MQTT.js/compare/v1.0.2...v1.0.3) (2015-01-21)\n\n\n\n## [1.0.2](https://github.com/mqttjs/MQTT.js/compare/v1.0.1...v1.0.2) (2015-01-19)\n\n\n\n## [1.0.1](https://github.com/mqttjs/MQTT.js/compare/v1.0.0...v1.0.1) (2015-01-13)\n\n\n\n# [1.0.0](https://github.com/mqttjs/MQTT.js/compare/v0.3.13...v1.0.0) (2015-01-13)\n\n\n\n## [0.3.13](https://github.com/mqttjs/MQTT.js/compare/v0.3.12...v0.3.13) (2014-11-11)\n\n\n\n## [0.3.12](https://github.com/mqttjs/MQTT.js/compare/v0.3.11...v0.3.12) (2014-08-31)\n\n\n\n## [0.3.11](https://github.com/mqttjs/MQTT.js/compare/v0.3.10...v0.3.11) (2014-07-11)\n\n\n\n## [0.3.10](https://github.com/mqttjs/MQTT.js/compare/v0.3.9...v0.3.10) (2014-06-21)\n\n\n\n## [0.3.9](https://github.com/mqttjs/MQTT.js/compare/v0.3.8...v0.3.9) (2014-05-27)\n\n\n\n## [0.3.8](https://github.com/mqttjs/MQTT.js/compare/v0.3.7...v0.3.8) (2014-03-19)\n\n\n\n## [0.3.7](https://github.com/mqttjs/MQTT.js/compare/v0.3.6...v0.3.7) (2013-11-28)\n\n\n\n## [0.3.6](https://github.com/mqttjs/MQTT.js/compare/v0.3.5...v0.3.6) (2013-11-28)\n\n\n\n## [0.3.5](https://github.com/mqttjs/MQTT.js/compare/v0.3.4...v0.3.5) (2013-11-27)\n\n\n\n## [0.3.3](https://github.com/mqttjs/MQTT.js/compare/v0.3.2...v0.3.3) (2013-09-30)\n\n\n\n## [0.3.2](https://github.com/mqttjs/MQTT.js/compare/v0.3.1...v0.3.2) (2013-09-19)\n\n\n\n## [0.3.1](https://github.com/mqttjs/MQTT.js/compare/v0.3.0...v0.3.1) (2013-08-22)\n\n\n\n# [0.3.0](https://github.com/mqttjs/MQTT.js/compare/v0.2.11...v0.3.0) (2013-08-21)\n\n\n### Reverts\n\n* Revert \"Adding a little buffer to cope with slow connections.\" ([ff1e3ed](https://github.com/mqttjs/MQTT.js/commit/ff1e3ed8613d4d57b66318019b64f5cb160b1bb2))\n\n\n\n## [0.2.11](https://github.com/mqttjs/MQTT.js/compare/v0.2.10...v0.2.11) (2013-07-20)\n\n\n\n## [0.2.10](https://github.com/mqttjs/MQTT.js/compare/v0.2.9...v0.2.10) (2013-06-12)\n\n\n\n## [0.2.9](https://github.com/mqttjs/MQTT.js/compare/v0.2.8...v0.2.9) (2013-05-29)\n\n\n\n## [0.2.8](https://github.com/mqttjs/MQTT.js/compare/v0.2.5...v0.2.8) (2013-05-27)\n\n\n\n## [0.2.5](https://github.com/mqttjs/MQTT.js/compare/v0.2.4...v0.2.5) (2013-03-23)\n\n\n\n## [0.2.4](https://github.com/mqttjs/MQTT.js/compare/v0.2.3...v0.2.4) (2013-03-07)\n\n\n\n## [0.2.3](https://github.com/mqttjs/MQTT.js/compare/v0.2.2...v0.2.3) (2013-03-07)\n\n\n\n## [0.2.2](https://github.com/mqttjs/MQTT.js/compare/0.2.0...v0.2.2) (2013-03-06)\n\n\n\n# [0.2.0](https://github.com/mqttjs/MQTT.js/compare/0.1.8...0.2.0) (2013-02-28)\n\n\n\n## [0.1.8](https://github.com/mqttjs/MQTT.js/compare/v0.1.3...0.1.8) (2013-02-12)\n\n\n\n## [0.1.3](https://github.com/mqttjs/MQTT.js/compare/v0.1.2...v0.1.3) (2012-02-06)\n\n\n\n## [0.1.2](https://github.com/mqttjs/MQTT.js/compare/v0.1.1...v0.1.2) (2012-01-23)\n\n\n\n## [0.1.1](https://github.com/mqttjs/MQTT.js/compare/v0.1.0...v0.1.1) (2012-01-18)\n\n\n\n# 0.1.0 (2012-01-17)\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# MQTT.js is an OPEN Open Source Project\n\n-----------------------------------------\n\n## What?\n\nIndividuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.\n\n## Rules\n\nThere are a few basic ground-rules for contributors:\n\n1. **No `--force` pushes** or modifying the Git history in any way.\n1. **Non-main branches** ought to be used for ongoing work.\n1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors.\n1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor.\n1. Contributors should attempt to adhere to the prevailing code-style.\n\n## Releases\n\nDeclaring formal releases remains the prerogative of the project maintainer.\n\n## Changes to this arrangement\n\nThis is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change.\n\n-----------------------------------------\n"
  },
  {
    "path": "DEVELOPMENT.md",
    "content": "# MQTTjs Development\n\nThis document aims to help you get started with developing MQTT.js.\n\n## Release Process\n\nIn order to create a new release you have two options:\n\n1. Locally run `npm run release` and follow the interactive CLI\n2. Manually trigger the GitHub Action `release` workflow specifyin the type of release you want to create\n\n## Tests\n\nTo run the tests, you can use the following command:\n\n```sh\nnpm test\n```\n\nThis will run both `browser` and `node` tests.\n\n### Running specific tests\n\nFor example, you can run `node -r esbuild-register --test test/keepaliveManager.ts`\n\n### Browser\n\nBrowser tests use [`wtr`](https://modern-web.dev/docs/test-runner/overview/) as the test runner. To build browser bundle using [esbuild](https://esbuild.github.io/) and run browser tests, you can use the following command:\n\n```sh\nnpm run test:browser\n```\n\nThe configuration file is [web-test-runner.config.msj](./web-test-runner.config.mjs). It starts a local broker using [aedes-cli](https://github.com/moscajs/aedes-cli) with `ws` and `wss` support and then runs the tests in 3 different browsers: `chrome`, `firefox` and `safari`.\n\nThe tests are located in the `test/browser` directory and there are also tests for service workers in the `test/browser/worker.js` directory.\n\nWhen developing/debugging tests it's useful to run the tests in a single browser, for example:\n\n```sh\nnpx wtr --manual --open\n```\n\nThis will open the browser on `localhost:8001` and lets you choose the test to run by clicking on the link with the test name. By opening the DevTools you will be able to see the tests output and put debugger in both worker and main tests files.\n\nBe aware that tests will use the bundled version of the library, so you need to run `npm run build` before running the tests. If you need to debug issues in the code it could be useful to enable source maps when building, in order to do this just set `sourcemap: true` in [esbuild.js](./esbuild.js) file and run `npm run build`.\n\n### Node\n\nFor NodeJS tests we use the NodeJS [Test Runner](https://nodejs.org/api/test.html). To run the tests, you can use the following command:\n\n```sh\nnpm run test:node\n```\n\nThe tests are located in the `test` directory. The entrypoint of tests is `runTests.ts` file. It is used to filter the tests to run, set concurrency and create a nice looking tests summary (see reason [here](https://github.com/nodejs/help/issues/3902#issuecomment-1726033310))\n\nWhen developing/debugging tests it's useful to run the tests for a single file/test, for example:\n\n```sh\nnode -r esbuild-register --test --inspect test/client.ts\n```\n\nIf you want to run tests using a filter, you can use the `--test-name-pattern` flag:\n\n```sh\nnode -r esbuild-register --test --test-name-pattern=\"should resend in-flight QoS\" --inspect test/client.ts\n```\n\nYou can also run tests in watch mode using the `--watch` flag.\n\n## Lint\n\nYou can run and automatically fix linting issues with\n\n```sh\nnpm run lint-fix\n```\n\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n=====================\n\nCopyright (c) 2015-2016 MQTT.js contributors\n---------------------------------------\n\n*MQTT.js contributors listed at <https://github.com/mqttjs/MQTT.js#contributors>*\n\nCopyright 2011-2014 by Adam Rudd\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# ![mqtt.js](https://raw.githubusercontent.com/mqttjs/MQTT.js/137ee0e3940c1f01049a30248c70f24dc6e6f829/MQTT.js.png)\n\n![Github Test Status](https://github.com/mqttjs/MQTT.js/workflows/MQTT.js%20CI/badge.svg) [![codecov](https://codecov.io/gh/mqttjs/MQTT.js/branch/master/graph/badge.svg)](https://codecov.io/gh/mqttjs/MQTT.js)\n\n[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/mqttjs/MQTT.js/graphs/commit-activity)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/mqttjs/MQTT.js/pulls)\n\n[![node](https://img.shields.io/node/v/mqtt.svg) ![npm](https://img.shields.io/npm/v/mqtt.svg?logo=npm)](https://www.npmjs.com/package/mqtt)\n[![NPM Downloads](https://img.shields.io/npm/dm/mqtt.svg)](https://npm-compare.com/mqtt/#timeRange=THREE_YEARS)\n\nMQTT.js is a client library for the [MQTT](http://mqtt.org/) protocol, written\nin JavaScript for node.js and the browser.\n\n## Table of Contents\n\n- [Upgrade notes](#notes)\n- [Installation](#install)\n- [Example](#example)\n- [React Native](#react-native)\n- [Import Styles](#example)\n- [Command Line Tools](#cli)\n- [API](#api)\n- [Browser](#browser)\n- [About QoS](#qos)\n- [TypeScript](#typescript)\n- [Weapp and Ali support](#weapp-alipay)\n- [Contributing](#contributing)\n- [Sponsor](#sponsor)\n- [License](#license)\n\nMQTT.js is an OPEN Open Source Project, see the [Contributing](#contributing) section to find out what this means.\n\n[![JavaScript Style\nGuide](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard)\n\n<a name=\"notes\"></a>\n\n## Important notes for existing users\n\n**v5.0.0** (07/2023)\n\n- Removes support for all end of life node versions (v12 and v14), and now supports node v18 and v20.\n- Completely rewritten in Typescript 🚀.\n- When creating `MqttClient` instance `new` is now required.\n\n**v4.0.0** (Released 04/2020) removes support for all end of life node versions, and now supports node v12 and v14. It also adds improvements to\ndebug logging, along with some feature additions.\n\nAs a **breaking change**, by default a error handler is built into the MQTT.js client, so if any\nerrors are emitted and the user has not created an event handler on the client for errors, the client will\nnot break as a result of unhandled errors. Additionally, typical TLS errors like `ECONNREFUSED`, `ECONNRESET` have been\nadded to a list of TLS errors that will be emitted from the MQTT.js client, and so can be handled as connection errors.\n\n**v3.0.0** adds support for MQTT 5, support for node v10.x, and many fixes to improve reliability.\n\n**Note:** MQTT v5 support is experimental as it has not been implemented by brokers yet.\n\n**v2.0.0** removes support for node v0.8, v0.10 and v0.12, and it is 3x faster in sending\npackets. It also removes all the deprecated functionality in v1.0.0,\nmainly `mqtt.createConnection` and `mqtt.Server`. From v2.0.0,\nsubscriptions are restored upon reconnection if `clean: true`.\nv1.x.x is now in _LTS_, and it will keep being supported as long as\nthere are v0.8, v0.10 and v0.12 users.\n\nAs a **breaking change**, the `encoding` option in the old client is\nremoved, and now everything is UTF-8 with the exception of the\n`password` in the CONNECT message and `payload` in the PUBLISH message,\nwhich are `Buffer`.\n\nAnother **breaking change** is that MQTT.js now defaults to MQTT v3.1.1,\nso to support old brokers, please read the [client options doc](#client).\n\n**v1.0.0** improves the overall architecture of the project, which is now\nsplit into three components: MQTT.js keeps the Client,\n[mqtt-connection](http://npm.im/mqtt-connection) includes the barebone\nConnection code for server-side usage, and [mqtt-packet](http://npm.im/mqtt-packet)\nincludes the protocol parser and generator. The new Client improves\nperformance by a 30% factor, embeds Websocket support\n([MOWS](http://npm.im/mows) is now deprecated), and it has a better\nsupport for QoS 1 and 2. The previous API is still supported but\ndeprecated, as such, it is not documented in this README.\n\n<a name=\"install\"></a>\n\n## Installation\n\n```sh\nnpm install mqtt --save\n```\n\n<a name=\"example\"></a>\n\n## Example\n\nFor the sake of simplicity, let's put the subscriber and the publisher in the same file:\n\n```js\nconst mqtt = require(\"mqtt\");\nconst client = mqtt.connect(\"mqtt://test.mosquitto.org\");\n\nclient.on(\"connect\", () => {\n  client.subscribe(\"presence\", (err) => {\n    if (!err) {\n      client.publish(\"presence\", \"Hello mqtt\");\n    }\n  });\n});\n\nclient.on(\"message\", (topic, message) => {\n  // message is Buffer\n  console.log(message.toString());\n  client.end();\n});\n```\n\noutput:\n\n```sh\nHello mqtt\n```\n\n<a name=\"example-react-native\"></a>\n\n### React Native\n\nMQTT.js can be used in React Native applications. To use it, see the [React Native example](https://github.com/MaximoLiberata/react-native-mqtt.js-example)\n\nIf you want to run your own MQTT broker, you can use\n[Mosquitto](http://mosquitto.org) or\n[Aedes-cli](https://github.com/moscajs/aedes-cli), and launch it.\n\nYou can also use a test instance: test.mosquitto.org.\n\nIf you do not want to install a separate broker, you can try using the\n[Aedes](https://github.com/moscajs/aedes).\n\n<a name=\"import_styles\"></a>\n\n## Import styles\n\n### CommonJS (Require)\n\n```js\nconst mqtt = require(\"mqtt\")  // require mqtt\nconst client = mqtt.connect(\"mqtt://test.mosquitto.org\")  // create a client\n```\n\n### ES6 Modules (Import)\n\n#### Default import\n\n```js\nimport mqtt from \"mqtt\"; // import namespace \"mqtt\"\nlet client = mqtt.connect(\"mqtt://test.mosquitto.org\"); // create a client\n```\n\n#### Importing individual components\n\n```js\nimport { connect } from \"mqtt\"; // import connect from mqtt\nlet client = connect(\"mqtt://test.mosquitto.org\"); // create a client\n```\n\n<a name=\"cli\"></a>\n\n## Command Line Tools\n\nMQTT.js bundles a command to interact with a broker.\nIn order to have it available on your path, you should install MQTT.js\nglobally:\n\n```sh\nnpm install mqtt -g\n```\n\nThen, on one terminal\n\n```sh\nmqtt sub -t 'hello' -h 'test.mosquitto.org' -v\n```\n\nOn another\n\n```sh\nmqtt pub -t 'hello' -h 'test.mosquitto.org' -m 'from MQTT.js'\n```\n\nSee `mqtt help <command>` for the command help.\n\n<a name=\"debug\"></a>\n\n## Debug Logs\n\nMQTT.js uses the [debug](https://www.npmjs.com/package/debug#cmd) package for debugging purposes. To enable debug logs, add the following environment variable on runtime :\n\n```ps\n# (example using PowerShell, the VS Code default)\n$env:DEBUG='mqttjs*'\n```\n\n<a name=\"reconnecting\"></a>\n\n## About Reconnection\n\nAn important part of any websocket connection is what to do when a connection\ndrops off and the client needs to reconnect. MQTT has built-in reconnection\nsupport that can be configured to behave in ways that suit the application.\n\n#### Refresh Authentication Options / Signed Urls with `transformWsUrl` (Websocket Only)\n\nWhen an mqtt connection drops and needs to reconnect, it's common to require\nthat any authentication associated with the connection is kept current with\nthe underlying auth mechanism. For instance some applications may pass an auth\ntoken with connection options on the initial connection, while other cloud\nservices may require a url be signed with each connection.\n\nBy the time the reconnect happens in the application lifecycle, the original\nauth data may have expired.\n\nTo address this we can use a hook called `transformWsUrl` to manipulate\neither of the connection url or the client options at the time of a reconnect.\n\nExample (update clientId & username on each reconnect):\n\n```js\n    const transformWsUrl = (url, options, client) => {\n      client.options.username = `token=${this.get_current_auth_token()}`;\n      client.options.clientId = `${this.get_updated_clientId()}`;\n\n      return `${this.get_signed_cloud_url(url)}`;\n    }\n\n    const connection = await mqtt.connectAsync(<wss url>, {\n      ...,\n      transformWsUrl: transformUrl,\n    });\n\n```\n\nNow every time a new WebSocket connection is opened (hopefully not too often),\nwe will get a fresh signed url or fresh auth token data.\n\nNote: Currently this hook does _not_ support promises, meaning that in order to\nuse the latest auth token, you must have some outside mechanism running that\nhandles application-level authentication refreshing so that the websocket\nconnection can simply grab the latest valid token or signed url.\n\n#### Customize Websockets with `createWebsocket` (Websocket Only)\n\nWhen you need to add a custom websocket subprotocol or header to open a connection\nthrough a proxy with custom authentication this callback allows you to create your own\ninstance of a websocket which will be used in the mqtt client.\n\n```js\n  const createWebsocket = (url, websocketSubProtocols, options) => {\n    const subProtocols = [\n      websocketSubProtocols[0],\n      'myCustomSubprotocolOrOAuthToken',\n    ]\n    return new WebSocket(url, subProtocols)\n  }\n\n  const client = await mqtt.connectAsync(<wss url>, {\n    ...,\n    createWebsocket: createWebsocket,\n  });\n```\n\n#### Enabling Reconnection with `reconnectPeriod` option\n\nTo ensure that the mqtt client automatically tries to reconnect when the\nconnection is dropped, you must set the client option `reconnectPeriod` to a\nvalue greater than 0. A value of 0 will disable reconnection and then terminate\nthe final connection when it drops.\n\nThe default value is 1000 ms which means it will try to reconnect 1 second\nafter losing the connection.\n\nNote that this will only enable reconnects after either a connection timeout, or\nafter a successful connection. It will _not_ (by default) enable retrying\nconnections that are actively denied with a CONNACK error by the server.\n\nTo also enable automatic reconnects for CONNACK errors, set\n`reconnectOnConnackError: true`.\n\n<a name=\"topicalias\"></a>\n\n## About Topic Alias Management\n\n### Enabling automatic Topic Alias using\n\nIf the client sets the option `autoUseTopicAlias:true` then MQTT.js uses existing topic alias automatically.\n\nexample scenario:\n\n```bash\n1. PUBLISH topic:'t1', ta:1                   (register)\n2. PUBLISH topic:'t1'       -> topic:'', ta:1 (auto use existing map entry)\n3. PUBLISH topic:'t2', ta:1                   (register overwrite)\n4. PUBLISH topic:'t2'       -> topic:'', ta:1 (auto use existing map entry based on the receent map)\n5. PUBLISH topic:'t1'                         (t1 is no longer mapped to ta:1)\n```\n\nUser doesn't need to manage which topic is mapped to which topic alias.\nIf the user want to register topic alias, then publish topic with topic alias.\nIf the user want to use topic alias, then publish topic without topic alias. If there is a mapped topic alias then added it as a property and update the topic to empty string.\n\n### Enabling automatic Topic Alias assign\n\nIf the client sets the option `autoAssignTopicAlias:true` then MQTT.js uses existing topic alias automatically.\nIf no topic alias exists, then assign a new vacant topic alias automatically. If topic alias is fully used, then LRU(Least Recently Used) topic-alias entry is overwritten.\n\nexample scenario:\n\n```bash\nThe broker returns CONNACK (TopicAliasMaximum:3)\n1. PUBLISH topic:'t1' -> 't1', ta:1 (auto assign t1:1 and register)\n2. PUBLISH topic:'t1' -> ''  , ta:1 (auto use existing map entry)\n3. PUBLISH topic:'t2' -> 't2', ta:2 (auto assign t1:2 and register. 2 was vacant)\n4. PUBLISH topic:'t3' -> 't3', ta:3 (auto assign t1:3 and register. 3 was vacant)\n5. PUBLISH topic:'t4' -> 't4', ta:1 (LRU entry is overwritten)\n```\n\nAlso user can manually register topic-alias pair using PUBLISH topic:'some', ta:X. It works well with automatic topic alias assign.\n\n<a name=\"api\"></a>\n\n## API\n\n- [`mqtt.connect()`](#connect)\n- [`mqtt.connectAsync()`](#connect-async)\n- [`mqtt.Client()`](#client)\n- [`mqtt.Client#connect()`](#client-connect)\n- [`mqtt.Client#publish()`](#publish)\n- [`mqtt.Client#publishAsync()`](#publish-async)\n- [`mqtt.Client#subscribe()`](#subscribe)\n- [`mqtt.Client#subscribeAsync()`](#subscribe-async)\n- [`mqtt.Client#unsubscribe()`](#unsubscribe)\n- [`mqtt.Client#unsubscribeAsync()`](#unsubscribe-async)\n- [`mqtt.Client#end()`](#end)\n- [`mqtt.Client#endAsync()`](#end-async)\n- [`mqtt.Client#removeOutgoingMessage()`](#removeOutgoingMessage)\n- [`mqtt.Client#reconnect()`](#reconnect)\n- [`mqtt.Client#handleMessage()`](#handleMessage)\n- [`mqtt.Client#connected`](#connected)\n- [`mqtt.Client#reconnecting`](#reconnecting)\n- [`mqtt.Client#getLastMessageId()`](#getLastMessageId)\n- [`mqtt.Store()`](#store)\n- [`mqtt.Store#put()`](#put)\n- [`mqtt.Store#del()`](#del)\n- [`mqtt.Store#createStream()`](#createStream)\n- [`mqtt.Store#close()`](#close)\n\n---\n\n<a name=\"connect\"></a>\n\n### mqtt.connect([url], options)\n\nConnects to the broker specified by the given url and options and\nreturns a [Client](#client).\n\nThe URL can be on the following protocols: 'mqtt', 'mqtts', 'tcp',\n'tls', 'ws', 'wss', 'wxs', 'alis'. If you are trying to connect to a unix socket just append the `+unix` suffix to the protocol (ex: `mqtt+unix`). This will set the `unixSocket` property automatically.\n\nThe URL can also be an object as returned by\n[`URL.parse()`](http://nodejs.org/api/url.html#url_url_parse_urlstr_parsequerystring_slashesdenotehost),\nin that case the two objects are merged, i.e. you can pass a single\nobject with both the URL and the connect options.\n\nYou can also specify a `servers` options with content: `[{ host:\n'localhost', port: 1883 }, ... ]`, in that case that array is iterated\nat every connect.\n\nFor all MQTT-related options, see the [Client](#client)\nconstructor.\n\n<a name=\"connect-async\"></a>\n\n### connectAsync([url], options)\n\nAsynchronous wrapper around the [`connect`](#connect) function.\n\nReturns a `Promise` that resolves to a `mqtt.Client` instance when the client\nfires a `'connect'` or `'end'` event, or rejects with an error if the `'error'`\nis fired.\n\nNote that the `manualConnect` option will cause the promise returned by this\nfunction to never resolve or reject as the underlying client never fires any\nevents.\n\n---\n\n<a name=\"client\"></a>\n\n### mqtt.Client(streamBuilder, options)\n\nThe `Client` class wraps a client connection to an\nMQTT broker over an arbitrary transport method (TCP, TLS,\nWebSocket, ecc).\n`Client` is an [EventEmitter](https://nodejs.org/en/learn/asynchronous-work/the-nodejs-event-emitter) that has it's own [events](#events)\n\n`Client` automatically handles the following:\n\n- Regular server pings\n- QoS flow\n- Automatic reconnections\n- Start publishing before being connected\n\nThe arguments are:\n\n- `streamBuilder` is a function that returns a subclass of the `Stream` class that supports\n  the `connect` event. Typically a `net.Socket`.\n- `options` is the client connection options (see: the [connect packet](https://github.com/mcollina/mqtt-packet#connect)). Defaults:\n  - `wsOptions`: is the WebSocket connection options. Default is `{}`.\n    It's specific for WebSockets. For possible options have a look at: <https://github.com/websockets/ws/blob/master/doc/ws.md>.\n  - `keepalive`: `60` seconds, set to `0` to disable\n  - `reschedulePings`: reschedule ping messages after sending packets (default `true`)\n  - `clientId`: `'mqttjs_' + Math.random().toString(16).substr(2, 8)`\n  - `protocolId`: `'MQTT'`\n  - `protocolVersion`: `4`\n  - `clean`: `true`, set to false to receive QoS 1 and 2 messages while\n    offline\n  - `reconnectPeriod`: `1000` milliseconds, interval between two\n    reconnections. Disable auto reconnect by setting to `0`.\n  - `reconnectOnConnackError`: `false`, whether to also reconnect if a CONNACK\n    is received with an error.\n  - `connectTimeout`: `30 * 1000` milliseconds, time to wait before a\n    CONNACK is received\n  - `username`: the username required by your broker, if any\n  - `password`: the password required by your broker, if any\n  - `socksProxy`: establish TCP and TLS connections via a socks proxy (URL, supported protocols are `socks5://`, `socks5h://`, `socks4://`, `socks4a://`)\n  - `socksTimeout`: timeout for connecting to the socks proxy\n  - `incomingStore`: a [Store](#store) for the incoming packets\n  - `outgoingStore`: a [Store](#store) for the outgoing packets\n  - `queueQoSZero`: if connection is broken, queue outgoing QoS zero messages (default `true`)\n  - `customHandleAcks`: MQTT 5 feature of custom handling puback and pubrec packets. Its callback:\n\n    ```js\n      customHandleAcks: function(topic, message, packet, done) {/*some logic with calling done(error, reasonCode)*/}\n    ```\n\n  - `autoUseTopicAlias`: enabling automatic Topic Alias using functionality\n  - `autoAssignTopicAlias`: enabling automatic Topic Alias assign functionality\n  - `properties`: properties MQTT 5.0.\n    `object` that supports the following properties:\n    - `sessionExpiryInterval`: representing the Session Expiry Interval in seconds `number`,\n    - `receiveMaximum`: representing the Receive Maximum value `number`,\n    - `maximumPacketSize`: representing the Maximum Packet Size the Client is willing to accept `number`,\n    - `topicAliasMaximum`: representing the Topic Alias Maximum value indicates the highest value that the Client will accept as a Topic Alias sent by the Server `number`,\n    - `requestResponseInformation`: The Client uses this value to request the Server to return Response Information in the CONNACK `boolean`,\n    - `requestProblemInformation`: The Client uses this value to indicate whether the Reason String or User Properties are sent in the case of failures `boolean`,\n    - `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object`,\n    - `authenticationMethod`: the name of the authentication method used for extended authentication `string`,\n    - `authenticationData`: Binary Data containing authentication data `binary`\n  - `authPacket`: settings for auth packet `object`\n  - `will`: a message that will sent by the broker automatically when\n    the client disconnect badly. The format is:\n    - `topic`: the topic to publish\n    - `payload`: the message to publish\n    - `qos`: the QoS\n    - `retain`: the retain flag\n    - `properties`: properties of will by MQTT 5.0:\n      - `willDelayInterval`: representing the Will Delay Interval in seconds `number`,\n      - `payloadFormatIndicator`: Will Message is UTF-8 Encoded Character Data or not `boolean`,\n      - `messageExpiryInterval`: value is the lifetime of the Will Message in seconds and is sent as the Publication Expiry Interval when the Server publishes the Will Message `number`,\n      - `contentType`: describing the content of the Will Message `string`,\n      - `responseTopic`: String which is used as the Topic Name for a response message `string`,\n      - `correlationData`: The Correlation Data is used by the sender of the Request Message to identify which request the Response Message is for when it is received `binary`,\n      - `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object`\n  - `transformWsUrl` : optional `(url, options, client) => url` function\n    For ws/wss protocols only. Can be used to implement signing\n    urls which upon reconnect can have become expired.\n  - `createWebsocket` : optional `url, websocketSubProtocols, options) => Websocket` function\n      For ws/wss protocols only. Can be used to implement a custom websocket subprotocol or implementation.\n  - `resubscribe` : if connection is broken and reconnects,\n    subscribed topics are automatically subscribed again (default `true`)\n  - `subscribeBatchSize` : optional `number`\n    Maximum number of topics per SUBSCRIBE packet. When the number of topics to subscribe exceeds this value, the client will automatically split them into multiple SUBSCRIBE packets of this size.\n  - `messageIdProvider`: custom messageId provider. when `new UniqueMessageIdProvider()` is set, then non conflict messageId is provided.\n  - `log`: custom log function. Default uses [debug](https://www.npmjs.com/package/debug) package.\n  - `manualConnect`: prevents the constructor to call `connect`. In this case after the `mqtt.connect` is called you should call `client.connect` manually.\n  - `timerVariant`: defaults to `auto`, which tries to determine which timer is most appropriate for you environment, if you're having detection issues, you can set it to `worker` or `native`. If none suits you, you can pass a timer object with set and clear properties:\n    ```js\n    timerVariant: {\n      set: (func, timer) => setInterval(func, timer),\n      clear: (id) => clearInterval(id)\n    }\n    ```\n  - `forceNativeWebSocket`: set to true if you're having detection issues (i.e. the `ws does not work in the browser` exception) to force the use of native WebSocket. It is important to note that if set to true for the first client created, then all the clients will use native WebSocket. And conversely, if not set or set to false, all will use the detection result.\n  - `unixSocket`: if you want to connect to a unix socket, set this to true\n\nInstead of setting `socksProxy` you can also supple the same parameter via the environment variable `MQTTJS_SOCKS_PROXY`.\n\nIn case mqtts (mqtt over tls) is required, the `options` object is passed through to [`tls.connect()`](http://nodejs.org/api/tls.html#tls_tls_connect_options_callback). If using a **self-signed certificate**, set `rejectUnauthorized: false`. However, be cautious as this exposes you to potential man in the middle attacks and isn't recommended for production.\n\nFor those supporting multiple TLS protocols on a single port, like MQTTS and MQTT over WSS, utilize the `ALPNProtocols` option. This lets you define the Application Layer Protocol Negotiation (ALPN) protocol. You can set `ALPNProtocols` as a string array, Buffer, or Uint8Array based on your setup.\n\nIf you are connecting to a broker that supports only MQTT 3.1 (not\n3.1.1 compliant), you should pass these additional options:\n\n```js\n{\n  protocolId: 'MQIsdp',\n  protocolVersion: 3\n}\n```\n\nThis is confirmed on RabbitMQ 3.2.4, and on Mosquitto < 1.3. Mosquitto\nversion 1.3 and 1.4 works fine without those.\n\n<a name=\"events\"></a>\n\n#### Event `'connect'`\n\n`function (connack) {}`\n\nEmitted on successful (re)connection (i.e. connack rc=0).\n\n- `connack` received connack packet. When `clean` connection option is `false` and server has a previous session\n  for `clientId` connection option, then `connack.sessionPresent` flag is `true`. When that is the case,\n  you may rely on stored session and prefer not to send subscribe commands for the client.\n\n#### Event `'reconnect'`\n\n`function () {}`\n\nEmitted when a reconnect starts.\n\n#### Event `'close'`\n\n`function () {}`\n\nEmitted after a disconnection.\n\n#### Event `'disconnect'`\n\n`function (packet) {}`\n\nEmitted after receiving disconnect packet from broker. MQTT 5.0 feature.\n\n#### Event `'offline'`\n\n`function () {}`\n\nEmitted when the client goes offline.\n\n#### Event `'error'`\n\n`function (error) {}`\n\nEmitted when the client cannot connect (i.e. connack rc != 0) or when a\nparsing error occurs.\n\nThe following TLS errors will be emitted as an `error` event:\n\n- `ECONNREFUSED`\n- `ECONNRESET`\n- `EADDRINUSE`\n- `ENOTFOUND`\n\n#### Event `'end'`\n\n`function () {}`\n\nEmitted when [`mqtt.Client#end()`](#end) is called.\nIf a callback was passed to `mqtt.Client#end()`, this event is emitted once the\ncallback returns.\n\n#### Event `'message'`\n\n`function (topic, message, packet) {}`\n\nEmitted when the client receives a publish packet\n\n- `topic` topic of the received packet\n- `message` payload of the received packet\n- `packet` received packet, as defined in\n  [mqtt-packet](https://github.com/mcollina/mqtt-packet#publish)\n\n#### Event `'packetsend'`\n\n`function (packet) {}`\n\nEmitted when the client sends any packet. This includes .published() packets\nas well as packets used by MQTT for managing subscriptions and connections\n\n- `packet` received packet, as defined in\n  [mqtt-packet](https://github.com/mcollina/mqtt-packet)\n\n#### Event `'packetreceive'`\n\n`function (packet) {}`\n\nEmitted when the client receives any packet. This includes packets from\nsubscribed topics as well as packets used by MQTT for managing subscriptions\nand connections\n\n- `packet` received packet, as defined in\n  [mqtt-packet](https://github.com/mcollina/mqtt-packet)\n\n---\n\n<a name=\"client-connect\"></a>\n\n### mqtt.Client#connect()\n\nBy default client connects when constructor is called. To prevent this you can set `manualConnect` option to `true` and call `client.connect()` manually.\n\n<a name=\"publish\"></a>\n\n### mqtt.Client#publish(topic, message, [options], [callback])\n\nPublish a message to a topic\n\n- `topic` is the topic to publish to, `String`\n- `message` is the message to publish, `Buffer` or `String`\n- `options` is the options to publish with, including:\n  - `qos` QoS level, `Number`, default `0`\n  - `retain` retain flag, `Boolean`, default `false`\n  - `dup` mark as duplicate flag, `Boolean`, default `false`\n  - `properties`: MQTT 5.0 properties `object`\n    - `payloadFormatIndicator`: Payload is UTF-8 Encoded Character Data or not `boolean`,\n    - `messageExpiryInterval`: the lifetime of the Application Message in seconds `number`,\n    - `topicAlias`: value that is used to identify the Topic instead of using the Topic Name `number`,\n    - `responseTopic`: String which is used as the Topic Name for a response message `string`,\n    - `correlationData`: used by the sender of the Request Message to identify which request the Response Message is for when it is received `binary`,\n    - `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object`,\n    - `subscriptionIdentifier`: representing the identifier of the subscription `number`,\n    - `contentType`: String describing the content of the Application Message `string`\n  - `cbStorePut` - `function ()`, fired when message is put into `outgoingStore` if QoS is `1` or `2`.\n- `callback` - `function (err, packet)`, fired when the QoS handling completes,\n  or at the next tick if QoS 0. An error occurs if client is disconnecting.\n\n<a name=\"publish-async\"></a>\n\n### mqtt.Client#publishAsync(topic, message, [options])\n\nAsync [`publish`](#publish). Returns a `Promise<Packet | undefined>`.\n\nA packet is anything that has a `messageId` property.\n---\n\n<a name=\"subscribe\"></a>\n\n### mqtt.Client#subscribe(topic/topic array/topic object, [options], [callback])\n\nSubscribe to a topic or topics\n\n- `topic` is a `String` topic to subscribe to or an `Array` of\n  topics to subscribe to. It can also be an object, it has as object\n  keys the topic name and as value the QoS, like `{'test1': {qos: 0}, 'test2': {qos: 1}}`.\n  MQTT `topic` wildcard characters are supported (`+` - for single level and `#` - for multi level)\n- `options` is the options to subscribe with, including:\n  - `qos` QoS subscription level, default 0\n  - `nl` No Local MQTT 5.0 flag (If the value is true, Application Messages MUST NOT be forwarded to a connection with a ClientID equal to the ClientID of the publishing connection)\n  - `rap` Retain as Published MQTT 5.0 flag (If true, Application Messages forwarded using this subscription keep the RETAIN flag they were published with. If false, Application Messages forwarded using this subscription have the RETAIN flag set to 0.)\n  - `rh` Retain Handling MQTT 5.0 (This option specifies whether retained messages are sent when the subscription is established.)\n  - `properties`: `object`\n    - `subscriptionIdentifier`: representing the identifier of the subscription `number`,\n    - `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object`\n- `callback` - `function (err, granted)`\n  callback fired on suback where:\n  - `err` a subscription error or an error that occurs when client is disconnecting\n  - `granted` is an array of `{topic, qos}` where:\n    - `topic` is a subscribed to topic\n    - `qos` is the granted QoS level on it\n\n<a name=\"subscribe-async\"></a>\n\n### mqtt.Client#subscribeAsync(topic/topic array/topic object, [options])\n\nAsync [`subscribe`](#subscribe). Returns a `Promise<ISubscriptionGrant[]>`.\n\n---\n\n<a name=\"unsubscribe\"></a>\n\n### mqtt.Client#unsubscribe(topic/topic array, [options], [callback])\n\nUnsubscribe from a topic or topics\n\n- `topic` is a `String` topic or an array of topics to unsubscribe from\n- `options`: options of unsubscribe.\n  - `properties`: `object`\n    - `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object`\n- `callback` - `function (err)`, fired on unsuback. An error occurs if client is disconnecting.\n\n<a name=\"unsubscribe-async\"></a>\n\n### mqtt.Client#unsubscribeAsync(topic/topic array, [options])\n\nAsync [`unsubscribe`](#unsubscribe). Returns a `Promise<void>`.\n\n---\n\n<a name=\"end\"></a>\n\n### mqtt.Client#end([force], [options], [callback])\n\nClose the client, accepts the following options:\n\n- `force`: passing it to true will close the client right away, without\n  waiting for the in-flight messages to be acked. This parameter is\n  optional.\n- `options`: options of disconnect.\n  - `reasonCode`: Disconnect Reason Code `number`\n  - `properties`: `object`\n    - `sessionExpiryInterval`: representing the Session Expiry Interval in seconds `number`,\n    - `reasonString`: representing the reason for the disconnect `string`,\n    - `userProperties`: The User Property is allowed to appear multiple times to represent multiple name, value pairs `object`,\n    - `serverReference`: String which can be used by the Client to identify another Server to use `string`\n- `callback`: will be called when the client is closed. This parameter is\n  optional.\n\n<a name=\"end-async\"></a>\n\n### mqtt.Client#endAsync([force], [options])\n\nAsync [`end`](#end). Returns a `Promise<void>`.\n\n---\n\n<a name=\"removeOutgoingMessage\"></a>\n\n### mqtt.Client#removeOutgoingMessage(mId)\n\nRemove a message from the outgoingStore.\nThe outgoing callback will be called with Error('Message removed') if the message is removed.\n\nAfter this function is called, the messageId is released and becomes reusable.\n\n- `mId`: The messageId of the message in the outgoingStore.\n\n---\n\n<a name=\"reconnect\"></a>\n\n### mqtt.Client#reconnect()\n\nConnect again using the same options as connect()\n\n---\n\n<a name=\"handleMessage\"></a>\n\n### mqtt.Client#handleMessage(packet, callback)\n\nHandle messages with backpressure support, one at a time.\nOverride at will, but **always call `callback`**, or the client\nwill hang.\n\n---\n\n<a name=\"connected\"></a>\n\n### mqtt.Client#connected\n\nBoolean : set to `true` if the client is connected. `false` otherwise.\n\n---\n\n<a name=\"getLastMessageId\"></a>\n\n### mqtt.Client#getLastMessageId()\n\nNumber : get last message id. This is for sent messages only.\n\n---\n\n<a name=\"reconnecting\"></a>\n\n### mqtt.Client#reconnecting\n\nBoolean : set to `true` if the client is trying to reconnect to the server. `false` otherwise.\n\n---\n\n<a name=\"store\"></a>\n\n### mqtt.Store(options)\n\nIn-memory implementation of the message store.\n\n- `options` is the store options:\n  - `clean`: `true`, clean inflight messages when close is called (default `true`)\n\nOther implementations of `mqtt.Store`:\n\n- [mqtt-jsonl-store](https://github.com/robertsLando/mqtt-jsonl-store) which uses\n  [jsonl-db](https://github.com/AlCalzone/jsonl-db) to store inflight data, it works only on Node.\n- [mqtt-level-store](http://npm.im/mqtt-level-store) which uses\n  [Level-browserify](http://npm.im/level-browserify) to store the inflight\n  data, making it usable both in Node and the Browser.\n- [mqtt-nedb-store](https://github.com/behrad/mqtt-nedb-store) which\n  uses [nedb](https://www.npmjs.com/package/nedb) to store the inflight\n  data.\n- [mqtt-localforage-store](http://npm.im/mqtt-localforage-store) which uses\n  [localForage](http://npm.im/localforage) to store the inflight\n  data, making it usable in the Browser without browserify.\n\n---\n\n<a name=\"put\"></a>\n\n### mqtt.Store#put(packet, callback)\n\nAdds a packet to the store, a packet is\nanything that has a `messageId` property.\nThe callback is called when the packet has been stored.\n\n---\n\n<a name=\"createStream\"></a>\n\n### mqtt.Store#createStream()\n\nCreates a stream with all the packets in the store.\n\n---\n\n<a name=\"del\"></a>\n\n### mqtt.Store#del(packet, cb)\n\nRemoves a packet from the store, a packet is\nanything that has a `messageId` property.\nThe callback is called when the packet has been removed.\n\n---\n\n<a name=\"close\"></a>\n\n### mqtt.Store#close(cb)\n\nCloses the Store.\n\n<a name=\"browser\"></a>\n<a name=\"webpack\"></a>\n<a name=\"vite\"></a>\n\n## Browser\n\n> [!IMPORTANT]\n> The only protocol supported in browsers is MQTT over WebSockets, so you must use `ws://` or `wss://` protocols.\n\nWhile the [ws](https://www.npmjs.com/package/ws) module is used in NodeJS, [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) is used in browsers. This is totally transparent to users except for the following:\n\n- The `wsOption` is not supported in browsers.\n- Browsers doesn't allow to catch many WebSocket errors for [security reasons](https://stackoverflow.com/a/31003057) as:\n\n  > Access to this information could allow a malicious Web page to gain information about your network, so they require browsers report all connection-time errors in an indistinguishable way.\n\n  So listening for `client.on('error')` may not catch all the errors you would get in NodeJS env.\n\n### Bundle\n\nMQTT.js is bundled using [esbuild](https://esbuild.github.io/). It is tested working with all bundlers like Webpack, Vite and React.\n\nYou can find all mqtt bundles versions in `dist` folder:\n\n- `mqtt.js` - iife format, not minified\n- `mqtt.min.js` - iife format, minified\n- `mqtt.esm.js` - esm format minified\n\nStarting from MQTT.js > 5.2.0 you can import mqtt in your code like this:\n\n```js\nimport mqtt from 'mqtt'\n```\n\nThis will be automatically handled by your bundler.\n\nOtherwise you can choose to use a specific bundle like:\n\n```js\nimport * as mqtt from 'mqtt/dist/mqtt'\nimport * as mqtt from 'mqtt/dist/mqtt.min'\nimport mqtt from 'mqtt/dist/mqtt.esm'\n```\n\n<a name=\"cdn\"></a>\n\n### Via CDN\n\nThe MQTT.js bundle is available through <http://unpkg.com>, specifically\nat <https://unpkg.com/mqtt/dist/mqtt.min.js>.\nSee <http://unpkg.com> for the full documentation on version ranges.\n\n<a name=\"qos\"></a>\n\n## About QoS\n\nHere is how QoS works:\n\n- QoS 0 : received **at most once** : The packet is sent, and that's it. There is no validation about whether it has been received.\n- QoS 1 : received **at least once** : The packet is sent and stored as long as the client has not received a confirmation from the server. MQTT ensures that it _will_ be received, but there can be duplicates.\n- QoS 2 : received **exactly once** : Same as QoS 1 but there is no duplicates.\n\nAbout data consumption, obviously, QoS 2 > QoS 1 > QoS 0, if that's a concern to you.\n\n<a name=\"typescript\"></a>\n\n## Usage with TypeScript\n\nStarting from v5 this project is written in TypeScript and the type definitions are included in the package.\n\nExample:\n\n```ts\nimport { connect } from \"mqtt\"\nconst client = connect('mqtt://test.mosquitto.org')\n```\n\n<a name=\"weapp-alipay\"></a>\n\n## WeChat and Ali Mini Program support\n\n### WeChat Mini Program\n\nSupports [WeChat Mini Program](https://mp.weixin.qq.com/). Use the `wxs` protocol. See [the WeChat docs](https://mp.weixin.qq.com/debug/wxadoc/dev/api/network-socket.html).\n\n```js\nimport 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only' // import before mqtt.\nimport 'esbuild-plugin-polyfill-node/polyfills/navigator'\nconst mqtt = require(\"mqtt\");\nconst client = mqtt.connect(\"wxs://test.mosquitto.org\", {\n  timerVariant: 'native' // more info ref issue: #1797\n});\n```\n\n### Ali Mini Program\n\nSupports [Ali Mini Program](https://open.alipay.com/channel/miniIndex.htm). Use the `alis` protocol. See [the Alipay docs](https://docs.alipay.com/mini/developer/getting-started).\n<a name=\"example\"></a>\n\n```js\nconst mqtt = require(\"mqtt\");\nconst client = mqtt.connect(\"alis://test.mosquitto.org\");\n```\n\n<a name=\"contributing\"></a>\n\n## Contributing\n\nMQTT.js is an **OPEN Open Source Project**. This means that:\n\n> Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.\n\nSee the [CONTRIBUTING.md](https://github.com/mqttjs/MQTT.js/blob/master/CONTRIBUTING.md) file for more details.\n\n### Contributors\n\nMQTT.js is only possible due to the excellent work of the following contributors:\n\n| Name               | GitHub                                             | Twitter                                                    |\n| ------------------ | -------------------------------------------------- | ---------------------------------------------------------- |\n| Adam Rudd          | [GitHub/adamvr](https://github.com/adamvr)         | [Twitter/@adam_vr](http://twitter.com/adam_vr)             |\n| Matteo Collina     | [GitHub/mcollina](https://github.com/mcollina)     | [Twitter/@matteocollina](http://twitter.com/matteocollina) |\n| Maxime Agor        | [GitHub/4rzael](https://github.com/4rzael)         | [Twitter/@4rzael](http://twitter.com/4rzael)               |\n| Siarhei Buntsevich | [GitHub/scarry1992](https://github.com/scarry1992) |                                                            |\n| Daniel Lando    | [GitHub/robertsLando](https://github.com/robertsLando) |                                                           |\n\n<a name=\"sponsor\"></a>\n\n## Sponsor\n\nIf you would like to support MQTT.js, please consider sponsoring the author and active maintainers:\n\n- [Matteo Collina](https://github.com/sponsors/mcollina): author of MQTT.js\n- [Daniel Lando](https://github.com/sponsors/robertsLando): active maintainer\n\n<a name=\"license\"></a>\n\n## License\n\nMIT\n"
  },
  {
    "path": "benchmarks/bombing.js",
    "content": "#! /usr/bin/env node\n\nconst mqtt = require('..')\n\nconst client = mqtt.connect({\n\tport: 1883,\n\thost: 'localhost',\n\tclean: true,\n\tkeepalive: 0,\n})\n\nlet sent = 0\nconst interval = 5000\n\nfunction count() {\n\tconsole.log('sent/s', (sent / interval) * 1000)\n\tsent = 0\n}\n\nsetInterval(count, interval)\n\nfunction publish() {\n\tsent++\n\tclient.publish('test', 'payload', publish)\n}\n\nclient.on('connect', publish)\n\nclient.on('error', () => {\n\tconsole.log('reconnect!')\n\tclient.stream.end()\n})\n"
  },
  {
    "path": "benchmarks/throughputCounter.js",
    "content": "#! /usr/bin/env node\n\nconst mqtt = require('..')\n\nconst client = mqtt.connect({\n\tport: 1883,\n\thost: 'localhost',\n\tclean: true,\n\tencoding: 'binary',\n\tkeepalive: 0,\n})\nlet counter = 0\nconst interval = 5000\n\nfunction count() {\n\tconsole.log('received/s', (counter / interval) * 1000)\n\tcounter = 0\n}\n\nsetInterval(count, interval)\n\nclient.on('connect', () => {\n\tcount()\n\tthis.subscribe('test')\n\tthis.on('message', () => {\n\t\tcounter++\n\t})\n})\n"
  },
  {
    "path": "electron-test/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n.DS_Store\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n\n# next.js build output\n.next\n\n# nuxt.js build output\n.nuxt\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# Webpack\n.webpack/\n\n# Vite\n.vite/\n\n# Electron-Forge\nout/\n"
  },
  {
    "path": "electron-test/README",
    "content": "```\nnpm i\nnpm run wdio\n```\n"
  },
  {
    "path": "electron-test/forge.config.js",
    "content": "const { FusesPlugin } = require('@electron-forge/plugin-fuses');\nconst { FuseV1Options, FuseVersion } = require('@electron/fuses');\n\nmodule.exports = {\n  packagerConfig: {\n    asar: true,\n  },\n  rebuildConfig: {},\n  makers: [\n    {\n      name: '@electron-forge/maker-zip',\n      platforms: ['linux'],\n    },\n  ],\n  plugins: [\n    {\n      name: '@electron-forge/plugin-auto-unpack-natives',\n      config: {},\n    },\n    // Fuses are used to enable/disable various Electron functionality\n    // at package time, before code signing the application\n    new FusesPlugin({\n      version: FuseVersion.V1,\n      [FuseV1Options.RunAsNode]: false,\n      [FuseV1Options.EnableCookieEncryption]: true,\n      [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,\n      [FuseV1Options.EnableNodeCliInspectArguments]: false,\n      [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,\n      [FuseV1Options.OnlyLoadAppFromAsar]: true,\n    }),\n  ],\n};\n"
  },
  {
    "path": "electron-test/package.json",
    "content": "{\n  \"name\": \"electron-test\",\n  \"productName\": \"electron-test\",\n  \"version\": \"1.0.0\",\n  \"description\": \"My Electron application description\",\n  \"main\": \"src/index.js\",\n  \"scripts\": {\n    \"start\": \"electron-forge start\",\n    \"package\": \"electron-forge package\",\n    \"make\": \"electron-forge make\",\n    \"publish\": \"electron-forge publish\",\n    \"lint\": \"echo \\\"No linting configured\\\"\",\n    \"wdio\": \"npm run package && wdio run ./wdio.conf.ts\"\n  },\n  \"devDependencies\": {\n    \"@electron-forge/cli\": \"7.6.1\",\n    \"@electron-forge/maker-zip\": \"7.6.1\",\n    \"@electron-forge/plugin-auto-unpack-natives\": \"7.6.1\",\n    \"@electron-forge/plugin-fuses\": \"7.6.1\",\n    \"@electron/fuses\": \"1.8.0\",\n    \"@wdio/cli\": \"9.9.1\",\n    \"@wdio/local-runner\": \"9.9.1\",\n    \"@wdio/mocha-framework\": \"9.9.0\",\n    \"@wdio/spec-reporter\": \"9.9.0\",\n    \"aedes-cli\": \"0.8.0\",\n    \"electron\": \"34.2.0\",\n    \"typescript\": \"^5.4.5\",\n    \"wdio-electron-service\": \"8.0.0\"\n  },\n  \"keywords\": [],\n  \"author\": {\n    \"name\": \"axi92\"\n  },\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"jquery\": \"3.7.1\"\n  }\n}\n"
  },
  {
    "path": "electron-test/src/index.css",
    "content": "body {\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,\n    Arial, sans-serif;\n  margin: auto;\n  max-width: 38rem;\n  padding: 2rem;\n}\n"
  },
  {
    "path": "electron-test/src/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>Hello World!</title>\n    <link rel=\"stylesheet\" href=\"index.css\" />\n  </head>\n  <body>\n    <h1>💖 Hello World!</h1>\n    <p>Welcome to your Electron application.</p>\n    Mqtt connection status: <a id=\"status\">offline</a><br>\n    Protocol: <a id=\"protocol\"></a>\n  </body>\n  <script>\n    require('./renderer.js');\n  </script>\n</html>\n"
  },
  {
    "path": "electron-test/src/index.js",
    "content": "const { app, BrowserWindow } = require('electron');\nconst path = require('node:path');\n\nconst createWindow = () => {\n\t// Create the browser window.\n\tconst mainWindow = new BrowserWindow({\n\t\twidth: 800,\n\t\theight: 600,\n\t\tshow: true,\n\t\twebPreferences: {\n\t\t\tsandbox: false,\n\t\t\tnodeIntegration: true,\n\t\t\tcontextIsolation: false,\n\t\t},\n\t});\n\n\t// and load the index.html of the app.\n\tmainWindow.loadFile(path.join(__dirname, 'index.html'));\n\n\t// Open the DevTools.\n\t// mainWindow.webContents.openDevTools();\n};\n\n// This method will be called when Electron has finished\n// initialization and is ready to create browser windows.\n// Some APIs can only be used after this event occurs.\napp.whenReady().then(() => {\n\tcreateWindow();\n\n\t// On OS X it's common to re-create a window in the app when the\n\t// dock icon is clicked and there are no other windows open.\n\tapp.on('activate', () => {\n\t\tif (BrowserWindow.getAllWindows().length === 0) {\n\t\t\tcreateWindow();\n\t\t}\n\t});\n});\n\n// Quit when all windows are closed, except on macOS. There, it's common\n// for applications and their menu bar to stay active until the user quits\n// explicitly with Cmd + Q.\napp.on('window-all-closed', () => {\n\tif (process.platform !== 'darwin') {\n\t\tapp.quit();\n\t}\n});\n"
  },
  {
    "path": "electron-test/src/renderer.js",
    "content": "window.$ = window.jQuery = require('jquery')\nconst path = require('node:path');\nconst mqtt = require(path.join(process.cwd(), '../build/mqtt'))\n\nconsole.log('start connecting...')\nconst client = mqtt.connect({\n  protocol: 'mqtt',\n  port: 1883,\n})\n\nclient.on('connect', () => {\n  $('#status').text('online')\n  $('#protocol').text(client.options.protocol)\n})\n"
  },
  {
    "path": "electron-test/test/service/server_launcher.ts",
    "content": "import type { Services } from '@wdio/types'\nimport { resolve as pathResolve } from 'path'\nconst { start } = require('aedes-cli')\n\n\nexport default class ServerLauncher implements Services.ServiceInstance {\n\n    #aedesBroker: any\n\n    constructor() {\n        this.#aedesBroker = null\n    }\n\n    async onPrepare(): Promise<void> {\n        const keyPath = pathResolve(__dirname, '../../../test/browser/certs/server-key.pem')\n        const certPath = pathResolve(__dirname, '../../../test/browser/certs/server-cert.pem')\n\n        this.#aedesBroker = await start({\n            protos: ['tcp', 'tls'],\n            port: 1883,\n            tlsPort: 8883,\n            key: keyPath,\n            cert: certPath,\n            verbose: true,\n            stats: false,\n        })\n    }\n\n    async onComplete(): Promise<void> {\n        if (!this.#aedesBroker?.servers) {\n            return\n        }\n\n        for (const server of this.#aedesBroker.servers) {\n            if (server.listening) {\n                await new Promise<void>((resolve, reject) => {\n                    server.close((err: any) => {\n                        if (err)\n                            reject(err)\n                        else\n                            resolve()\n                    })\n                })\n            }\n        }\n    }\n}"
  },
  {
    "path": "electron-test/test/specs/test.e2e.ts",
    "content": "import { expect, $ } from '@wdio/globals'\n// import { join } from 'path'\nimport isBrowser from '../../../build/lib/is-browser'\n// import isBrowser from join(process.cwd(), '../build/lib/is-browser')\n\ndescribe('Electron Testing', () => {\n    it('should render electron window', async () => {\n        await expect($('h1')).toHaveText('💖 Hello World!')\n    })\n\n    it('should connect', async() => {\n      await expect($('#status')).toHaveText('online')\n    })\n\n    it('should not be a browser context', async() =>{\n      expect(isBrowser).toBe(false)\n    })\n\n    it('should use protocoll mqtt', async() => {\n      await expect($('#protocol')).toHaveText('mqtt')\n    })\n})\n\n"
  },
  {
    "path": "electron-test/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"moduleResolution\": \"node\",\n        \"module\": \"commonjs\",\n        \"target\": \"es2022\",\n        \"lib\": [\n            \"es2022\",\n            \"dom\"\n        ],\n        \"types\": [\n            \"node\",\n            \"@wdio/globals/types\",\n            \"expect-webdriverio\",\n            \"@wdio/mocha-framework\",\n            \"wdio-electron-service\"\n        ],\n        \"skipLibCheck\": true,\n        \"noEmit\": true,\n        \"allowImportingTsExtensions\": true,\n        \"resolveJsonModule\": true,\n        \"isolatedModules\": true,\n        \"strict\": true,\n        \"noUnusedLocals\": true,\n        \"noUnusedParameters\": true,\n        \"noFallthroughCasesInSwitch\": true\n    },\n    \"include\": [\n        \"test\",\n        \"wdio.conf.ts\"\n    ]\n}"
  },
  {
    "path": "electron-test/wdio.conf.ts",
    "content": "import { resolve as pathResolve } from 'node:path';\nimport ServerLauncher from './test/service/server_launcher'\n\nconst electronAppBinaryPath = pathResolve('./out/electron-test-linux-x64/electron-test');\n\nexport const config: WebdriverIO.Config = {\n    //\n    // ====================\n    // Runner Configuration\n    // ====================\n    // WebdriverIO supports running e2e tests as well as unit and component tests.\n    runner: 'local',\n    tsConfigPath: './tsconfig.json',\n\n    //\n    // ==================\n    // Specify Test Files\n    // ==================\n    // Define which test specs should run. The pattern is relative to the directory\n    // of the configuration file being run.\n    //\n    // The specs are defined as an array of spec files (optionally using wildcards\n    // that will be expanded). The test for each spec file will be run in a separate\n    // worker process. In order to have a group of spec files run in the same worker\n    // process simply enclose them in an array within the specs array.\n    //\n    // The path of the spec files will be resolved relative from the directory of\n    // of the config file unless it's absolute.\n    //\n    specs: [\n        './test/specs/**/*.ts'\n    ],\n    // Patterns to exclude.\n    exclude: [\n        // 'path/to/excluded/files'\n    ],\n    //\n    // ============\n    // Capabilities\n    // ============\n    // Define your capabilities here. WebdriverIO can run multiple capabilities at the same\n    // time. Depending on the number of capabilities, WebdriverIO launches several test\n    // sessions. Within your capabilities you can overwrite the spec and exclude options in\n    // order to group specific specs to a specific capability.\n    //\n    // First, you can define how many instances should be started at the same time. Let's\n    // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have\n    // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec\n    // files and you set maxInstances to 10, all spec files will get tested at the same time\n    // and 30 processes will get spawned. The property handles how many capabilities\n    // from the same test should run tests.\n    //\n    maxInstances: 10,\n    //\n    // If you have trouble getting all important capabilities together, check out the\n    // Sauce Labs platform configurator - a great tool to configure your capabilities:\n    // https://saucelabs.com/platform/platform-configurator\n    //\n    capabilities: [{\n        browserName: 'electron',\n        // Electron service options\n        // see https://webdriver.io/docs/desktop-testing/electron/configuration/#service-options\n        'wdio:electronServiceOptions': {\n            appBinaryPath: electronAppBinaryPath,\n        }\n    }],\n\n    //\n    // ===================\n    // Test Configurations\n    // ===================\n    // Define all options that are relevant for the WebdriverIO instance here\n    //\n    // Level of logging verbosity: trace | debug | info | warn | error | silent\n    logLevel: 'info',\n    //\n    // Set specific log levels per logger\n    // loggers:\n    // - webdriver, webdriverio\n    // - @wdio/browserstack-service, @wdio/lighthouse-service, @wdio/sauce-service\n    // - @wdio/mocha-framework, @wdio/jasmine-framework\n    // - @wdio/local-runner\n    // - @wdio/sumologic-reporter\n    // - @wdio/cli, @wdio/config, @wdio/utils\n    // Level of logging verbosity: trace | debug | info | warn | error | silent\n    // logLevels: {\n    //     webdriver: 'info',\n    //     '@wdio/appium-service': 'info'\n    // },\n    //\n    // If you only want to run your tests until a specific amount of tests have failed use\n    // bail (default is 0 - don't bail, run all tests).\n    bail: 0,\n    //\n    // Set a base URL in order to shorten url command calls. If your `url` parameter starts\n    // with `/`, the base url gets prepended, not including the path portion of your baseUrl.\n    // If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url\n    // gets prepended directly.\n    // baseUrl: 'http://localhost:8080',\n    //\n    // Default timeout for all waitFor* commands.\n    waitforTimeout: 10000,\n    //\n    // Default timeout in milliseconds for request\n    // if browser driver or grid doesn't send response\n    connectionRetryTimeout: 120000,\n    //\n    // Default request retries count\n    connectionRetryCount: 3,\n    //\n    // Test runner services\n    // Services take over a specific job you don't want to take care of. They enhance\n    // your test setup with almost no effort. Unlike plugins, they don't add new\n    // commands. Instead, they hook themselves up into the test process.\n    services: [\n        [ServerLauncher, {}],\n        'electron',\n    ],\n\n    // Framework you want to run your specs with.\n    // The following are supported: Mocha, Jasmine, and Cucumber\n    // see also: https://webdriver.io/docs/frameworks\n    //\n    // Make sure you have the wdio adapter package for the specific framework installed\n    // before running any tests.\n    framework: 'mocha',\n\n    //\n    // The number of times to retry the entire specfile when it fails as a whole\n    // specFileRetries: 1,\n    //\n    // Delay in seconds between the spec file retry attempts\n    // specFileRetriesDelay: 0,\n    //\n    // Whether or not retried spec files should be retried immediately or deferred to the end of the queue\n    // specFileRetriesDeferred: false,\n    //\n    // Test reporter for stdout.\n    // The only one supported by default is 'dot'\n    // see also: https://webdriver.io/docs/dot-reporter\n    reporters: ['spec'],\n\n    // Options to be passed to Mocha.\n    // See the full list at http://mochajs.org/\n    mochaOpts: {\n        ui: 'bdd',\n        timeout: 60000\n    },\n\n    //\n    // =====\n    // Hooks\n    // =====\n    // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance\n    // it and to build services around it. You can either apply a single function or an array of\n    // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got\n    // resolved to continue.\n    /**\n     * Gets executed once before all workers get launched.\n     * @param {object} config wdio configuration object\n     * @param {Array.<Object>} capabilities list of capabilities details\n     */\n    // onPrepare: function (config, capabilities) {\n    // },\n    /**\n     * Gets executed before a worker process is spawned and can be used to initialize specific service\n     * for that worker as well as modify runtime environments in an async fashion.\n     * @param  {string} cid      capability id (e.g 0-0)\n     * @param  {object} caps     object containing capabilities for session that will be spawn in the worker\n     * @param  {object} specs    specs to be run in the worker process\n     * @param  {object} args     object that will be merged with the main configuration once worker is initialized\n     * @param  {object} execArgv list of string arguments passed to the worker process\n     */\n    // onWorkerStart: function (cid, caps, specs, args, execArgv) {\n    // },\n    /**\n     * Gets executed just after a worker process has exited.\n     * @param  {string} cid      capability id (e.g 0-0)\n     * @param  {number} exitCode 0 - success, 1 - fail\n     * @param  {object} specs    specs to be run in the worker process\n     * @param  {number} retries  number of retries used\n     */\n    // onWorkerEnd: function (cid, exitCode, specs, retries) {\n    // },\n    /**\n     * Gets executed just before initialising the webdriver session and test framework. It allows you\n     * to manipulate configurations depending on the capability or spec.\n     * @param {object} config wdio configuration object\n     * @param {Array.<Object>} capabilities list of capabilities details\n     * @param {Array.<String>} specs List of spec file paths that are to be run\n     * @param {string} cid worker id (e.g. 0-0)\n     */\n    // beforeSession: function (config, capabilities, specs, cid) {\n    // },\n    /**\n     * Gets executed before test execution begins. At this point you can access to all global\n     * variables like `browser`. It is the perfect place to define custom commands.\n     * @param {Array.<Object>} capabilities list of capabilities details\n     * @param {Array.<String>} specs        List of spec file paths that are to be run\n     * @param {object}         browser      instance of created browser/device session\n     */\n    // before: function (capabilities, specs) {\n    // },\n    /**\n     * Runs before a WebdriverIO command gets executed.\n     * @param {string} commandName hook command name\n     * @param {Array} args arguments that command would receive\n     */\n    // beforeCommand: function (commandName, args) {\n    // },\n    /**\n     * Hook that gets executed before the suite starts\n     * @param {object} suite suite details\n     */\n    // beforeSuite: function (suite) {\n    // },\n    /**\n     * Function to be executed before a test (in Mocha/Jasmine) starts.\n     */\n    // beforeTest: function (test, context) {\n    // },\n    /**\n     * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling\n     * beforeEach in Mocha)\n     */\n    // beforeHook: function (test, context, hookName) {\n    // },\n    /**\n     * Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling\n     * afterEach in Mocha)\n     */\n    // afterHook: function (test, context, { error, result, duration, passed, retries }, hookName) {\n    // },\n    /**\n     * Function to be executed after a test (in Mocha/Jasmine only)\n     * @param {object}  test             test object\n     * @param {object}  context          scope object the test was executed with\n     * @param {Error}   result.error     error object in case the test fails, otherwise `undefined`\n     * @param {*}       result.result    return object of test function\n     * @param {number}  result.duration  duration of test\n     * @param {boolean} result.passed    true if test has passed, otherwise false\n     * @param {object}  result.retries   information about spec related retries, e.g. `{ attempts: 0, limit: 0 }`\n     */\n    // afterTest: function(test, context, { error, result, duration, passed, retries }) {\n    // },\n\n\n    /**\n     * Hook that gets executed after the suite has ended\n     * @param {object} suite suite details\n     */\n    // afterSuite: function (suite) {\n    // },\n    /**\n     * Runs after a WebdriverIO command gets executed\n     * @param {string} commandName hook command name\n     * @param {Array} args arguments that command would receive\n     * @param {number} result 0 - command success, 1 - command error\n     * @param {object} error error object if any\n     */\n    // afterCommand: function (commandName, args, result, error) {\n    // },\n    /**\n     * Gets executed after all tests are done. You still have access to all global variables from\n     * the test.\n     * @param {number} result 0 - test pass, 1 - test fail\n     * @param {Array.<Object>} capabilities list of capabilities details\n     * @param {Array.<String>} specs List of spec file paths that ran\n     */\n    // after: function (result, capabilities, specs) {\n    // },\n    /**\n     * Gets executed right after terminating the webdriver session.\n     * @param {object} config wdio configuration object\n     * @param {Array.<Object>} capabilities list of capabilities details\n     * @param {Array.<String>} specs List of spec file paths that ran\n     */\n    // afterSession: function (config, capabilities, specs) {\n    // },\n    /**\n     * Gets executed after all workers got shut down and the process is about to exit. An error\n     * thrown in the onComplete hook will result in the test run failing.\n     * @param {object} exitCode 0 - success, 1 - fail\n     * @param {object} config wdio configuration object\n     * @param {Array.<Object>} capabilities list of capabilities details\n     * @param {<Object>} results object containing test results\n     */\n    // onComplete: function(exitCode, config, capabilities, results) {\n    // },\n    /**\n    * Gets executed when a refresh happens.\n    * @param {string} oldSessionId session ID of the old session\n    * @param {string} newSessionId session ID of the new session\n    */\n    // onReload: function(oldSessionId, newSessionId) {\n    // }\n    /**\n    * Hook that gets executed before a WebdriverIO assertion happens.\n    * @param {object} params information about the assertion to be executed\n    */\n    // beforeAssertion: function(params) {\n    // }\n    /**\n    * Hook that gets executed after a WebdriverIO assertion happened.\n    * @param {object} params information about the assertion that was executed, including its results\n    */\n    // afterAssertion: function(params) {\n    // }\n}\n"
  },
  {
    "path": "esbuild.js",
    "content": "const { build } = require('esbuild')\nconst { polyfillNode } = require('esbuild-plugin-polyfill-node');\nconst { rimraf } = require('rimraf')\nconst fs = require('fs')\nconst { version } = require('./package.json');\n\nconst outdir = 'dist'\n\n/**\n * @type {import('esbuild').BuildOptions}\n */\nconst options = {\n    entryPoints: ['build/index.js'],\n    bundle: true,\n    outfile: `${outdir}/mqtt.js`,\n    format: 'iife',\n    platform: 'browser',\n    globalName: 'mqtt',\n    sourcemap: false, // this can be enabled while debugging, if we decide to keep this enabled we should also ship the `src` folder to npm\n    plugins: [\n        polyfillNode({\n            polyfills: [\n                'readable-stream'\n            ]\n        }),\n        {\n            name: 'resolve-package-json',\n            setup(build) {\n                // when importing 'package.json' we want to provide a custom object like { version: '1.2.3' }\n\n                build.onResolve({ filter: /package\\.json$/ }, args => {\n                    return {\n                        path: args.path,\n                        namespace: 'package-json'\n                    }\n                })\n\n                build.onLoad({ filter: /.*/, namespace: 'package-json' }, args => {\n                    return {\n                        contents: JSON.stringify({ version }),\n                        loader: 'json'\n                    }\n                }\n                )\n            }\n        },\n        {\n            name: 'resolve-socks',\n            setup(build) {\n                // socks is not supported in the browser and adds several 100kb to the build, so stub it\n                build.onResolve({ filter: /socks$/ }, args => {\n                    return {\n                        path: args.path,\n                        namespace: 'socks-stub'\n                    }\n                })\n\n                build.onLoad({ filter: /.*/, namespace: 'socks-stub' }, args => {\n                    return {\n                        contents: 'module.exports = {}',\n                        loader: 'js'\n                    }\n                }\n                )\n            }\n        },\n    ],\n}\n\nasync function run() {\n    const start = Date.now()\n    await rimraf(outdir)\n    await build(options)\n\n    options.minify = true\n    options.outfile = `${outdir}/mqtt.min.js`\n    await build(options)\n\n\n    options.outfile = `${outdir}/mqtt.esm.js`\n    options.format = 'esm'\n\n    await build(options)\n\n    console.log(`Build time: ${Date.now() - start}ms`)\n    console.log('Build output:')\n\n    // log generated files with their size in KB\n    const files = fs.readdirSync(outdir)\n    for (const file of files) {\n        const stat = fs.statSync(`${outdir}/${file}`)\n        console.log(`- ${file} ${Math.round(stat.size / 1024 * 100) / 100} KB`)\n    }\n}\n\nrun().catch((e) => {\n    console.error(e)\n    process.exit(1)\n})"
  },
  {
    "path": "eslint.config.js",
    "content": "const {\n    defineConfig,\n    globalIgnores,\n} = require(\"eslint/config\");\n\nconst tsParser = require(\"@typescript-eslint/parser\");\nconst typescriptEslintEslintPlugin = require(\"@typescript-eslint/eslint-plugin\");\nconst globals = require(\"globals\");\nconst js = require(\"@eslint/js\");\n\nconst {\n    FlatCompat,\n} = require(\"@eslint/eslintrc\");\n\nconst compat = new FlatCompat({\n    baseDirectory: __dirname,\n    recommendedConfig: js.configs.recommended,\n    allConfig: js.configs.all\n});\n\nmodule.exports = defineConfig([{\n    languageOptions: {\n        parser: tsParser,\n\n        globals: {\n            ...globals.browser,\n            ...globals.commonjs,\n            ...globals.node,\n            ...globals.worker,\n        },\n\n        sourceType: \"module\",\n\n        parserOptions: {\n            project: \"tsconfig.json\",\n            tsconfigRootDir: __dirname,\n        },\n    },\n\n    plugins: {\n        \"@typescript-eslint\": typescriptEslintEslintPlugin,\n    },\n\n    extends: compat.extends(\"plugin:prettier/recommended\", \"plugin:@typescript-eslint/recommended\"),\n\n    rules: {\n        \"global-require\": \"off\",\n        \"no-console\": process.env.NODE_ENV === \"production\" ? \"warn\" : \"off\",\n\n        \"no-unused-vars\": [\"error\", {\n            args: \"none\",\n        }],\n\n        \"no-underscore-dangle\": \"off\",\n        \"no-param-reassign\": \"off\",\n        \"no-restricted-syntax\": \"off\",\n        \"default-case\": \"off\",\n        \"consistent-return\": \"off\",\n        \"max-classes-per-file\": \"off\",\n        \"no-plusplus\": \"off\",\n        \"no-bitwise\": \"off\",\n        \"class-methods-use-this\": \"off\",\n        \"no-continue\": \"off\",\n        \"@typescript-eslint/no-explicit-any\": \"off\",\n\n        \"@typescript-eslint/no-unused-vars\": [\"error\", {\n            args: \"none\",\n        }],\n\n        \"@typescript-eslint/naming-convention\": \"off\",\n        \"@typescript-eslint/dot-notation\": \"off\",\n        \"@typescript-eslint/no-use-before-define\": \"off\",\n\n        \"@typescript-eslint/consistent-type-imports\": [\"error\", {\n            \"prefer\": \"type-imports\",\n            \"fixStyle\": \"inline-type-imports\",\n            \"disallowTypeAnnotations\": true,\n        }],\n    },\n}, globalIgnores([\n    \"types/\",\n    \"examples/\",\n    \"doc/\",\n    \"dist/\",\n    \"build/\",\n    \"electron-test/\",\n    \"**/*.js\",\n    \"**/*.mjs\",\n])]);\n"
  },
  {
    "path": "example.ts",
    "content": "import mqtt from './src/index'\n\nconst client = mqtt.connect('mqtt://broker.hivemq.com', {\n\tkeepalive: 3,\n\tport: 1883,\n\treconnectPeriod: 15000,\n\trejectUnauthorized: false,\n})\n\nconst randomNumber = Math.floor(Math.random() * 1000)\n\nconst testTopic = `presence_${randomNumber.toString()}`\n\nfunction publish() {\n\tconst msg = `Hello mqtt ${new Date().toISOString()}`\n\tclient.publish(testTopic, msg, { qos: 1 }, (err2) => {\n\t\tif (!err2) {\n\t\t\tconsole.log('message published')\n\t\t} else {\n\t\t\tconsole.error(err2)\n\t\t}\n\t})\n}\n\nclient.subscribe(testTopic, (err) => {\n\tif (!err) {\n\t\tconsole.log('subscribed to', testTopic)\n\t} else {\n\t\tconsole.error(err)\n\t}\n})\n\nclient.on('message', (topic, message) => {\n\tconsole.log('received message \"%s\" from topic \"%s\"', message, topic)\n})\n\nsetInterval(() => {\n\tpublish()\n}, 2000)\n\nclient.on('error', (err) => {\n\tconsole.error(err)\n})\n\nclient.on('connect', () => {\n\tconsole.log('connected')\n\tpublish()\n})\n\nclient.on('disconnect', () => {\n\tconsole.log('disconnected')\n})\n\nclient.on('offline', () => {\n\tconsole.log('offline')\n})\n\nclient.on('reconnect', () => {\n\tconsole.log('reconnect')\n})\n"
  },
  {
    "path": "examples/client/secure-client.js",
    "content": "'use strict'\n\nconst mqtt = require('../..')\nconst path = require('path')\nconst fs = require('fs')\nconst KEY = fs.readFileSync(path.join(__dirname, '..', '..', 'test', 'helpers', 'tls-key.pem'))\nconst CERT = fs.readFileSync(path.join(__dirname, '..', '..', 'test', 'helpers', 'tls-cert.pem'))\n\nconst PORT = 8443\n\nconst options = {\n  port: PORT,\n  key: KEY,\n  cert: CERT,\n  rejectUnauthorized: false\n}\n\nconst client = mqtt.connect(options)\n\nclient.subscribe('messages')\nclient.publish('messages', 'Current time is: ' + new Date())\nclient.on('message', function (topic, message) {\n  console.log(message)\n})\n"
  },
  {
    "path": "examples/client/simple-both.js",
    "content": "'use strict'\n\nconst mqtt = require('../..')\nconst client = mqtt.connect()\n\n// or const client = mqtt.connect({ port: 1883, host: '192.168.1.100', keepalive: 10000});\n\nclient.subscribe('presence')\nclient.publish('presence', 'bin hier')\nclient.on('message', function (topic, message) {\n  console.log(message)\n})\nclient.end()\n"
  },
  {
    "path": "examples/client/simple-publish.js",
    "content": "'use strict'\n\nconst mqtt = require('../..')\nconst client = mqtt.connect()\n\nclient.publish('presence', 'hello!')\nclient.end()\n"
  },
  {
    "path": "examples/client/simple-subscribe.js",
    "content": "'use strict'\n\nconst mqtt = require('../..')\nconst client = mqtt.connect()\n\nclient.subscribe('presence')\nclient.on('message', function (topic, message) {\n  console.log(message)\n})\n"
  },
  {
    "path": "examples/tls client/crt.ca.cg.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIF7zCCA9egAwIBAgIJAOeJR1p1PU3qMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYD\nVQQGEwJFUzERMA8GA1UECAwIWmFyYWdvemExETAPBgNVBAcMCFphcmFnb3phMRkw\nFwYDVQQKDBBNUVRUIGZvciBub2RlLmpzMRAwDgYDVQQLDAdNUVRULmpzMQ0wCwYD\nVQQDDARtcXR0MRwwGgYJKoZIhvcNAQkBFg1mYWtlQG1haWwuY29tMB4XDTEzMDgz\nMDEzMDIwNVoXDTIzMDgyODEzMDIwNVowgY0xCzAJBgNVBAYTAkVTMREwDwYDVQQI\nDAhaYXJhZ296YTERMA8GA1UEBwwIWmFyYWdvemExGTAXBgNVBAoMEE1RVFQgZm9y\nIG5vZGUuanMxEDAOBgNVBAsMB01RVFQuanMxDTALBgNVBAMMBG1xdHQxHDAaBgkq\nhkiG9w0BCQEWDWZha2VAbWFpbC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw\nggIKAoICAQC7Of6OppOE+xwPdPcsT0w3keCa5k4ufZCqUAHex7+mLlrpjfCjQ2z6\nRm0XBiCu9vy+xvLtbGDh5e/gocjAkkEywjbtrRMiFq5i41BNT3dzEWb9cCXvMWYa\nRxQgIqouJUz5r+TbaP1bm4gAWTHmp09ccoIs9Tykxhyc1nZxXVrEsHF4aBmuw5NJ\nZwxK1tJTgP4m5H38Ms7ahGpByPsnMg6GBRs/Yen0mGhOsG+MU4TFiQb4bwIxg8Eu\nntGP1gARvtmyTkkTDhfksRs+muEV17uPtdhGNS/0CGRWaZ2mjEYyD70Ijl2grLd4\n6Vz27uPaqUvbgntPNadKqFN+jEHTtptou3k6V9C8CeLHIq+5N6abfPVHBzaqyNqg\nQelzpSgQQBJ1H0CYREjzAs9uLfeep5ejW99Ik4YwtL6UrTVUyGzGgAl9mevZN5a4\n7mEY7MNUFdwigq0ZpbZmzYiuOURGYnoiy5o64balG5XH6Zh6B1WWhK7CArPVosz8\neoQacj1WEM5d2Ivg1OLlEdD8FZDABv5CMTmRvnoFQuuIDzWVfrhdcZQ2tQuNLWrz\nYDKheCunPkAIFOlGi70Xv3DVrTCr6kixwL2p9MHTzF4xiWWtiOv41ZXHTMG0t2I3\nYmA45FEO5JawebPgUoGhoc2vgIw5Jo9dcGtwLCqBHSnCojPoTipVhQIDAQABo1Aw\nTjAdBgNVHQ4EFgQU1yVv/ezoDLs+qjbx0O4KiHpC41swHwYDVR0jBBgwFoAU1yVv\n/ezoDLs+qjbx0O4KiHpC41swDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC\nAgEAScnfOewEk59DgfICJJ2vhcI33wqqn54zhJ1pi8SX3e7PLv26UEUXZaddIqeZ\nJzA/IWF+GCBQFAL7Z+sI4djXx/UpZp5ptCQBFc0tinHk1CGlC0E+LI3JS/cnFf+2\nL8VKZHbSf4ua2f/VMJo7uoyrw/gQHgUToAlYYWpGcIKKm7d0JYQE60wlHk9TXgCc\ns9XAwI+bP9VKNQkZCeooODG/5VcxdJafZSU3rW1WniFcD/R+ZNq7FZYbM+2u2mRt\nQm7Hh/FjrN4Hnmf3xdNUE0NLHznwk4CD6EeQukN12yP2ccubnG6Z7HFFdV0g9fEP\nAVMsgY/9E9Te/BBoQKjhIg8c274ozIOsCHODx15Mn52848sq0LIQjyeOH4rtuWLL\n1dFE1ysY2gzSMUtrP+on+r6F1GkndFszxfDrBcZMXs85VAy3eKfY/jzUMrdfn0YJ\n36Wz7F40vnOUd2ni24kaOfnRodbu3lOEYD6l5fDGP79kfITyy+dtL6ExTLZQmEn+\nxKsWM9bBkV4STpFiTF61tJwzlcAL1ZDLqDaSwsM8UDZopnDgvklNoJK9XzdLwD1X\nPofOtUe08G4tq5cBDVURLKif+7EfCyAqvUptQ3MJarhoXzhDy9CjtN8TmWexKC1q\nkB5DBML0Y4NnqTEnfYCs/XFPosaS+0GximGySJcg08ay6ZA=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "examples/tls client/mqttclient.js",
    "content": "'use strict'\n\n/** ************************** IMPORTANT NOTE ***********************************\n\n  The certificate used on this example has been generated for a host named stark.\n  So as host we SHOULD use stark if we want the server to be authorized.\n  For testing this we should add on the computer running this example a line on\n  the hosts file:\n  /etc/hosts [UNIX]\n  OR\n  \\System32\\drivers\\etc\\hosts [Windows]\n\n  The line to add on the file should be as follows:\n  <the ip address of the server> stark\n *******************************************************************************/\n\nconst mqtt = require('mqtt')\nconst fs = require('fs')\nconst path = require('path')\nconst KEY = fs.readFileSync(path.join(__dirname, '/tls-key.pem'))\nconst CERT = fs.readFileSync(path.join(__dirname, '/tls-cert.pem'))\nconst TRUSTED_CA_LIST = fs.readFileSync(path.join(__dirname, '/crt.ca.cg.pem'))\n\nconst PORT = 1883\nconst HOST = 'stark'\n\nconst options = {\n  port: PORT,\n  host: HOST,\n  key: KEY,\n  cert: CERT,\n  rejectUnauthorized: true,\n  // The CA list will be used to determine if server is authorized\n  ca: TRUSTED_CA_LIST,\n  protocol: 'mqtts'\n}\n\nconst client = mqtt.connect(options)\n\nclient.subscribe('messages')\nclient.publish('messages', 'Current time is: ' + new Date())\nclient.on('message', (topic, message) => {\n  console.log(message)\n})\n\nclient.on('connect', () => {\n  console.log('Connected')\n})\n"
  },
  {
    "path": "examples/tls client/tls-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIICATCCAWoCCQC2pNY4sfld/jANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB\nVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0\ncyBQdHkgTHRkMB4XDTEzMDgyNzEyNTU0NVoXDTEzMDkyNjEyNTU0NVowRTELMAkG\nA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0\nIFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzXGU\n1mZUBqLwoP1fWkiZeypiKgWICUdNm+d2JHXnpQMEVBxSvsaRGOnzWVvgbMVxmD7n\n5/p9qQGTj8FY/+t2NHpbt1I9lGV0+BlZxGJvyvHikEAXPD85EEFhqSbDwgkVuMqa\nw08njqhJJ37fbd2ux6w4woRrDTN4r9CNMhFb9QECAwEAATANBgkqhkiG9w0BAQUF\nAAOBgQBIlZYo1rf8GlISuV1haSBm8U/uiyjIX/pTE5Cs7Kb84SPzKB0tHnGGCa2t\nLu+TEwetF3NatuI1biqYuevQSfmEM75zsRSwt1P40sJ2y9B1XRTdamHOHCYCJG/b\nrti7WJYjvO8JsCUeB6M+5jFodbmvjsGgAHLLUINXrxOqYe+PWg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "examples/tls client/tls-key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQDNcZTWZlQGovCg/V9aSJl7KmIqBYgJR02b53YkdeelAwRUHFK+\nxpEY6fNZW+BsxXGYPufn+n2pAZOPwVj/63Y0elu3Uj2UZXT4GVnEYm/K8eKQQBc8\nPzkQQWGpJsPCCRW4yprDTyeOqEknft9t3a7HrDjChGsNM3iv0I0yEVv1AQIDAQAB\nAoGBALv9P+WEE0VTWf7mepdBsXfbi6HKF/Xtkh2kCh5I6WO8Q/y3Qhwh1OnIQg41\nnUHK1iwq+8fxFYVN1PoJQWhEzI6JdBCrn88oADo/aVm1mGN5CWr3pwn92SAVMhbw\n442AWWG81RStrr2uPhLBNE6U/4P835qM8uG4rCP+5Z5SzX7VAkEA+TptuSc0TEkL\n5B/Nml2fYNfbQvRGVzyCbdCXdgkeZt5xuSuwDgC4GvWgjL+SAN1fjTek/Iez5NnL\nxHa5w93j2wJBANMGmRTaTxvpGdkUi/utTPtCp6GXL7hS9v41LClmQTYBOYscPn2b\nDny2fyZPp29sZ7+AvXHWZxw7QtH+jO2Xz1MCQCI7vlqSYgKgffulyq4LchrxS3LU\n7tyIuTmwTz2tXvmuUFyo/ZPO0XsShi0PG1T3E2roW8c8NJ+Ysv6XeEjJL8UCQG0Z\n/S0tzTa15no4SEM/jwxcosRFoRNgOXimTwW8azybl3+Xg6t27h+GTuikyAEwf9cf\nnVJssfSDowFk5MG1+icCQQCqBOTXEukcJRXZixkpfEuuvS3RNzOYwG4ReKjpvWPy\nEvsfHoCsO1Sz9qz8DXpwl3GEWUGGTfWwBfereX6HLXj+\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "examples/vite-example/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\n.DS_Store\ndist\ndist-ssr\ncoverage\n*.local\n\n/cypress/videos/\n/cypress/screenshots/\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "examples/vite-example/README.md",
    "content": "# vite-example\n\nThis template should help get you started developing with Vue 3 in Vite.\n\n## Recommended IDE Setup\n\n[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).\n\n## Customize configuration\n\nSee [Vite Configuration Reference](https://vitejs.dev/config/).\n\n## Project Setup\n\n```sh\nnpm install\n```\n\n### Compile and Hot-Reload for Development\n\n```sh\nnpm run dev\n```\n\n### Compile and Minify for Production\n\n```sh\nnpm run build\n```\n"
  },
  {
    "path": "examples/vite-example/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\">\n    <link rel=\"icon\" href=\"/favicon.ico\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Vite App</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/vite-example/package.json",
    "content": "{\n  \"name\": \"vite-example\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@esbuild-plugins/node-modules-polyfill\": \"^0.2.2\",\n    \"mqtt\": \"file:../../\",\n    \"process\": \"^0.11.10\",\n    \"vue\": \"^3.3.4\"\n  },\n  \"devDependencies\": {\n    \"@esbuild-plugins/node-globals-polyfill\": \"^0.2.3\",\n    \"@vitejs/plugin-vue\": \"^4.2.3\",\n    \"rollup-plugin-polyfill-node\": \"^0.12.0\",\n    \"vite\": \"^4.5.3\"\n  }\n}\n"
  },
  {
    "path": "examples/vite-example/src/App.vue",
    "content": "<script setup>\nimport { ref } from 'vue'\nimport mqtt from 'mqtt'\n\nconst connected = ref(false)\n\nconst client = mqtt.connect('wss://test.mosquitto.org:8081', {\n  log: console.log.bind(console),\n  keepalive: 30,\n});\n\nconst messages = ref([])\n\nclient.on(\"connect\", () => {\n  console.log(\"connected\");\n  connected.value = true\n  client.subscribe(\"presence\", (err) => {\n    if (!err) {\n      console.log(\"subscribed\");\n      client.publish(\"presence\", \"Hello mqtt\");\n    }\n  });\n});\n\nclient.on(\"message\", (topic, message) => {\n  console.log('message', topic, message.toString());\n  // message is Buffer\n  messages.value.push(message.toString());\n});\n\nclient.on(\"close\", () => {\n  console.log(\"close\");\n  connected.value = false\n});\n\n</script>\n\n<template>\n  <main>\n    <h1>MQTTjs VITE Example</h1>\n    <p>\n      MQTTjs is a simple MQTT client for the browser. It uses WebSockets to\n      connect to an MQTT broker.\n    </p>\n    <p>\n      This example uses the public MQTT broker at\n      <a href=\"https://test.mosquitto.org/\">test.mosquitto.org</a>.\n    </p>\n    \n    <p>Status: {{ connected ? 'Connected' : 'Disconnected' }}</p>\n    <p>\n      <button @click=\"client.publish('presence', 'Hello mqtt')\">Publish</button>\n      <button @click=\"client.end()\">Disconnect</button>\n    </p>\n    <p>Messages:</p>\n    <ul>\n      <li v-for=\"message in messages\" :key=\"message\">{{ message }}</li>\n    </ul>\n    \n  </main>\n</template>\n\n\n"
  },
  {
    "path": "examples/vite-example/src/assets/base.css",
    "content": "/* color palette from <https://github.com/vuejs/theme> */\n:root {\n  --vt-c-white: #ffffff;\n  --vt-c-white-soft: #f8f8f8;\n  --vt-c-white-mute: #f2f2f2;\n\n  --vt-c-black: #181818;\n  --vt-c-black-soft: #222222;\n  --vt-c-black-mute: #282828;\n\n  --vt-c-indigo: #2c3e50;\n\n  --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);\n  --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);\n  --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);\n  --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);\n\n  --vt-c-text-light-1: var(--vt-c-indigo);\n  --vt-c-text-light-2: rgba(60, 60, 60, 0.66);\n  --vt-c-text-dark-1: var(--vt-c-white);\n  --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);\n}\n\n/* semantic color variables for this project */\n:root {\n  --color-background: var(--vt-c-white);\n  --color-background-soft: var(--vt-c-white-soft);\n  --color-background-mute: var(--vt-c-white-mute);\n\n  --color-border: var(--vt-c-divider-light-2);\n  --color-border-hover: var(--vt-c-divider-light-1);\n\n  --color-heading: var(--vt-c-text-light-1);\n  --color-text: var(--vt-c-text-light-1);\n\n  --section-gap: 160px;\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    --color-background: var(--vt-c-black);\n    --color-background-soft: var(--vt-c-black-soft);\n    --color-background-mute: var(--vt-c-black-mute);\n\n    --color-border: var(--vt-c-divider-dark-2);\n    --color-border-hover: var(--vt-c-divider-dark-1);\n\n    --color-heading: var(--vt-c-text-dark-1);\n    --color-text: var(--vt-c-text-dark-2);\n  }\n}\n\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n  margin: 0;\n  font-weight: normal;\n}\n\nbody {\n  min-height: 100vh;\n  color: var(--color-text);\n  background: var(--color-background);\n  transition: color 0.5s, background-color 0.5s;\n  line-height: 1.6;\n  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,\n    Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\n  font-size: 15px;\n  text-rendering: optimizeLegibility;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n"
  },
  {
    "path": "examples/vite-example/src/assets/main.css",
    "content": "@import './base.css';\n\n#app {\n  max-width: 1280px;\n  margin: 0 auto;\n  padding: 2rem;\n\n  font-weight: normal;\n}\n\na,\n.green {\n  text-decoration: none;\n  color: hsla(160, 100%, 37%, 1);\n  transition: 0.4s;\n}\n\n@media (hover: hover) {\n  a:hover {\n    background-color: hsla(160, 100%, 37%, 0.2);\n  }\n}\n\n@media (min-width: 1024px) {\n  body {\n    display: flex;\n    place-items: center;\n  }\n\n  #app {\n    display: grid;\n    grid-template-columns: 1fr 1fr;\n    padding: 0 2rem;\n  }\n}\n"
  },
  {
    "path": "examples/vite-example/src/main.js",
    "content": "import './assets/main.css'\n\nimport { createApp } from 'vue'\nimport App from './App.vue'\n\ncreateApp(App).mount('#app')\n"
  },
  {
    "path": "examples/vite-example/vite.config.js",
    "content": "import { fileURLToPath, URL } from 'node:url'\n\nimport { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [\n    vue(),\n  ],\n  resolve: {\n    alias: {\n      '@': fileURLToPath(new URL('./src', import.meta.url)),\n    }\n  },\n})\n"
  },
  {
    "path": "examples/ws/aedes_server.js",
    "content": "const aedes = require('aedes')()\nconst httpServer = require('http').createServer()\nconst WebSocket = require('ws')\nconst wsPort = 8080\n\n// Here we are creating the Websocket Server that is using the HTTP Server...\nconst wss = new WebSocket.Server({ server: httpServer })\nwss.on('connection', function connection (ws) {\n  const duplex = WebSocket.createWebSocketStream(ws)\n  aedes.handle(duplex)\n})\n\nhttpServer.listen(wsPort, () => {\n  console.log('websocket server listening on port', wsPort)\n})\n\naedes.on('clientError', (client, err) => {\n  console.log('client error', client.id, err.message, err.stack)\n})\n\naedes.on('connectionError', (client, err) => {\n  console.log('client error', client, err.message, err.stack)\n})\n\naedes.on('publish', (packet, client) => {\n  if (packet && packet.payload) {\n    console.log('publish packet:', packet.payload.toString())\n  }\n  if (client) {\n    console.log('message from client', client.id)\n  }\n})\n\naedes.on('subscribe', (subscriptions, client) => {\n  if (client) {\n    console.log('subscribe from client', subscriptions, client.id)\n  }\n})\n\naedes.on('client', (client) => {\n  console.log('new client', client.id)\n})\n"
  },
  {
    "path": "examples/ws/client.js",
    "content": "'use strict'\n\nconst mqtt = require('../../')\n\nconst clientId = 'mqttjs_' + Math.random().toString(16).substr(2, 8)\n\n// This sample should be run in tandem with the aedes_server.js file.\n// Simply run it:\n// $ node aedes_server.js\n//\n// Then run this file in a separate console:\n// $ node websocket_sample.js\n//\nconst host = 'ws://localhost:8080'\n\nconst options = {\n  keepalive: 30,\n  clientId,\n  protocolId: 'MQTT',\n  protocolVersion: 4,\n  clean: true,\n  reconnectPeriod: 1000,\n  connectTimeout: 30 * 1000,\n  will: {\n    topic: 'WillMsg',\n    payload: 'Connection Closed abnormally..!',\n    qos: 0,\n    retain: false\n  },\n  rejectUnauthorized: false\n}\n\nconsole.log('connecting mqtt client')\nconst client = mqtt.connect(host, options)\n\nclient.on('error', (err) => {\n  console.log(err)\n  client.end()\n})\n\nclient.on('connect', () => {\n  console.log('client connected:' + clientId)\n  client.subscribe('topic', { qos: 0 })\n  client.publish('topic', 'wss secure connection demo...!', { qos: 0, retain: false })\n})\n\nclient.on('message', (topic, message, packet) => {\n  console.log('Received Message:= ' + message.toString() + '\\nOn topic:= ' + topic)\n})\n\nclient.on('close', () => {\n  console.log(clientId + ' disconnected')\n})\n"
  },
  {
    "path": "examples/wss/client_with_proxy.js",
    "content": "'use strict'\n\nconst mqtt = require('mqtt')\nconst url = require('url')\nconst HttpsProxyAgent = require('https-proxy-agent')\n/*\nhost: host of the endpoint you want to connect e.g. my.mqqt.host.com\npath: path to you endpoint e.g. '/foo/bar/mqtt'\n*/\nconst endpoint = 'wss://<host><path>'\n/* create proxy agent\nproxy: your proxy e.g. proxy.foo.bar.com\nport: http proxy port e.g. 8080\n*/\nconst proxy = process.env.http_proxy || 'http://<proxy>:<port>'\n// eslint-disable-next-line\nconst parsed = url.parse(endpoint)\n// eslint-disable-next-line\nconst proxyOpts = url.parse(proxy)\n// true for wss\nproxyOpts.secureEndpoint = parsed.protocol ? parsed.protocol === 'wss:' : true\nconst agent = new HttpsProxyAgent(proxyOpts)\nconst wsOptions = {\n  agent\n  // other wsOptions\n  // foo:'bar'\n}\nconst mqttOptions = {\n  keepalive: 60,\n  reschedulePings: true,\n  protocolId: 'MQTT',\n  protocolVersion: 4,\n  reconnectPeriod: 1000,\n  connectTimeout: 30 * 1000,\n  clean: true,\n  clientId: 'testClient',\n  wsOptions\n}\n\nconst client = mqtt.connect(parsed, mqttOptions)\n\nclient.on('connect', () => {\n  console.log('connected')\n})\n\nclient.on('error', (a) => {\n  console.log('error!' + a)\n})\n\nclient.on('offline', (a) => {\n  console.log('lost connection!' + a)\n})\n\nclient.on('close', (a) => {\n  console.log('connection closed!' + a)\n})\n\nclient.on('message', (topic, message) => {\n  console.log(message.toString())\n})\n"
  },
  {
    "path": "help/help.txt",
    "content": "MQTT.js command line interface, available commands are:\n\n  * publish     publish a message to the broker\n  * subscribe   subscribe for updates from the broker\n  * version     the current MQTT.js version\n  * help        help about commands\n\nLaunch 'mqtt help [command]' to know more about the commands.\n"
  },
  {
    "path": "help/publish.txt",
    "content": "Usage: mqtt publish [opts] topic [message]\n\nAvailable options:\n\n  -h/--hostname HOST    the broker host\n  -p/--port PORT        the broker port\n  -i/--client-id ID     the client id\n  -q/--qos 0/1/2        the QoS of the message\n  -t/--topic TOPIC      the message topic\n  -m/--message MSG      the message body\n  -r/--retain           send a retained message\n  -s/--stdin            read the message body from stdin\n  -M/--multiline        read lines from stdin as multiple messages \n  -u/--username USER    the username\n  -P/--password PASS    the password\n  -C/--protocol PROTO   the protocol to use, 'mqtt',\n                        'mqtts', 'ws' or 'wss'\n  --key PATH            path to the key file\n  --cert PATH           path to the cert file\n  --ca PATH             path to the ca certificate\n  --insecure            do not verify the server certificate\n  --will-topic TOPIC    the will topic\n  --will-payload BODY   the will message\n  --will-qos 0/1/2      the will qos\n  --will-retain         send a will retained message \n  -H/--help             show this\n"
  },
  {
    "path": "help/subscribe.txt",
    "content": "Usage: mqtt subscribe [opts] [topic]\n\nAvailable options:\n\n  -h/--hostname HOST    the broker host\n  -p/--port PORT        the broker port\n  -i/--clientId ID      the client id\n  -q/--qos 0/1/2        the QoS of the message\n  --no-clean            do not discard any pending message for\n                        the given id\n  -t/--topic TOPIC      the message topic\n  -k/--keepalive SEC    send a ping every SEC seconds\n  -u/--username USER    the username\n  -P/--password PASS    the password\n  -l/--protocol PROTO   the protocol to use, 'mqtt',\n                        'mqtts', 'ws' or 'wss'\n  --key PATH            path to the key file\n  --cert PATH           path to the cert file\n  --ca PATH             path to the ca certificate\n  --insecure            do not verify the server certificate\n  --will-topic TOPIC    the will topic\n  --will-message BODY   the will message\n  --will-qos 0/1/2      the will qos\n  --will-retain         send a will retained message\n  -v/--verbose          print the topic before the message\n  -H/--help             show this\n"
  },
  {
    "path": "nyc.config.js",
    "content": "\nmodule.exports = {\n    include: [\n        'src/**',\n    ],\n    exclude: [\n        'src/bin/*',\n        'src/lib/BufferedDuplex.ts',\n        'src/connect/wx.ts',\n        'src/connect/ali.ts',\n    ],\n    reporter: [\n        'text',\n        'lcov'\n    ],\n    branches: 80,\n    functions: 89,\n    lines: 86,\n    statements: 86,\n    'check-coverage': true\n}"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"mqtt\",\n  \"description\": \"A library for the MQTT protocol\",\n  \"version\": \"5.15.0\",\n  \"contributors\": [\n    \"Adam Rudd <adamvrr@gmail.com>\",\n    \"Matteo Collina <matteo.collina@gmail.com> (https://github.com/mcollina)\",\n    \"Siarhei Buntsevich <scarry0506@gmail.com> (https://github.com/scarry1992)\",\n    \"Yoseph Maguire <yomaguir@microsoft.com> (https://github.com/YoDaMa)\",\n    \"Daniel Lando <daniel.sorridi@gmail.com> (https://github.com/robertsLando)\"\n  ],\n  \"keywords\": [\n    \"mqtt\",\n    \"publish/subscribe\",\n    \"publish\",\n    \"subscribe\"\n  ],\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/mqttjs/MQTT.js.git\"\n  },\n  \"main\": \"./build/index.js\",\n  \"module\": \"./dist/mqtt.esm.js\",\n  \"bin\": {\n    \"mqtt_pub\": \"./build/bin/pub.js\",\n    \"mqtt_sub\": \"./build/bin/sub.js\",\n    \"mqtt\": \"./build/bin/mqtt.js\"\n  },\n  \"files\": [\n    \"dist/\",\n    \"CONTRIBUTING.md\",\n    \"LICENSE.md\",\n    \"help/\",\n    \"build/\"\n  ],\n  \"exports\": {\n    \".\": {\n      \"react-native\": \"./dist/mqtt.esm.js\",\n      \"browser\": {\n        \"import\": \"./dist/mqtt.esm.js\",\n        \"default\": \"./dist/mqtt.min.js\"\n      },\n      \"default\": \"./build/index.js\"\n    },\n    \"./package.json\": \"./package.json\",\n    \"./*.map\": \"./build/*.js.map\",\n    \"./dist/*\": \"./dist/*.js\",\n    \"./*\": \"./build/*.js\"\n  },\n  \"types\": \"build/index.d.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"*\": [\n        \"./build/index.d.ts\"\n      ]\n    }\n  },\n  \"scripts\": {\n    \"lint\": \"eslint --ext .ts .\",\n    \"lint-fix\": \"eslint --fix --ext .ts .\",\n    \"build:ts\": \"rimraf build/ && tsc -p tsconfig.build.json\",\n    \"build:browser\": \"node esbuild.js\",\n    \"build\": \"npm run build:ts && npm run build:browser\",\n    \"prepare\": \"npm run build\",\n    \"unit-test:node\": \"node -r esbuild-register --test-concurrency 4 --test-reporter=junit --test-reporter-destination=junit.xml --test-reporter=spec --test-reporter-destination=stdout --test test/node/*.ts \",\n    \"unit-test:browser\": \"wtr\",\n    \"test:node\": \"node_modules/.bin/nyc npm run unit-test:node\",\n    \"test:browser\": \"npm run build && npm run unit-test:browser\",\n    \"test\": \"npm run test:node\",\n    \"changelog\": \"conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md\",\n    \"changelog-init\": \"conventional-changelog -p angular -i CHANGELOG.md -s -r 0\",\n    \"release\": \"read -p 'GITHUB_TOKEN: ' GITHUB_TOKEN && export GITHUB_TOKEN=$GITHUB_TOKEN && release-it\"\n  },\n  \"publishConfig\": {\n    \"registry\": \"https://registry.npmjs.org\",\n    \"provenance\": true\n  },\n  \"pre-commit\": [\n    \"lint\"\n  ],\n  \"engines\": {\n    \"node\": \">=16.0.0\"\n  },\n  \"browser\": {\n    \"./mqtt.js\": \"./dist/mqtt.js\",\n    \"fs\": false,\n    \"tls\": false,\n    \"net\": false\n  },\n  \"dependencies\": {\n    \"@types/readable-stream\": \"^4.0.21\",\n    \"@types/ws\": \"^8.18.1\",\n    \"commist\": \"^3.2.0\",\n    \"concat-stream\": \"^2.0.0\",\n    \"debug\": \"^4.4.1\",\n    \"help-me\": \"^5.0.0\",\n    \"lru-cache\": \"^10.4.3\",\n    \"minimist\": \"^1.2.8\",\n    \"mqtt-packet\": \"^9.0.2\",\n    \"number-allocator\": \"^1.0.14\",\n    \"readable-stream\": \"^4.7.0\",\n    \"rfdc\": \"^1.4.1\",\n    \"socks\": \"^2.8.6\",\n    \"split2\": \"^4.2.0\",\n    \"worker-timers\": \"^8.0.23\",\n    \"ws\": \"^8.18.3\"\n  },\n  \"devDependencies\": {\n    \"@eslint/eslintrc\": \"^3.3.1\",\n    \"@eslint/js\": \"^9.32.0\",\n    \"@esm-bundle/chai\": \"^4.3.4\",\n    \"@release-it/conventional-changelog\": \"^10.0.1\",\n    \"@tsconfig/node20\": \"^20.1.6\",\n    \"@types/chai\": \"^5.2.2\",\n    \"@types/node\": \"^20.17.16\",\n    \"@types/sinon\": \"^17.0.4\",\n    \"@types/tape\": \"^5.8.1\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.38.0\",\n    \"@typescript-eslint/parser\": \"^8.38.0\",\n    \"@web/test-runner\": \"^0.20.2\",\n    \"@web/test-runner-playwright\": \"^0.11.1\",\n    \"aedes-cli\": \"^0.8.0\",\n    \"chai\": \"^5.2.1\",\n    \"chokidar\": \"^4.0.3\",\n    \"conventional-changelog-cli\": \"^5.0.0\",\n    \"end-of-stream\": \"^1.4.5\",\n    \"esbuild\": \"^0.25.8\",\n    \"esbuild-plugin-polyfill-node\": \"^0.3.0\",\n    \"esbuild-register\": \"^3.6.0\",\n    \"eslint\": \"^9.32.0\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-plugin-import\": \"^2.32.0\",\n    \"eslint-plugin-prettier\": \"^5.5.3\",\n    \"global\": \"^4.4.0\",\n    \"globals\": \"^16.3.0\",\n    \"leaked-handles\": \"^5.2.0\",\n    \"mkdirp\": \"^3.0.1\",\n    \"mqtt-connection\": \"^4.1.0\",\n    \"mqtt-level-store\": \"^3.1.0\",\n    \"nyc\": \"^17.1.0\",\n    \"pre-commit\": \"^1.2.2\",\n    \"prettier\": \"^3.6.2\",\n    \"release-it\": \"^19.0.4\",\n    \"rimraf\": \"^6.0.1\",\n    \"should\": \"^13.2.3\",\n    \"sinon\": \"^17.0.2\",\n    \"snazzy\": \"^9.0.0\",\n    \"tape\": \"^5.9.0\",\n    \"ts-node\": \"^10.9.2\",\n    \"typescript\": \"^5.8.3\"\n  }\n}\n"
  },
  {
    "path": "src/bin/mqtt.ts",
    "content": "#!/usr/bin/env node\n\n/*\n * Copyright (c) 2015-2015 MQTT.js contributors.\n * Copyright (c) 2011-2014 Adam Rudd.\n *\n * See LICENSE for more information\n */\nimport path from 'path'\nimport Commist from 'commist'\nimport help from 'help-me'\nimport publish from './pub'\nimport subscribe from './sub'\nimport { MQTTJS_VERSION } from '../mqtt'\n\nconst helpMe = help({\n\tdir: path.join(__dirname, '../../', 'help'),\n\text: '.txt',\n})\n\nconst commist = Commist()\n\ncommist.register('publish', publish)\ncommist.register('pub', publish)\n\ncommist.register('subscribe', subscribe)\ncommist.register('sub', subscribe)\n\ncommist.register('version', () => {\n\tconsole.log('MQTT.js version:', MQTTJS_VERSION)\n})\ncommist.register('help', helpMe.toStdout)\n\nif (commist.parse(process.argv.slice(2)) !== null) {\n\tconsole.log('No such command:', process.argv[2], '\\n')\n\thelpMe.toStdout()\n}\n"
  },
  {
    "path": "src/bin/pub.ts",
    "content": "#!/usr/bin/env node\n\nimport { Writable } from 'readable-stream'\nimport path from 'path'\nimport fs from 'fs'\nimport concat from 'concat-stream'\nimport help from 'help-me'\n\nimport minimist, { type ParsedArgs } from 'minimist'\nimport split2 from 'split2'\nimport { type IClientOptions, type IClientPublishOptions } from 'src/lib/client'\nimport { pipeline } from 'stream'\nimport { connect } from '../mqtt'\n\nconst helpMe = help({\n\tdir: path.join(__dirname, '../../', 'help'),\n})\n\nfunction send(args: ParsedArgs) {\n\tconst client = connect(args as IClientOptions)\n\tclient.on('connect', () => {\n\t\tclient.publish(\n\t\t\targs.topic,\n\t\t\targs.message,\n\t\t\targs as IClientPublishOptions,\n\t\t\t(err) => {\n\t\t\t\tif (err) {\n\t\t\t\t\tconsole.warn(err)\n\t\t\t\t}\n\t\t\t\tclient.end()\n\t\t\t},\n\t\t)\n\t})\n\tclient.on('error', (err) => {\n\t\tconsole.warn(err)\n\t\tclient.end()\n\t})\n}\n\nfunction multisend(args: ParsedArgs) {\n\tconst client = connect(args as IClientOptions)\n\tconst sender = new Writable({\n\t\tobjectMode: true,\n\t})\n\tsender._write = (line, enc, cb) => {\n\t\tclient.publish(\n\t\t\targs.topic,\n\t\t\tline.trim(),\n\t\t\targs as IClientPublishOptions,\n\t\t\tcb,\n\t\t)\n\t}\n\n\tclient.on('connect', () => {\n\t\tpipeline(process.stdin, split2(), sender, (err) => {\n\t\t\tclient.end()\n\t\t\tif (err) {\n\t\t\t\tthrow err\n\t\t\t}\n\t\t})\n\t})\n}\n\nexport default function start(args: string[]) {\n\tconst parsedArgs = minimist(args, {\n\t\tstring: [\n\t\t\t'hostname',\n\t\t\t'username',\n\t\t\t'password',\n\t\t\t'key',\n\t\t\t'cert',\n\t\t\t'ca',\n\t\t\t'message',\n\t\t\t'clientId',\n\t\t\t'i',\n\t\t\t'id',\n\t\t],\n\t\tboolean: ['stdin', 'retain', 'help', 'insecure', 'multiline'],\n\t\talias: {\n\t\t\tport: 'p',\n\t\t\thostname: ['h', 'host'],\n\t\t\ttopic: 't',\n\t\t\tmessage: 'm',\n\t\t\tqos: 'q',\n\t\t\tclientId: ['i', 'id'],\n\t\t\tretain: 'r',\n\t\t\tusername: 'u',\n\t\t\tpassword: 'P',\n\t\t\tstdin: 's',\n\t\t\tmultiline: 'M',\n\t\t\tprotocol: ['C', 'l'],\n\t\t\thelp: 'H',\n\t\t\tca: 'cafile',\n\t\t},\n\t\tdefault: {\n\t\t\thost: 'localhost',\n\t\t\tqos: 0,\n\t\t\tretain: false,\n\t\t\ttopic: '',\n\t\t\tmessage: '',\n\t\t},\n\t})\n\n\tif (parsedArgs.help) {\n\t\treturn helpMe.toStdout('publish')\n\t}\n\n\tif (parsedArgs.key) {\n\t\tparsedArgs.key = fs.readFileSync(parsedArgs.key)\n\t}\n\n\tif (parsedArgs.cert) {\n\t\tparsedArgs.cert = fs.readFileSync(parsedArgs.cert)\n\t}\n\n\tif (parsedArgs.ca) {\n\t\tparsedArgs.ca = fs.readFileSync(parsedArgs.ca)\n\t}\n\n\tif (parsedArgs.key && parsedArgs.cert && !parsedArgs.protocol) {\n\t\tparsedArgs.protocol = 'mqtts'\n\t}\n\n\tif (parsedArgs.port) {\n\t\tif (typeof parsedArgs.port !== 'number') {\n\t\t\tconsole.warn(\n\t\t\t\t\"# Port: number expected, '%s' was given.\",\n\t\t\t\ttypeof parsedArgs.port,\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif (parsedArgs['will-topic']) {\n\t\tparsedArgs.will = {}\n\t\tparsedArgs.will.topic = parsedArgs['will-topic']\n\t\tparsedArgs.will.payload = parsedArgs['will-message']\n\t\tparsedArgs.will.qos = parsedArgs['will-qos']\n\t\tparsedArgs.will.retain = parsedArgs['will-retain']\n\t}\n\n\tif (parsedArgs.insecure) {\n\t\tparsedArgs.rejectUnauthorized = false\n\t}\n\n\tparsedArgs.topic = (parsedArgs.topic || parsedArgs._.shift())?.toString()\n\tparsedArgs.message = (\n\t\tparsedArgs.message || parsedArgs._.shift()\n\t)?.toString()\n\n\tif (!parsedArgs.topic) {\n\t\tconsole.error('missing topic\\n')\n\t\treturn helpMe.toStdout('publish')\n\t}\n\n\tif (parsedArgs.stdin) {\n\t\tif (parsedArgs.multiline) {\n\t\t\tmultisend(parsedArgs)\n\t\t} else {\n\t\t\tprocess.stdin.pipe(\n\t\t\t\tconcat((data) => {\n\t\t\t\t\tparsedArgs.message = data\n\t\t\t\t\tsend(parsedArgs)\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\t} else {\n\t\tsend(parsedArgs)\n\t}\n}\n\nif (require.main === module) {\n\tstart(process.argv.slice(2))\n}\n"
  },
  {
    "path": "src/bin/sub.ts",
    "content": "#!/usr/bin/env node\n\nimport path from 'path'\nimport fs from 'fs'\nimport minimist from 'minimist'\nimport help from 'help-me'\nimport { type IClientOptions } from 'src/lib/client'\nimport { connect } from '../mqtt'\n\nconst helpMe = help({\n\tdir: path.join(__dirname, '../../', 'help'),\n})\n\nexport default function start(args: string[]) {\n\tconst parsedArgs = minimist(args, {\n\t\tstring: [\n\t\t\t'hostname',\n\t\t\t'username',\n\t\t\t'password',\n\t\t\t'key',\n\t\t\t'cert',\n\t\t\t'ca',\n\t\t\t'clientId',\n\t\t\t'i',\n\t\t\t'id',\n\t\t],\n\t\tboolean: ['stdin', 'help', 'clean', 'insecure'],\n\t\talias: {\n\t\t\tport: 'p',\n\t\t\thostname: ['h', 'host'],\n\t\t\ttopic: 't',\n\t\t\tqos: 'q',\n\t\t\tclean: 'c',\n\t\t\tkeepalive: 'k',\n\t\t\tclientId: ['i', 'id'],\n\t\t\tusername: 'u',\n\t\t\tpassword: 'P',\n\t\t\tprotocol: ['C', 'l'],\n\t\t\tverbose: 'v',\n\t\t\thelp: '-H',\n\t\t\tca: 'cafile',\n\t\t},\n\t\tdefault: {\n\t\t\thost: 'localhost',\n\t\t\tqos: 0,\n\t\t\tretain: false,\n\t\t\tclean: true,\n\t\t\tkeepAlive: 30, // 30 sec\n\t\t},\n\t})\n\n\tif (parsedArgs.help) {\n\t\treturn helpMe.toStdout('subscribe')\n\t}\n\n\tparsedArgs.topic = parsedArgs.topic || parsedArgs._.shift()\n\n\tif (!parsedArgs.topic) {\n\t\tconsole.error('missing topic\\n')\n\t\treturn helpMe.toStdout('subscribe')\n\t}\n\n\tif (parsedArgs.key) {\n\t\tparsedArgs.key = fs.readFileSync(parsedArgs.key)\n\t}\n\n\tif (parsedArgs.cert) {\n\t\tparsedArgs.cert = fs.readFileSync(parsedArgs.cert)\n\t}\n\n\tif (parsedArgs.ca) {\n\t\tparsedArgs.ca = fs.readFileSync(parsedArgs.ca)\n\t}\n\n\tif (parsedArgs.key && parsedArgs.cert && !parsedArgs.protocol) {\n\t\tparsedArgs.protocol = 'mqtts'\n\t}\n\n\tif (parsedArgs.insecure) {\n\t\tparsedArgs.rejectUnauthorized = false\n\t}\n\n\tif (parsedArgs.port) {\n\t\tif (typeof parsedArgs.port !== 'number') {\n\t\t\tconsole.warn(\n\t\t\t\t\"# Port: number expected, '%s' was given.\",\n\t\t\t\ttypeof parsedArgs.port,\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif (parsedArgs['will-topic']) {\n\t\tparsedArgs.will = {}\n\t\tparsedArgs.will.topic = parsedArgs['will-topic']\n\t\tparsedArgs.will.payload = parsedArgs['will-message']\n\t\tparsedArgs.will.qos = parsedArgs['will-qos']\n\t\tparsedArgs.will.retain = parsedArgs['will-retain']\n\t}\n\n\tparsedArgs.keepAlive = parsedArgs['keep-alive']\n\n\tconst client = connect(parsedArgs as IClientOptions)\n\n\tclient.on('connect', () => {\n\t\tclient.subscribe(\n\t\t\tparsedArgs.topic,\n\t\t\t{ qos: parsedArgs.qos },\n\t\t\t(err, result) => {\n\t\t\t\tif (err) {\n\t\t\t\t\tconsole.error(err)\n\t\t\t\t\tprocess.exit(1)\n\t\t\t\t}\n\n\t\t\t\tresult.forEach((sub) => {\n\t\t\t\t\tif (sub.qos > 2) {\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t'subscription negated to',\n\t\t\t\t\t\t\tsub.topic,\n\t\t\t\t\t\t\t'with code',\n\t\t\t\t\t\t\tsub.qos,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tprocess.exit(1)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\t})\n\n\tclient.on('message', (topic, payload) => {\n\t\tif (parsedArgs.verbose) {\n\t\t\tconsole.log(topic, payload.toString())\n\t\t} else {\n\t\t\tconsole.log(payload.toString())\n\t\t}\n\t})\n\n\tclient.on('error', (err) => {\n\t\tconsole.warn(err)\n\t\tclient.end()\n\t})\n}\n\nif (require.main === module) {\n\tstart(process.argv.slice(2))\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import * as mqtt from './mqtt'\n\nexport default mqtt\nexport * from './mqtt'\n"
  },
  {
    "path": "src/lib/BufferedDuplex.ts",
    "content": "import { Duplex, type Transform } from 'readable-stream'\nimport { Buffer } from 'buffer'\nimport { type IClientOptions } from './client'\n\n/**\n * Utils writev function for browser, ensure to write Buffers to socket (convert strings).\n */\nexport function writev(\n\tchunks: { chunk: any; encoding: string }[],\n\tcb: (err?: Error) => void,\n) {\n\tconst buffers = new Array(chunks.length)\n\tfor (let i = 0; i < chunks.length; i++) {\n\t\tif (typeof chunks[i].chunk === 'string') {\n\t\t\tbuffers[i] = Buffer.from(chunks[i].chunk, 'utf8')\n\t\t} else {\n\t\t\tbuffers[i] = chunks[i].chunk\n\t\t}\n\t}\n\n\tthis._write(Buffer.concat(buffers), 'binary', cb)\n}\n\n/**\n * How this works:\n * - `socket` is the `WebSocket` instance, the connection to our broker.\n * - `proxy` is a `Transform`, it ensure data written to the `socket` is a `Buffer`.\n * This class buffers the data written to the `proxy` (so then to `socket`) until the `socket` is ready.\n * The stream returned from this class, will be passed to the `MqttClient`.\n */\nexport class BufferedDuplex extends Duplex {\n\tpublic socket: WebSocket\n\n\tprivate proxy: Transform\n\n\tprivate isSocketOpen: boolean\n\n\tprivate writeQueue: Array<{\n\t\tchunk: any\n\t\tencoding: string\n\t\tcb: (err?: Error) => void\n\t}>\n\n\tconstructor(opts: IClientOptions, proxy: Transform, socket: WebSocket) {\n\t\tsuper({\n\t\t\tobjectMode: true,\n\t\t})\n\t\tthis.proxy = proxy\n\t\tthis.socket = socket\n\t\tthis.writeQueue = []\n\n\t\tif (!opts.objectMode) {\n\t\t\tthis._writev = writev.bind(this)\n\t\t}\n\n\t\tthis.isSocketOpen = false\n\n\t\tthis.proxy.on('data', (chunk) => {\n\t\t\tif (!this.destroyed && this.readable) {\n\t\t\t\tthis.push(chunk)\n\t\t\t}\n\t\t})\n\t}\n\n\t_read(size?: number): void {\n\t\tthis.proxy.read(size)\n\t}\n\n\t_write(chunk: any, encoding: string, cb: (err?: Error) => void) {\n\t\tif (!this.isSocketOpen) {\n\t\t\t// Buffer the data in a queue\n\t\t\tthis.writeQueue.push({ chunk, encoding, cb })\n\t\t} else {\n\t\t\tthis.writeToProxy(chunk, encoding, cb)\n\t\t}\n\t}\n\n\t_final(callback: (error?: Error) => void): void {\n\t\tthis.writeQueue = []\n\t\tthis.proxy.end(callback)\n\t}\n\n\t_destroy(err: Error, callback: (error: Error) => void): void {\n\t\tthis.writeQueue = []\n\t\t// do not pass error here otherwise we should listen for `error` event on proxy to prevent uncaught exception\n\t\tthis.proxy.destroy()\n\t\tcallback(err)\n\t}\n\n\t/** Method to call when socket is ready to stop buffering writes */\n\tsocketReady() {\n\t\tthis.emit('connect')\n\t\tthis.isSocketOpen = true\n\t\tthis.processWriteQueue()\n\t}\n\n\tprivate writeToProxy(\n\t\tchunk: any,\n\t\tencoding: string,\n\t\tcb: (err?: Error) => void,\n\t) {\n\t\tif (this.proxy.write(chunk, encoding) === false) {\n\t\t\tthis.proxy.once('drain', cb)\n\t\t} else {\n\t\t\tcb()\n\t\t}\n\t}\n\n\tprivate processWriteQueue() {\n\t\twhile (this.writeQueue.length > 0) {\n\t\t\tconst { chunk, encoding, cb } = this.writeQueue.shift()!\n\t\t\tthis.writeToProxy(chunk, encoding, cb)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/lib/KeepaliveManager.ts",
    "content": "import type MqttClient from './client'\nimport getTimer, { type Timer } from './get-timer'\nimport type { TimerVariant } from './shared'\n\nexport default class KeepaliveManager {\n\tprivate _keepalive: number\n\n\tprivate timerId: number\n\n\tprivate timer: Timer\n\n\tprivate destroyed = false\n\n\tprivate counter: number\n\n\tprivate client: MqttClient\n\n\tprivate _keepaliveTimeoutTimestamp: number\n\n\tprivate _intervalEvery: number\n\n\t/** Timestamp of next keepalive timeout */\n\tget keepaliveTimeoutTimestamp() {\n\t\treturn this._keepaliveTimeoutTimestamp\n\t}\n\n\t/** Milliseconds of the actual interval */\n\tget intervalEvery() {\n\t\treturn this._intervalEvery\n\t}\n\n\tget keepalive() {\n\t\treturn this._keepalive\n\t}\n\n\tconstructor(client: MqttClient, variant: TimerVariant | Timer) {\n\t\tthis.client = client\n\t\tthis.timer =\n\t\t\ttypeof variant === 'object' &&\n\t\t\t'set' in variant &&\n\t\t\t'clear' in variant\n\t\t\t\t? variant\n\t\t\t\t: getTimer(variant)\n\t\tthis.setKeepalive(client.options.keepalive)\n\t}\n\n\tprivate clear() {\n\t\tif (this.timerId) {\n\t\t\tthis.timer.clear(this.timerId)\n\t\t\tthis.timerId = null\n\t\t}\n\t}\n\n\t/** Change the keepalive */\n\tsetKeepalive(value: number) {\n\t\t// keepalive is in seconds\n\t\tvalue *= 1000\n\n\t\tif (isNaN(value) || value <= 0 || value > 2147483647) {\n\t\t\tthrow new Error(\n\t\t\t\t`Keepalive value must be an integer between 0 and 2147483647. Provided value is ${value}`,\n\t\t\t)\n\t\t}\n\n\t\tthis._keepalive = value\n\n\t\tthis.reschedule()\n\n\t\tthis.client['log'](`KeepaliveManager: set keepalive to ${value}ms`)\n\t}\n\n\tdestroy() {\n\t\tthis.clear()\n\t\tthis.destroyed = true\n\t}\n\n\treschedule() {\n\t\tif (this.destroyed) {\n\t\t\treturn\n\t\t}\n\n\t\tthis.clear()\n\t\tthis.counter = 0\n\n\t\t// https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Figure_3.5_Keep\n\t\tconst keepAliveTimeout = Math.ceil(this._keepalive * 1.5)\n\n\t\tthis._keepaliveTimeoutTimestamp = Date.now() + keepAliveTimeout\n\t\tthis._intervalEvery = Math.ceil(this._keepalive / 2)\n\n\t\tthis.timerId = this.timer.set(() => {\n\t\t\t// this should never happen, but just in case\n\t\t\tif (this.destroyed) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.counter += 1\n\n\t\t\t// after keepalive seconds, send a pingreq\n\t\t\tif (this.counter === 2) {\n\t\t\t\tthis.client.sendPing()\n\t\t\t} else if (this.counter > 2) {\n\t\t\t\tthis.client.onKeepaliveTimeout()\n\t\t\t}\n\t\t}, this._intervalEvery)\n\t}\n}\n"
  },
  {
    "path": "src/lib/TypedEmitter.ts",
    "content": "import EventEmitter from 'events'\nimport { applyMixin } from './shared'\n\nexport type EventHandler =\n\t// Add more overloads as necessary\n\t| ((arg1: any, arg2: any, arg3: any, arg4: any) => void)\n\t| ((arg1: any, arg2: any, arg3: any) => void)\n\t| ((arg1: any, arg2: any) => void)\n\t| ((arg1: any) => void)\n\t| ((...args: any[]) => void)\n\nexport interface TypedEventEmitter<\n\tTEvents extends Record<keyof TEvents, EventHandler>,\n> {\n\ton<TEvent extends keyof TEvents>(\n\t\tevent: TEvent,\n\t\tcallback: TEvents[TEvent],\n\t): this\n\tonce<TEvent extends keyof TEvents>(\n\t\tevent: TEvent,\n\t\tcallback: TEvents[TEvent],\n\t): this\n\tprependListener<TEvent extends keyof TEvents>(\n\t\tevent: TEvent,\n\t\tcallback: TEvents[TEvent],\n\t): this\n\tprependOnceListener<TEvent extends keyof TEvents>(\n\t\tevent: TEvent,\n\t\tcallback: TEvents[TEvent],\n\t): this\n\n\tremoveListener<TEvent extends keyof TEvents>(\n\t\tevent: TEvent,\n\t\tcallback: TEvents[TEvent],\n\t): this\n\toff<TEvent extends keyof TEvents>(\n\t\tevent: TEvent,\n\t\tcallback: TEvents[TEvent],\n\t): this\n\n\tremoveAllListeners(event?: keyof TEvents): this\n\n\temit<TEvent extends keyof TEvents>(\n\t\tevent: TEvent,\n\t\t...args: Parameters<TEvents[TEvent]>\n\t): boolean\n\n\tsetMaxListeners(n: number): this\n\tgetMaxListeners(): number\n\n\tlisteners<TEvent extends keyof TEvents>(\n\t\teventName: TEvent,\n\t): TEvents[TEvent][]\n\trawListeners<TEvent extends keyof TEvents>(\n\t\teventName: TEvent,\n\t): TEvents[TEvent][]\n\tlistenerCount<TEvent extends keyof TEvents>(\n\t\tevent: TEvent,\n\t\tlistener?: TEvents[TEvent],\n\t): number\n\n\teventNames(): Array<keyof TEvents>\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging\nexport class TypedEventEmitter<\n\tTEvents extends Record<keyof TEvents, EventHandler>,\n> {}\n\n// Make TypedEventEmitter inherit from EventEmitter without actually extending\napplyMixin(TypedEventEmitter, EventEmitter)\n"
  },
  {
    "path": "src/lib/client.ts",
    "content": "/**\n * Module dependencies\n */\nimport mqttPacket, {\n\ttype IAuthPacket,\n\tIConnackPacket,\n\tIDisconnectPacket,\n\tIPublishPacket,\n\ttype ISubscribePacket,\n\ttype ISubscription,\n\ttype IUnsubscribePacket,\n\tPacket,\n\ttype QoS,\n\ttype ISubackPacket,\n\ttype IConnectPacket,\n} from 'mqtt-packet'\nimport { type DuplexOptions, Writable } from 'readable-stream'\nimport clone from 'rfdc/default'\nimport _debug from 'debug'\nimport type { ClientOptions } from 'ws'\nimport { type ClientRequestArgs } from 'http'\nimport * as validations from './validations'\nimport Store, { type IStore } from './store'\nimport handlePacket from './handlers'\nimport DefaultMessageIdProvider, {\n\ttype IMessageIdProvider,\n} from './default-message-id-provider'\nimport TopicAliasRecv from './topic-alias-recv'\nimport {\n\ttype DoneCallback,\n\ttype ErrorWithReasonCode,\n\tErrorWithSubackPacket,\n\ttype GenericCallback,\n\ttype IStream,\n\tMQTTJS_VERSION,\n\ttype StreamBuilder,\n\ttype TimerVariant,\n\ttype VoidCallback,\n\tnextTick,\n} from './shared'\nimport type TopicAliasSend from './topic-alias-send'\nimport { TypedEventEmitter } from './TypedEmitter'\nimport KeepaliveManager from './KeepaliveManager'\nimport isBrowser, { isWebWorker } from './is-browser'\nimport { type Timer } from './get-timer'\n\nconst setImmediate =\n\tglobalThis.setImmediate ||\n\t(((...args: any[]) => {\n\t\tconst callback = args.shift()\n\t\tnextTick(() => {\n\t\t\tcallback(...args)\n\t\t})\n\t}) as typeof globalThis.setImmediate)\n\nconst defaultConnectOptions: IClientOptions = {\n\tkeepalive: 60,\n\treschedulePings: true,\n\tprotocolId: 'MQTT',\n\tprotocolVersion: 4,\n\treconnectPeriod: 1000,\n\tconnectTimeout: 30 * 1000,\n\tclean: true,\n\tresubscribe: true,\n\tsubscribeBatchSize: null,\n\twriteCache: true,\n\ttimerVariant: 'auto',\n}\n\nexport type BaseMqttProtocol =\n\t| 'wss'\n\t| 'ws'\n\t| 'mqtt'\n\t| 'mqtts'\n\t| 'tcp'\n\t| 'ssl'\n\t| 'wx'\n\t| 'wxs'\n\t| 'ali'\n\t| 'alis'\n\n// create a type that allows all MqttProtocol + `+unix` string\nexport type MqttProtocolWithUnix = `${BaseMqttProtocol}+unix`\n\nexport type MqttProtocol = BaseMqttProtocol | MqttProtocolWithUnix\n\nexport type StorePutCallback = () => void\n\nexport interface ISecureClientOptions {\n\t/**\n\t * optional private keys in PEM format\n\t */\n\tkey?: string | string[] | Buffer | Buffer[] | any[]\n\tkeyPath?: string\n\t/**\n\t * optional cert chains in PEM format\n\t */\n\tcert?: string | string[] | Buffer | Buffer[]\n\tcertPath?: string\n\t/**\n\t * Optionally override the trusted CA certificates in PEM format\n\t */\n\tca?: string | string[] | Buffer | Buffer[]\n\tcaPaths?: string | string[]\n\n\trejectUnauthorized?: boolean\n\t/**\n\t * optional alpn's\n\t */\n\tALPNProtocols?: string[] | Buffer[] | Uint8Array[] | Buffer | Uint8Array\n}\n\nexport type AckHandler = (\n\ttopic: string,\n\tmessage: Buffer,\n\tpacket: any,\n\tcb: (error: Error | number, code?: number) => void,\n) => void\n\nexport interface IClientOptions extends ISecureClientOptions {\n\t/** CLIENT PROPERTIES */\n\n\t/** Encoding to use. Example 'binary' */\n\tencoding?: BufferEncoding\n\t/** Set browser buffer size. Default to 512KB */\n\tbrowserBufferSize?: number\n\t/** used in ws protocol to set `objectMode` */\n\tbinary?: boolean\n\t/** Used on ali protocol */\n\tmy?: any\n\t/** Manually call `connect` after creating client instance */\n\tmanualConnect?: boolean\n\t/** Custom auth packet properties */\n\tauthPacket?: Partial<IAuthPacket>\n\t/** Disable/Enable writeToStream.cacheNumbers */\n\twriteCache?: boolean\n\t/** Should be set to `host` */\n\tservername?: string\n\t/** The default protocol to use when using `servers` and no protocol is specified */\n\tdefaultProtocol?: MqttProtocol\n\t/** Support clientId passed in the query string of the url */\n\tquery?: Record<string, string>\n\t/** Auth string in the format <username>:<password> */\n\tauth?: string\n\t/** Optional SOCKS proxy to use for TCP / TLS connections , i.e. socks5://localhost:1333, socks4://localhost:1333, socks5h://localhost:1333 . Default is socks5h. */\n\tsocksProxy?: string\n\t/** Timeout for establishing a socks connection */\n\tsocksTimeout?: number\n\t/** Custom ack handler */\n\tcustomHandleAcks?: AckHandler\n\t/** Broker port */\n\tport?: number\n\t/** Broker host. Does NOT include port */\n\thost?: string\n\t/** @deprecated use `host instead */\n\thostname?: string\n\t/** Set to true if the connection is to a unix socket */\n\tunixSocket?: boolean\n\t/** Websocket `path` added as suffix or Unix socket path when `unixSocket` option is true */\n\tpath?: string\n\t/** The `MqttProtocol` to use */\n\tprotocol?: MqttProtocol\n\n\t/** Websocket options */\n\twsOptions?: ClientOptions | ClientRequestArgs | DuplexOptions\n\n\t/**\n\t * 1000 milliseconds, interval between two reconnections\n\t */\n\treconnectPeriod?: number\n\t/**\n\t * Set to true to enable the reconnect period to apply if the initial\n\t * connection is denied with an error in the CONNACK packet, such as with an\n\t * authentication error.\n\t */\n\treconnectOnConnackError?: boolean\n\t/**\n\t * 30 * 1000 milliseconds, time to wait before a CONNACK is received\n\t */\n\tconnectTimeout?: number\n\n\t/**\n\t * a Store for the incoming packets\n\t */\n\tincomingStore?: IStore\n\t/**\n\t * a Store for the outgoing packets\n\t */\n\toutgoingStore?: IStore\n\n\t/** Enable/Disable queue for QoS 0 packets */\n\tqueueQoSZero?: boolean\n\n\t/** Custom log function, default uses `debug` */\n\tlog?: (...args: any[]) => void\n\n\t/** automatically use topic alias */\n\tautoUseTopicAlias?: boolean\n\n\t/** automatically assign topic alias */\n\tautoAssignTopicAlias?: boolean\n\n\t/** Set to false to disable ping reschedule. When enabled ping messages are rescheduled on each message sent */\n\treschedulePings?: boolean\n\n\t/** List of broker servers. On each reconnect try the next server will be used */\n\tservers?: Array<{\n\t\thost: string\n\t\tport: number\n\t\tprotocol?:\n\t\t\t| 'wss'\n\t\t\t| 'ws'\n\t\t\t| 'mqtt'\n\t\t\t| 'mqtts'\n\t\t\t| 'tcp'\n\t\t\t| 'ssl'\n\t\t\t| 'wx'\n\t\t\t| 'wxs'\n\t}>\n\t/**\n\t * true, set to false to disable re-subscribe functionality\n\t */\n\tresubscribe?: boolean\n\n\t/**\n\t * Maximum number of topics to include in a single SUBSCRIBE packet.\n\t * When subscribing to more topics than this, the client will automatically\n\t * split them into batches of this size.\n\t * This is useful on AWS IoT Core, which limits each SUBSCRIBE packet to 8 topics.\n\t */\n\tsubscribeBatchSize?: number\n\n\t/** when defined this function will be called to transform the url string generated by MqttClient from provided options */\n\ttransformWsUrl?: (\n\t\turl: string,\n\t\toptions: IClientOptions,\n\t\tclient: MqttClient,\n\t) => string\n\n\t/** when defined this function will be called to create the Websocket instance, used to add custom protocols or websocket implementations */\n\tcreateWebsocket?: (\n\t\turl: string,\n\t\twebsocketSubProtocols: string[],\n\t\toptions: IClientOptions,\n\t) => any\n\n\t/** Custom message id provider */\n\tmessageIdProvider?: IMessageIdProvider\n\n\t/** When using websockets, this is the timeout used when writing to socket. Default 1000 (1s) */\n\tbrowserBufferTimeout?: number\n\n\t/**\n\t * When using websockets, this sets the `objectMode` option.\n\t * When in objectMode, streams can push Strings and Buffers\n\t * as well as any other JavaScript object.\n\t * Another major difference is that when in objectMode,\n\t * the internal buffering algorithm counts objects rather than bytes.\n\t * This means if we have a Transform stream with the highWaterMark option set to 5,\n\t * the stream will only buffer a maximum of 5 objects internally\n\t */\n\tobjectMode?: boolean\n\n\t/** CONNECT PACKET PROPERTIES */\n\n\t/**\n\t * 'mqttjs_' + Math.random().toString(16).substr(2, 8)\n\t */\n\tclientId?: string\n\t/**\n\t * 3=MQTT 3.1 4=MQTT 3.1.1 5=MQTT 5.0. Defaults to 4\n\t */\n\tprotocolVersion?: IConnectPacket['protocolVersion']\n\t/**\n\t * 'MQTT'\n\t */\n\tprotocolId?: IConnectPacket['protocolId']\n\t/**\n\t * true, set to false to receive QoS 1 and 2 messages while offline\n\t */\n\tclean?: boolean\n\t/**\n\t *  60 seconds, set to 0 to disable\n\t */\n\tkeepalive?: number\n\t/**\n\t * the username required by your broker, if any\n\t */\n\tusername?: string\n\t/**\n\t * the password required by your broker, if any\n\t */\n\tpassword?: Buffer | string\n\t/**\n\t * a message that will sent by the broker automatically when the client disconnect badly.\n\t */\n\twill?: IConnectPacket['will']\n\t/** see `connect` packet: https://github.com/mqttjs/mqtt-packet/blob/master/types/index.d.ts#L65 */\n\tproperties?: IConnectPacket['properties']\n\t/**\n\t * @description 'auto', set to 'native' or 'worker' if you're having issues with 'auto' detection\n\t * or pass a custom timer object\n\t */\n\ttimerVariant?: TimerVariant | Timer\n\t/**\n\t * false, set to true to force the use of native WebSocket if you're having issues with the detection\n\t */\n\tforceNativeWebSocket?: boolean\n}\n\nexport interface IClientPublishOptions {\n\t/**\n\t * the QoS\n\t */\n\tqos?: QoS\n\t/**\n\t * the retain flag\n\t */\n\tretain?: boolean\n\t/**\n\t * whether or not mark a message as duplicate\n\t */\n\tdup?: boolean\n\t/*\n\t *  MQTT 5.0 properties object\n\t */\n\tproperties?: IPublishPacket['properties']\n\t/**\n\t * callback called when message is put into `outgoingStore`\n\t */\n\tcbStorePut?: StorePutCallback\n}\n\nexport interface IClientReconnectOptions {\n\t/**\n\t * a Store for the incoming packets\n\t */\n\tincomingStore?: Store\n\t/**\n\t * a Store for the outgoing packets\n\t */\n\toutgoingStore?: Store\n}\nexport interface IClientSubscribeProperties {\n\t/*\n\t *  MQTT 5.0 properties object of subscribe\n\t * */\n\tproperties?: ISubscribePacket['properties']\n}\n\nexport interface IClientSubscribeOptions extends IClientSubscribeProperties {\n\t/**\n\t * the QoS\n\t */\n\tqos: QoS\n\t/*\n\t * no local flag\n\t * */\n\tnl?: boolean\n\t/*\n\t * Retain As Published flag\n\t * */\n\trap?: boolean\n\t/*\n\t * Retain Handling option\n\t * */\n\trh?: number\n}\nexport interface ISubscriptionRequest extends IClientSubscribeOptions {\n\t/**\n\t *  is a subscribed to topic\n\t */\n\ttopic: string\n}\n\nexport interface ISubscriptionGrant\n\textends Omit<ISubscriptionRequest, 'qos' | 'properties'> {\n\t/**\n\t *  is the granted qos level on it, may return 128 on error\n\t */\n\tqos: QoS | 128\n}\n\nexport type ISubscriptionMap = {\n\t/**\n\t * object which has topic names as object keys and as value the options, like {'test1': {qos: 0}, 'test2': {qos: 2}}.\n\t */\n\t[topic: string]: IClientSubscribeOptions\n} & {\n\tresubscribe?: boolean\n}\n\nexport interface IClientUnsubscribeProperties {\n\t/*\n\t *  MQTT 5.0 properties object for unsubscribe\n\t * */\n\tproperties?: IUnsubscribePacket['properties']\n}\n\nexport { IConnackPacket, IDisconnectPacket, IPublishPacket, Packet }\nexport type OnConnectCallback = (packet: IConnackPacket) => void\nexport type OnDisconnectCallback = (packet: IDisconnectPacket) => void\nexport type ClientSubscribeCallback = (\n\terr: Error | null,\n\tgranted?: ISubscriptionGrant[],\n\tpacket?: ISubackPacket,\n) => void\nexport type OnMessageCallback = (\n\ttopic: string,\n\tpayload: Buffer,\n\tpacket: IPublishPacket,\n) => void\nexport type OnPacketCallback = (packet: Packet) => void\nexport type OnCloseCallback = () => void\nexport type OnErrorCallback = (error: Error | ErrorWithReasonCode) => void\nexport type PacketCallback = (\n\terror?: Error | ErrorWithReasonCode,\n\tpacket?: Packet,\n) => any\nexport type CloseCallback = (error?: Error) => void\n\nexport interface MqttClientEventCallbacks {\n\tconnect: OnConnectCallback\n\tmessage: OnMessageCallback\n\tpacketsend: OnPacketCallback\n\tpacketreceive: OnPacketCallback\n\tdisconnect: OnDisconnectCallback\n\terror: OnErrorCallback\n\tclose: OnCloseCallback\n\tend: VoidCallback\n\treconnect: VoidCallback\n\toffline: VoidCallback\n\toutgoingEmpty: VoidCallback\n}\n\n/**\n * MqttClient constructor\n *\n * @param {Stream} stream - stream\n * @param {Object} [options] - connection options\n * (see Connection#connect)\n */\nexport default class MqttClient extends TypedEventEmitter<MqttClientEventCallbacks> {\n\tpublic static VERSION = MQTTJS_VERSION\n\n\t/** Public fields */\n\n\t/** It's true when client is connected to broker */\n\tpublic connected: boolean\n\n\tpublic disconnecting: boolean\n\n\tpublic disconnected: boolean\n\n\tpublic reconnecting: boolean\n\n\tpublic incomingStore: IStore\n\n\tpublic outgoingStore: IStore\n\n\tpublic options: IClientOptions\n\n\tpublic queueQoSZero: boolean\n\n\tpublic _reconnectCount: number\n\n\tpublic log: (...args: any[]) => void\n\n\tpublic messageIdProvider: IMessageIdProvider\n\n\tpublic outgoing: Record<\n\t\tnumber,\n\t\t{ volatile: boolean; cb: (err: Error, packet?: Packet) => void }\n\t>\n\n\tpublic messageIdToTopic: Record<number, string[]>\n\n\tpublic noop: (error?: any) => void\n\n\tpublic keepaliveManager: KeepaliveManager\n\n\t/**\n\t * The connection to the Broker. In browsers env this also have `socket` property\n\t * set to the `WebSocket` instance.\n\t */\n\tpublic stream: IStream\n\n\tpublic queue: { packet: Packet; cb: PacketCallback }[]\n\n\t/* Private fields */\n\n\t/** Function used to build the stream */\n\tprivate streamBuilder: StreamBuilder\n\n\tprivate _resubscribeTopics: ISubscriptionMap\n\n\tprivate connackTimer: NodeJS.Timeout\n\n\tprivate reconnectTimer: NodeJS.Timeout\n\n\tprivate _storeProcessing: boolean\n\n\t/** keep a reference of packets that have been successfully processed from outgoing store  */\n\tprivate _packetIdsDuringStoreProcessing: Record<number, boolean>\n\n\tprivate _storeProcessingQueue: {\n\t\tinvoke: () => any\n\t\tcbStorePut?: DoneCallback\n\t\tcallback: GenericCallback<any>\n\t}[]\n\n\tprivate _firstConnection: boolean\n\n\tprivate topicAliasRecv: TopicAliasRecv\n\n\tprivate topicAliasSend: TopicAliasSend\n\n\tprivate _deferredReconnect: () => void\n\n\tprivate connackPacket: IConnackPacket\n\n\tpublic static defaultId() {\n\t\treturn `mqttjs_${Math.random().toString(16).substr(2, 8)}`\n\t}\n\n\tconstructor(streamBuilder: StreamBuilder, options: IClientOptions) {\n\t\tsuper()\n\n\t\tthis.options = options || {}\n\n\t\t// Defaults\n\t\tfor (const k in defaultConnectOptions) {\n\t\t\tif (typeof this.options[k] === 'undefined') {\n\t\t\t\tthis.options[k] = defaultConnectOptions[k]\n\t\t\t} else {\n\t\t\t\tthis.options[k] = options[k]\n\t\t\t}\n\t\t}\n\n\t\tthis.log = this.options.log || _debug('mqttjs:client')\n\t\tthis.noop = this._noop.bind(this)\n\n\t\tthis.log('MqttClient :: version:', MqttClient.VERSION)\n\n\t\tif (isWebWorker) {\n\t\t\tthis.log('MqttClient :: environment', 'webworker')\n\t\t} else {\n\t\t\tthis.log(\n\t\t\t\t'MqttClient :: environment',\n\t\t\t\tisBrowser ? 'browser' : 'node',\n\t\t\t)\n\t\t}\n\n\t\tthis.log('MqttClient :: options.protocol', options.protocol)\n\t\tthis.log(\n\t\t\t'MqttClient :: options.protocolVersion',\n\t\t\toptions.protocolVersion,\n\t\t)\n\t\tthis.log('MqttClient :: options.username', options.username)\n\t\tthis.log('MqttClient :: options.keepalive', options.keepalive)\n\t\tthis.log(\n\t\t\t'MqttClient :: options.reconnectPeriod',\n\t\t\toptions.reconnectPeriod,\n\t\t)\n\t\tthis.log(\n\t\t\t'MqttClient :: options.rejectUnauthorized',\n\t\t\toptions.rejectUnauthorized,\n\t\t)\n\t\tthis.log(\n\t\t\t'MqttClient :: options.properties.topicAliasMaximum',\n\t\t\toptions.properties\n\t\t\t\t? options.properties.topicAliasMaximum\n\t\t\t\t: undefined,\n\t\t)\n\n\t\tthis.options.clientId =\n\t\t\ttypeof options.clientId === 'string'\n\t\t\t\t? options.clientId\n\t\t\t\t: MqttClient.defaultId()\n\n\t\tthis.log('MqttClient :: clientId', this.options.clientId)\n\n\t\tthis.options.customHandleAcks =\n\t\t\toptions.protocolVersion === 5 && options.customHandleAcks\n\t\t\t\t? options.customHandleAcks\n\t\t\t\t: (...args) => {\n\t\t\t\t\t\targs[3](null, 0)\n\t\t\t\t\t}\n\n\t\t// Disable pre-generated write cache if requested. Will allocate buffers on-the-fly instead. WARNING: This can affect write performance\n\t\tif (!this.options.writeCache) {\n\t\t\tmqttPacket.writeToStream.cacheNumbers = false\n\t\t}\n\n\t\tthis.streamBuilder = streamBuilder\n\n\t\tthis.messageIdProvider =\n\t\t\ttypeof this.options.messageIdProvider === 'undefined'\n\t\t\t\t? new DefaultMessageIdProvider()\n\t\t\t\t: this.options.messageIdProvider\n\n\t\t// Inflight message storages\n\t\tthis.outgoingStore = options.outgoingStore || new Store()\n\t\tthis.incomingStore = options.incomingStore || new Store()\n\n\t\t// Should QoS zero messages be queued when the connection is broken?\n\t\tthis.queueQoSZero =\n\t\t\toptions.queueQoSZero === undefined ? true : options.queueQoSZero\n\n\t\t// map of subscribed topics to support reconnection\n\t\tthis._resubscribeTopics = {}\n\n\t\t// map of a subscribe messageId and a topic\n\t\tthis.messageIdToTopic = {}\n\n\t\t// Keepalive manager, setup in _setupKeepaliveManager\n\t\tthis.keepaliveManager = null\n\t\t// Is the client connected?\n\t\tthis.connected = false\n\t\t// Are we disconnecting?\n\t\tthis.disconnecting = false\n\t\t// Are we reconnecting?\n\t\tthis.reconnecting = false\n\t\t// Packet queue\n\t\tthis.queue = []\n\t\t// connack timer\n\t\tthis.connackTimer = null\n\t\t// Reconnect timer\n\t\tthis.reconnectTimer = null\n\t\t// Is processing store?\n\t\tthis._storeProcessing = false\n\t\t// Packet Ids are put into the store during store processing\n\t\tthis._packetIdsDuringStoreProcessing = {}\n\t\t// Store processing queue\n\t\tthis._storeProcessingQueue = []\n\n\t\t// Inflight callbacks\n\t\tthis.outgoing = {}\n\n\t\t// True if connection is first time.\n\t\tthis._firstConnection = true\n\n\t\tif (options.properties && options.properties.topicAliasMaximum > 0) {\n\t\t\tif (options.properties.topicAliasMaximum > 0xffff) {\n\t\t\t\tthis.log(\n\t\t\t\t\t'MqttClient :: options.properties.topicAliasMaximum is out of range',\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tthis.topicAliasRecv = new TopicAliasRecv(\n\t\t\t\t\toptions.properties.topicAliasMaximum,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\t// Send queued packets\n\t\tthis.on('connect', () => {\n\t\t\tconst { queue } = this\n\n\t\t\tconst deliver = () => {\n\t\t\t\tconst entry = queue.shift()\n\t\t\t\tthis.log('deliver :: entry %o', entry)\n\t\t\t\tlet packet = null\n\n\t\t\t\tif (!entry) {\n\t\t\t\t\tthis._resubscribe()\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tpacket = entry.packet\n\t\t\t\tthis.log('deliver :: call _sendPacket for %o', packet)\n\t\t\t\tlet send = true\n\t\t\t\tif (packet.messageId && packet.messageId !== 0) {\n\t\t\t\t\tif (!this.messageIdProvider.register(packet.messageId)) {\n\t\t\t\t\t\tsend = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (send) {\n\t\t\t\t\tthis._sendPacket(packet, (err) => {\n\t\t\t\t\t\tif (entry.cb) {\n\t\t\t\t\t\t\tentry.cb(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdeliver()\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tthis.log(\n\t\t\t\t\t\t'messageId: %d has already used. The message is skipped and removed.',\n\t\t\t\t\t\tpacket.messageId,\n\t\t\t\t\t)\n\t\t\t\t\tdeliver()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.log('connect :: sending queued packets')\n\t\t\tdeliver()\n\t\t})\n\n\t\tthis.on('close', () => {\n\t\t\tthis.log('close :: connected set to `false`')\n\t\t\tthis.connected = false\n\n\t\t\tthis.log('close :: clearing connackTimer')\n\t\t\tclearTimeout(this.connackTimer)\n\n\t\t\tthis._destroyKeepaliveManager()\n\n\t\t\tif (this.topicAliasRecv) {\n\t\t\t\tthis.topicAliasRecv.clear()\n\t\t\t}\n\n\t\t\tthis.log('close :: calling _setupReconnect')\n\t\t\tthis._setupReconnect()\n\t\t})\n\n\t\tif (!this.options.manualConnect) {\n\t\t\tthis.log('MqttClient :: setting up stream')\n\t\t\tthis.connect()\n\t\t}\n\t}\n\n\t/**\n\t * @param packet the packet received by the broker\n\t * @return the auth packet to be returned to the broker\n\t * @api public\n\t */\n\tpublic handleAuth(packet: IAuthPacket, callback: PacketCallback) {\n\t\tcallback()\n\t}\n\n\t/**\n\t * Handle messages with backpressure support, one at a time.\n\t * Override at will.\n\t *\n\t * @param Packet packet the packet\n\t * @param Function callback call when finished\n\t * @api public\n\t */\n\tpublic handleMessage(packet: IPublishPacket, callback: DoneCallback) {\n\t\tcallback()\n\t}\n\n\t/**\n\t * _nextId\n\t * @return unsigned int\n\t */\n\tprivate _nextId() {\n\t\treturn this.messageIdProvider.allocate()\n\t}\n\n\t/**\n\t * getLastMessageId\n\t * @return unsigned int\n\t */\n\tpublic getLastMessageId() {\n\t\treturn this.messageIdProvider.getLastAllocated()\n\t}\n\n\t/**\n\t * Setup the event handlers in the inner stream, sends `connect` and `auth` packets\n\t */\n\tpublic connect() {\n\t\tconst writable = new Writable()\n\t\tconst parser = mqttPacket.parser(this.options)\n\n\t\tlet completeParse = null\n\t\tconst packets = []\n\n\t\tthis.log('connect :: calling method to clear reconnect')\n\t\tthis._clearReconnect()\n\n\t\tif (this.disconnected && !this.reconnecting) {\n\t\t\tthis.incomingStore = this.options.incomingStore || new Store()\n\t\t\tthis.outgoingStore = this.options.outgoingStore || new Store()\n\t\t\tthis.disconnecting = false\n\t\t\tthis.disconnected = false\n\t\t}\n\n\t\tthis.log(\n\t\t\t'connect :: using streamBuilder provided to client to create stream',\n\t\t)\n\t\tthis.stream = this.streamBuilder(this)\n\n\t\tparser.on('packet', (packet) => {\n\t\t\tthis.log('parser :: on packet push to packets array.')\n\t\t\tpackets.push(packet)\n\t\t})\n\n\t\tconst work = () => {\n\t\t\tthis.log('work :: getting next packet in queue')\n\t\t\tconst packet = packets.shift()\n\n\t\t\tif (packet) {\n\t\t\t\tthis.log('work :: packet pulled from queue')\n\t\t\t\thandlePacket(this, packet, nextTickWork)\n\t\t\t} else {\n\t\t\t\tthis.log('work :: no packets in queue')\n\t\t\t\tconst done = completeParse\n\t\t\t\tcompleteParse = null\n\t\t\t\tthis.log('work :: done flag is %s', !!done)\n\t\t\t\tif (done) done()\n\t\t\t}\n\t\t}\n\n\t\tconst nextTickWork = () => {\n\t\t\tif (packets.length) {\n\t\t\t\tnextTick(work)\n\t\t\t} else {\n\t\t\t\tconst done = completeParse\n\t\t\t\tcompleteParse = null\n\t\t\t\tdone()\n\t\t\t}\n\t\t}\n\n\t\twritable._write = (buf, enc, done) => {\n\t\t\tcompleteParse = done\n\t\t\tthis.log('writable stream :: parsing buffer')\n\t\t\tparser.parse(buf)\n\t\t\twork()\n\t\t}\n\n\t\tconst streamErrorHandler = (error) => {\n\t\t\tthis.log('streamErrorHandler :: error', error.message)\n\t\t\t// error.code will only be set on NodeJS env, browser don't allow to detect errors on sockets\n\t\t\t// also emitting errors on browsers seems to create issues\n\t\t\tif (error.code) {\n\t\t\t\t// handle error\n\t\t\t\tthis.log('streamErrorHandler :: emitting error')\n\t\t\t\tthis.emit('error', error)\n\t\t\t} else {\n\t\t\t\tthis.noop(error)\n\t\t\t}\n\t\t}\n\n\t\tthis.log('connect :: pipe stream to writable stream')\n\t\tthis.stream.pipe(writable)\n\n\t\t// Suppress connection errors\n\t\tthis.stream.on('error', streamErrorHandler)\n\n\t\t// Echo stream close\n\t\tthis.stream.on('close', () => {\n\t\t\tthis.log('(%s)stream :: on close', this.options.clientId)\n\t\t\tthis._flushVolatile()\n\t\t\tthis.log('stream: emit close to MqttClient')\n\t\t\tthis.emit('close')\n\t\t})\n\n\t\t// Send a connect packet\n\t\tthis.log('connect: sending packet `connect`')\n\n\t\tconst connectPacket: IConnectPacket = {\n\t\t\tcmd: 'connect',\n\t\t\tprotocolId: this.options.protocolId,\n\t\t\tprotocolVersion: this.options.protocolVersion,\n\t\t\tclean: this.options.clean,\n\t\t\tclientId: this.options.clientId,\n\t\t\tkeepalive: this.options.keepalive,\n\t\t\tusername: this.options.username,\n\t\t\tpassword: this.options.password as Buffer,\n\t\t\tproperties: this.options.properties,\n\t\t}\n\n\t\tif (this.options.will) {\n\t\t\tconnectPacket.will = {\n\t\t\t\t...this.options.will,\n\t\t\t\tpayload: this.options.will?.payload as Buffer,\n\t\t\t}\n\t\t}\n\n\t\tif (this.topicAliasRecv) {\n\t\t\tif (!connectPacket.properties) {\n\t\t\t\tconnectPacket.properties = {}\n\t\t\t}\n\t\t\tif (this.topicAliasRecv) {\n\t\t\t\tconnectPacket.properties.topicAliasMaximum =\n\t\t\t\t\tthis.topicAliasRecv.max\n\t\t\t}\n\t\t}\n\t\t// avoid message queue\n\t\tthis._writePacket(connectPacket)\n\n\t\t// Echo connection errors\n\t\tparser.on('error', this.emit.bind(this, 'error'))\n\n\t\t// auth\n\t\tif (this.options.properties) {\n\t\t\tif (\n\t\t\t\t!this.options.properties.authenticationMethod &&\n\t\t\t\tthis.options.properties.authenticationData\n\t\t\t) {\n\t\t\t\tthis.end(() =>\n\t\t\t\t\tthis.emit(\n\t\t\t\t\t\t'error',\n\t\t\t\t\t\tnew Error('Packet has no Authentication Method'),\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\treturn this\n\t\t\t}\n\t\t\tif (\n\t\t\t\tthis.options.properties.authenticationMethod &&\n\t\t\t\tthis.options.authPacket &&\n\t\t\t\ttypeof this.options.authPacket === 'object'\n\t\t\t) {\n\t\t\t\tconst authPacket: IAuthPacket = {\n\t\t\t\t\tcmd: 'auth',\n\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t...this.options.authPacket,\n\t\t\t\t}\n\t\t\t\tthis._writePacket(authPacket)\n\t\t\t}\n\t\t}\n\n\t\t// many drain listeners are needed for qos 1 callbacks if the connection is intermittent\n\t\tthis.stream.setMaxListeners(1000)\n\n\t\tclearTimeout(this.connackTimer)\n\t\tthis.connackTimer = setTimeout(() => {\n\t\t\tthis.log(\n\t\t\t\t'!!connectTimeout hit!! Calling _cleanUp with force `true`',\n\t\t\t)\n\t\t\tthis.emit('error', new Error('connack timeout'))\n\t\t\tthis._cleanUp(true)\n\t\t}, this.options.connectTimeout)\n\n\t\treturn this\n\t}\n\n\t/**\n\t * publish - publish <message> to <topic>\n\t *\n\t * @param {String} topic - topic to publish to\n\t * @param {String, Buffer} message - message to publish\n\t * @param {Object} [opts] - publish options, includes:\n\t *    {Number} qos - qos level to publish on\n\t *    {Boolean} retain - whether or not to retain the message\n\t *    {Boolean} dup - whether or not mark a message as duplicate\n\t *    {Function} cbStorePut - function(){} called when message is put into `outgoingStore`\n\t * @param {Function} [callback] - function(err){}\n\t *    called when publish succeeds or fails\n\t * @returns {MqttClient} this - for chaining\n\t * @api public\n\t *\n\t * @example client.publish('topic', 'message');\n\t * @example\n\t *     client.publish('topic', 'message', {qos: 1, retain: true, dup: true});\n\t * @example client.publish('topic', 'message', console.log);\n\t */\n\tpublic publish(topic: string, message: string | Buffer): MqttClient\n\tpublic publish(\n\t\ttopic: string,\n\t\tmessage: string | Buffer,\n\t\tcallback?: PacketCallback,\n\t): MqttClient\n\tpublic publish(\n\t\ttopic: string,\n\t\tmessage: string | Buffer,\n\t\topts?: IClientPublishOptions,\n\t\tcallback?: PacketCallback,\n\t): MqttClient\n\tpublic publish(\n\t\ttopic: string,\n\t\tmessage: string | Buffer,\n\t\topts?: IClientPublishOptions | DoneCallback,\n\t\tcallback?: PacketCallback,\n\t): MqttClient {\n\t\tthis.log('publish :: message `%s` to topic `%s`', message, topic)\n\t\tconst { options } = this\n\n\t\t// .publish(topic, payload, cb);\n\t\tif (typeof opts === 'function') {\n\t\t\tcallback = opts as DoneCallback\n\t\t\topts = null\n\t\t}\n\n\t\topts = opts || {}\n\n\t\t// default opts\n\t\tconst defaultOpts: IClientPublishOptions = {\n\t\t\tqos: 0,\n\t\t\tretain: false,\n\t\t\tdup: false,\n\t\t}\n\t\topts = { ...defaultOpts, ...opts }\n\n\t\tconst { qos, retain, dup, properties, cbStorePut } = opts\n\n\t\tif (this._checkDisconnecting(callback)) {\n\t\t\treturn this\n\t\t}\n\n\t\tconst publishProc = () => {\n\t\t\tlet messageId = 0\n\t\t\tif (qos === 1 || qos === 2) {\n\t\t\t\tmessageId = this._nextId()\n\t\t\t\tif (messageId === null) {\n\t\t\t\t\tthis.log('No messageId left')\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst packet: IPublishPacket = {\n\t\t\t\tcmd: 'publish',\n\t\t\t\ttopic,\n\t\t\t\tpayload: message,\n\t\t\t\tqos,\n\t\t\t\tretain,\n\t\t\t\tmessageId,\n\t\t\t\tdup,\n\t\t\t}\n\n\t\t\tif (options.protocolVersion === 5) {\n\t\t\t\tpacket.properties = properties\n\t\t\t}\n\n\t\t\tthis.log('publish :: qos', qos)\n\t\t\tswitch (qos) {\n\t\t\t\tcase 1:\n\t\t\t\tcase 2:\n\t\t\t\t\t// Add to callbacks\n\t\t\t\t\tthis.outgoing[packet.messageId] = {\n\t\t\t\t\t\tvolatile: false,\n\t\t\t\t\t\tcb: callback || this.noop,\n\t\t\t\t\t}\n\t\t\t\t\tthis.log('MqttClient:publish: packet cmd: %s', packet.cmd)\n\t\t\t\t\tthis._sendPacket(packet, undefined, cbStorePut)\n\t\t\t\t\tbreak\n\t\t\t\tdefault:\n\t\t\t\t\tthis.log('MqttClient:publish: packet cmd: %s', packet.cmd)\n\t\t\t\t\tthis._sendPacket(packet, callback, cbStorePut)\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\n\t\tif (\n\t\t\tthis._storeProcessing ||\n\t\t\tthis._storeProcessingQueue.length > 0 ||\n\t\t\t!publishProc()\n\t\t) {\n\t\t\tthis._storeProcessingQueue.push({\n\t\t\t\tinvoke: publishProc,\n\t\t\t\tcbStorePut: opts.cbStorePut,\n\t\t\t\tcallback,\n\t\t\t})\n\t\t}\n\n\t\treturn this\n\t}\n\n\tpublic publishAsync(\n\t\ttopic: string,\n\t\tmessage: string | Buffer,\n\t): Promise<Packet | undefined>\n\tpublic publishAsync(\n\t\ttopic: string,\n\t\tmessage: string | Buffer,\n\t\topts?: IClientPublishOptions,\n\t): Promise<Packet | undefined>\n\tpublic publishAsync(\n\t\ttopic: string,\n\t\tmessage: string | Buffer,\n\t\topts?: IClientPublishOptions,\n\t): Promise<Packet | undefined> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.publish(topic, message, opts, (err, packet) => {\n\t\t\t\tif (err) {\n\t\t\t\t\treject(err)\n\t\t\t\t} else {\n\t\t\t\t\tresolve(packet)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * subscribe - subscribe to <topic>\n\t *\n\t * @param {String, Array, Object} topic - topic(s) to subscribe to, supports objects in the form {'topic': qos}\n\t * @param {Object} [opts] - optional subscription options, includes:\n\t *    {Number} qos - subscribe qos level\n\t * @param {Function} [callback] - function(err, granted){} where:\n\t *    {Error} err - subscription error (none at the moment!)\n\t *    {Array} granted - array of {topic: 't', qos: 0}\n\t * @returns {MqttClient} this - for chaining\n\t * @api public\n\t * @example client.subscribe('topic');\n\t * @example client.subscribe('topic', {qos: 1});\n\t * @example client.subscribe({'topic': {qos: 0}, 'topic2': {qos: 1}}, console.log);\n\t * @example client.subscribe('topic', console.log);\n\t */\n\tpublic subscribe(\n\t\ttopicObject: string | string[] | ISubscriptionMap,\n\t): MqttClient\n\tpublic subscribe(\n\t\ttopicObject: string | string[] | ISubscriptionMap,\n\t\tcallback?: ClientSubscribeCallback,\n\t): MqttClient\n\tpublic subscribe(\n\t\ttopicObject: string | string[] | ISubscriptionMap,\n\t\topts?: IClientSubscribeOptions | IClientSubscribeProperties,\n\t): MqttClient\n\tpublic subscribe(\n\t\ttopicObject: string | string[] | ISubscriptionMap,\n\t\topts?: IClientSubscribeOptions | IClientSubscribeProperties,\n\t\tcallback?: ClientSubscribeCallback,\n\t): MqttClient\n\tpublic subscribe(\n\t\ttopicObject: string | string[] | ISubscriptionMap,\n\t\topts?:\n\t\t\t| IClientSubscribeOptions\n\t\t\t| IClientSubscribeProperties\n\t\t\t| ClientSubscribeCallback,\n\t\tcallback?: ClientSubscribeCallback,\n\t): MqttClient {\n\t\tconst version = this.options.protocolVersion\n\n\t\tif (typeof opts === 'function') {\n\t\t\tcallback = opts\n\t\t}\n\n\t\tcallback = callback || this.noop\n\n\t\t// force re-subscribe on reconnect. This is only true\n\t\t// when provided `topicObject` is `this._resubscribeTopics`\n\t\tlet resubscribe = false\n\t\tlet topicsList = []\n\n\t\tif (typeof topicObject === 'string') {\n\t\t\ttopicObject = [topicObject]\n\t\t\ttopicsList = topicObject\n\t\t} else if (Array.isArray(topicObject)) {\n\t\t\ttopicsList = topicObject\n\t\t} else if (typeof topicObject === 'object') {\n\t\t\tresubscribe = topicObject.resubscribe\n\t\t\tdelete topicObject.resubscribe\n\t\t\ttopicsList = Object.keys(topicObject)\n\t\t}\n\n\t\t// validate topics\n\t\tconst invalidTopic = validations.validateTopics(topicsList)\n\t\tif (invalidTopic !== null) {\n\t\t\tsetImmediate(callback, new Error(`Invalid topic ${invalidTopic}`))\n\t\t\treturn this\n\t\t}\n\n\t\tif (this._checkDisconnecting(callback)) {\n\t\t\tthis.log('subscribe: discconecting true')\n\t\t\treturn this\n\t\t}\n\n\t\tconst defaultOpts: Partial<IClientSubscribeOptions> = {\n\t\t\tqos: 0,\n\t\t}\n\n\t\tif (version === 5) {\n\t\t\tdefaultOpts.nl = false\n\t\t\tdefaultOpts.rap = false\n\t\t\tdefaultOpts.rh = 0\n\t\t}\n\t\topts = { ...defaultOpts, ...opts } as IClientSubscribeOptions\n\n\t\tconst { properties } = opts\n\n\t\tconst subs: ISubscriptionRequest[] = []\n\n\t\tconst parseSub = (\n\t\t\ttopic: string,\n\t\t\tsubOptions?: IClientSubscribeOptions,\n\t\t) => {\n\t\t\t// subOptions is defined only when providing a subs map, use opts otherwise\n\t\t\tsubOptions = (subOptions || opts) as IClientSubscribeOptions\n\t\t\tif (\n\t\t\t\t!Object.prototype.hasOwnProperty.call(\n\t\t\t\t\tthis._resubscribeTopics,\n\t\t\t\t\ttopic,\n\t\t\t\t) ||\n\t\t\t\tthis._resubscribeTopics[topic].qos < subOptions.qos ||\n\t\t\t\tresubscribe\n\t\t\t) {\n\t\t\t\tconst currentOpts: ISubscription & IClientSubscribeProperties =\n\t\t\t\t\t{\n\t\t\t\t\t\ttopic,\n\t\t\t\t\t\tqos: subOptions.qos,\n\t\t\t\t\t}\n\t\t\t\tif (version === 5) {\n\t\t\t\t\tcurrentOpts.nl = subOptions.nl\n\t\t\t\t\tcurrentOpts.rap = subOptions.rap\n\t\t\t\t\tcurrentOpts.rh = subOptions.rh\n\t\t\t\t\t// use opts.properties\n\t\t\t\t\tcurrentOpts.properties = properties\n\t\t\t\t}\n\t\t\t\tthis.log(\n\t\t\t\t\t'subscribe: pushing topic `%s` and qos `%s` to subs list',\n\t\t\t\t\tcurrentOpts.topic,\n\t\t\t\t\tcurrentOpts.qos,\n\t\t\t\t)\n\t\t\t\tsubs.push(currentOpts)\n\t\t\t}\n\t\t}\n\n\t\tif (Array.isArray(topicObject)) {\n\t\t\t// array of topics\n\t\t\ttopicObject.forEach((topic) => {\n\t\t\t\tthis.log('subscribe: array topic %s', topic)\n\t\t\t\tparseSub(topic)\n\t\t\t})\n\t\t} else {\n\t\t\t// object topic --> subOptions (no properties)\n\t\t\tObject.keys(topicObject).forEach((topic) => {\n\t\t\t\tthis.log(\n\t\t\t\t\t'subscribe: object topic %s, %o',\n\t\t\t\t\ttopic,\n\t\t\t\t\ttopicObject[topic],\n\t\t\t\t)\n\t\t\t\tparseSub(topic, topicObject[topic])\n\t\t\t})\n\t\t}\n\n\t\tif (!subs.length) {\n\t\t\tcallback(null, [])\n\t\t\treturn this\n\t\t}\n\n\t\tconst subscribeChunkedSubs = (\n\t\t\tchunkedSubs: ISubscriptionRequest[],\n\t\t\tmessageId: number,\n\t\t): Promise<ISubackPacket> => {\n\t\t\tconst packet: ISubscribePacket = {\n\t\t\t\tcmd: 'subscribe',\n\t\t\t\tsubscriptions: chunkedSubs,\n\t\t\t\t// qos: 1,\n\t\t\t\t// retain: false,\n\t\t\t\t// dup: false,\n\t\t\t\tmessageId,\n\t\t\t}\n\n\t\t\tif (properties) {\n\t\t\t\tpacket.properties = properties\n\t\t\t}\n\n\t\t\t// subscriptions to resubscribe to in case of disconnect\n\t\t\tif (this.options.resubscribe) {\n\t\t\t\tthis.log('subscribe :: resubscribe true')\n\t\t\t\tconst topics = []\n\t\t\t\tchunkedSubs.forEach((sub) => {\n\t\t\t\t\tif (this.options.reconnectPeriod > 0) {\n\t\t\t\t\t\tconst topic: IClientSubscribeOptions = { qos: sub.qos }\n\t\t\t\t\t\tif (version === 5) {\n\t\t\t\t\t\t\ttopic.nl = sub.nl || false\n\t\t\t\t\t\t\ttopic.rap = sub.rap || false\n\t\t\t\t\t\t\ttopic.rh = sub.rh || 0\n\t\t\t\t\t\t\ttopic.properties = sub.properties\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis._resubscribeTopics[sub.topic] = topic\n\t\t\t\t\t\ttopics.push(sub.topic)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tthis.messageIdToTopic[packet.messageId] = topics\n\t\t\t}\n\n\t\t\tconst promise = new Promise<ISubackPacket>((resolve, reject) => {\n\t\t\t\tthis.outgoing[packet.messageId] = {\n\t\t\t\t\tvolatile: true,\n\t\t\t\t\tcb(err, packet2: ISubackPacket) {\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\tconst { granted } = packet2\n\t\t\t\t\t\t\tfor (\n\t\t\t\t\t\t\t\tlet grantedI = 0;\n\t\t\t\t\t\t\t\tgrantedI < granted.length;\n\t\t\t\t\t\t\t\tgrantedI += 1\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tchunkedSubs[grantedI].qos = granted[\n\t\t\t\t\t\t\t\t\tgrantedI\n\t\t\t\t\t\t\t\t] as QoS\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!err) {\n\t\t\t\t\t\t\tresolve(packet2)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treject(\n\t\t\t\t\t\t\t\tnew ErrorWithSubackPacket(err.message, packet2),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t})\n\t\t\tthis.log('subscribe :: call _sendPacket')\n\t\t\tthis._sendPacket(packet)\n\t\t\treturn promise\n\t\t}\n\n\t\tconst subscribeProc = () => {\n\t\t\tconst batchSize = this.options.subscribeBatchSize ?? subs.length\n\t\t\tconst subscribePromises: Promise<ISubackPacket>[] = []\n\n\t\t\tfor (let i = 0; i < subs.length; i += batchSize) {\n\t\t\t\tconst chunkedSubs = subs.slice(i, i + batchSize)\n\t\t\t\tconst messageId = this._nextId()\n\t\t\t\tif (messageId === null) {\n\t\t\t\t\tthis.log('No messageId left')\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tsubscribePromises.push(\n\t\t\t\t\tsubscribeChunkedSubs(chunkedSubs, messageId),\n\t\t\t\t)\n\t\t\t}\n\t\t\tPromise.all(subscribePromises)\n\t\t\t\t.then((packets) => {\n\t\t\t\t\tcallback(null, subs, packets.at(-1))\n\t\t\t\t})\n\t\t\t\t.catch((err: ErrorWithSubackPacket) => {\n\t\t\t\t\tcallback(err, subs, err.packet)\n\t\t\t\t})\n\n\t\t\treturn true\n\t\t}\n\n\t\tif (\n\t\t\tthis._storeProcessing ||\n\t\t\tthis._storeProcessingQueue.length > 0 ||\n\t\t\t!subscribeProc()\n\t\t) {\n\t\t\tthis._storeProcessingQueue.push({\n\t\t\t\tinvoke: subscribeProc,\n\t\t\t\tcallback,\n\t\t\t})\n\t\t}\n\n\t\treturn this\n\t}\n\n\tpublic subscribeAsync(\n\t\ttopicObject: string | string[] | ISubscriptionMap,\n\t): Promise<ISubscriptionGrant[]>\n\tpublic subscribeAsync(\n\t\ttopicObject: string | string[] | ISubscriptionMap,\n\t\topts?: IClientSubscribeOptions | IClientSubscribeProperties,\n\t): Promise<ISubscriptionGrant[]>\n\tpublic subscribeAsync(\n\t\ttopicObject: string | string[] | ISubscriptionMap,\n\t\topts?: IClientSubscribeOptions | IClientSubscribeProperties,\n\t): Promise<ISubscriptionGrant[]> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.subscribe(topicObject, opts, (err, granted) => {\n\t\t\t\tif (err) {\n\t\t\t\t\treject(err)\n\t\t\t\t} else {\n\t\t\t\t\tresolve(granted)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * unsubscribe - unsubscribe from topic(s)\n\t *\n\t * @param {String, Array} topic - topics to unsubscribe from\n\t * @param {Object} [opts] - optional subscription options, includes:\n\t *    {Object} properties - properties of unsubscribe packet\n\t * @param {Function} [callback] - callback fired on unsuback\n\t * @returns {MqttClient} this - for chaining\n\t * @api public\n\t * @example client.unsubscribe('topic');\n\t * @example client.unsubscribe('topic', console.log);\n\t */\n\tpublic unsubscribe(topic: string | string[]): MqttClient\n\tpublic unsubscribe(\n\t\ttopic: string | string[],\n\t\topts?: IClientUnsubscribeProperties,\n\t): MqttClient\n\tpublic unsubscribe(\n\t\ttopic: string | string[],\n\t\tcallback?: PacketCallback,\n\t): MqttClient\n\tpublic unsubscribe(\n\t\ttopic: string | string[],\n\t\topts?: IClientUnsubscribeProperties,\n\t\tcallback?: PacketCallback,\n\t): MqttClient\n\tpublic unsubscribe(\n\t\ttopic: string | string[],\n\t\topts?: IClientUnsubscribeProperties | PacketCallback,\n\t\tcallback?: PacketCallback,\n\t): MqttClient {\n\t\tif (typeof topic === 'string') {\n\t\t\ttopic = [topic]\n\t\t}\n\n\t\tif (typeof opts === 'function') {\n\t\t\tcallback = opts\n\t\t}\n\n\t\tcallback = callback || this.noop\n\n\t\tconst invalidTopic = validations.validateTopics(topic)\n\t\tif (invalidTopic !== null) {\n\t\t\tsetImmediate(callback, new Error(`Invalid topic ${invalidTopic}`))\n\t\t\treturn this\n\t\t}\n\n\t\tif (this._checkDisconnecting(callback)) {\n\t\t\treturn this\n\t\t}\n\n\t\tconst unsubscribeProc = () => {\n\t\t\tconst messageId = this._nextId()\n\t\t\tif (messageId === null) {\n\t\t\t\tthis.log('No messageId left')\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst packet: IUnsubscribePacket = {\n\t\t\t\tcmd: 'unsubscribe',\n\t\t\t\t// qos: 1,\n\t\t\t\tmessageId,\n\t\t\t\tunsubscriptions: [],\n\t\t\t}\n\n\t\t\tif (typeof topic === 'string') {\n\t\t\t\tpacket.unsubscriptions = [topic]\n\t\t\t} else if (Array.isArray(topic)) {\n\t\t\t\tpacket.unsubscriptions = topic\n\t\t\t}\n\n\t\t\tif (this.options.resubscribe) {\n\t\t\t\tpacket.unsubscriptions.forEach((topic2) => {\n\t\t\t\t\tdelete this._resubscribeTopics[topic2]\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tif (typeof opts === 'object' && opts.properties) {\n\t\t\t\tpacket.properties = opts.properties\n\t\t\t}\n\n\t\t\tthis.outgoing[packet.messageId] = {\n\t\t\t\tvolatile: true,\n\t\t\t\tcb: callback,\n\t\t\t}\n\n\t\t\tthis.log('unsubscribe: call _sendPacket')\n\t\t\tthis._sendPacket(packet)\n\n\t\t\treturn true\n\t\t}\n\n\t\tif (\n\t\t\tthis._storeProcessing ||\n\t\t\tthis._storeProcessingQueue.length > 0 ||\n\t\t\t!unsubscribeProc()\n\t\t) {\n\t\t\tthis._storeProcessingQueue.push({\n\t\t\t\tinvoke: unsubscribeProc,\n\t\t\t\tcallback,\n\t\t\t})\n\t\t}\n\n\t\treturn this\n\t}\n\n\tpublic unsubscribeAsync(\n\t\ttopic: string | string[],\n\t): Promise<Packet | undefined>\n\tpublic unsubscribeAsync(\n\t\ttopic: string | string[],\n\t\topts?: IClientUnsubscribeProperties,\n\t): Promise<Packet | undefined>\n\tpublic unsubscribeAsync(\n\t\ttopic: string | string[],\n\t\topts?: IClientUnsubscribeProperties,\n\t): Promise<Packet | undefined> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.unsubscribe(topic, opts, (err, packet) => {\n\t\t\t\tif (err) {\n\t\t\t\t\treject(err)\n\t\t\t\t} else {\n\t\t\t\t\tresolve(packet)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * end - close connection\n\t *\n\t * @returns {MqttClient} this - for chaining\n\t * @param {Boolean} force - do not wait for all in-flight messages to be acked\n\t * @param {Object} opts - added to the disconnect packet\n\t * @param {Function} cb - called when the client has been closed\n\t *\n\t * @api public\n\t */\n\tpublic end(cb?: DoneCallback): MqttClient\n\tpublic end(force?: boolean): MqttClient\n\tpublic end(opts?: Partial<IDisconnectPacket>, cb?: DoneCallback): MqttClient\n\tpublic end(force?: boolean, cb?: DoneCallback): MqttClient\n\tpublic end(\n\t\tforce?: boolean,\n\t\topts?: Partial<IDisconnectPacket>,\n\t\tcb?: DoneCallback,\n\t): MqttClient\n\tpublic end(\n\t\tforce?: boolean | Partial<IDisconnectPacket> | DoneCallback,\n\t\topts?: Partial<IDisconnectPacket> | DoneCallback,\n\t\tcb?: DoneCallback,\n\t): MqttClient {\n\t\tthis.log('end :: (%s)', this.options.clientId)\n\n\t\tif (force == null || typeof force !== 'boolean') {\n\t\t\tcb = cb || (opts as DoneCallback)\n\t\t\topts = force as Partial<IDisconnectPacket>\n\t\t\tforce = false\n\t\t}\n\n\t\tif (typeof opts !== 'object') {\n\t\t\tcb = cb || opts\n\t\t\topts = null\n\t\t}\n\n\t\tthis.log('end :: cb? %s', !!cb)\n\n\t\tif (!cb || typeof cb !== 'function') {\n\t\t\tcb = this.noop\n\t\t}\n\n\t\tconst closeStores = () => {\n\t\t\tthis.log('end :: closeStores: closing incoming and outgoing stores')\n\t\t\tthis.disconnected = true\n\t\t\tthis.incomingStore.close((e1) => {\n\t\t\t\tthis.outgoingStore.close((e2) => {\n\t\t\t\t\tthis.log('end :: closeStores: emitting end')\n\t\t\t\t\tthis.emit('end')\n\t\t\t\t\tif (cb) {\n\t\t\t\t\t\tconst err = e1 || e2\n\t\t\t\t\t\tthis.log(\n\t\t\t\t\t\t\t'end :: closeStores: invoking callback with args',\n\t\t\t\t\t\t)\n\t\t\t\t\t\tcb(err)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t\tif (this._deferredReconnect) {\n\t\t\t\tthis._deferredReconnect()\n\t\t\t} else if (\n\t\t\t\tthis.options.reconnectPeriod === 0 ||\n\t\t\t\tthis.options.manualConnect\n\t\t\t) {\n\t\t\t\tthis.disconnecting = false\n\t\t\t}\n\t\t}\n\n\t\tconst finish = () => {\n\t\t\t// defer closesStores of an I/O cycle,\n\t\t\t// just to make sure things are\n\t\t\t// ok for websockets\n\t\t\tthis.log(\n\t\t\t\t'end :: (%s) :: finish :: calling _cleanUp with force %s',\n\t\t\t\tthis.options.clientId,\n\t\t\t\tforce,\n\t\t\t)\n\t\t\tthis._cleanUp(\n\t\t\t\t<boolean>force,\n\t\t\t\t() => {\n\t\t\t\t\tthis.log(\n\t\t\t\t\t\t'end :: finish :: calling process.nextTick on closeStores',\n\t\t\t\t\t)\n\t\t\t\t\t// const boundProcess = nextTick.bind(null, closeStores)\n\t\t\t\t\tnextTick(closeStores)\n\t\t\t\t},\n\t\t\t\topts,\n\t\t\t)\n\t\t}\n\n\t\tif (this.disconnecting) {\n\t\t\tcb()\n\t\t\treturn this\n\t\t}\n\n\t\tthis._clearReconnect()\n\n\t\tthis.disconnecting = true\n\n\t\tif (!force && Object.keys(this.outgoing).length > 0) {\n\t\t\t// wait 10ms, just to be sure we received all of it\n\t\t\tthis.log(\n\t\t\t\t'end :: (%s) :: calling finish in 10ms once outgoing is empty',\n\t\t\t\tthis.options.clientId,\n\t\t\t)\n\t\t\tthis.once('outgoingEmpty', setTimeout.bind(null, finish, 10))\n\t\t} else {\n\t\t\tthis.log(\n\t\t\t\t'end :: (%s) :: immediately calling finish',\n\t\t\t\tthis.options.clientId,\n\t\t\t)\n\t\t\tfinish()\n\t\t}\n\n\t\treturn this\n\t}\n\n\tpublic endAsync(): Promise<void>\n\tpublic endAsync(force?: boolean): Promise<void>\n\tpublic endAsync(opts?: Partial<IDisconnectPacket>): Promise<void>\n\tpublic endAsync(\n\t\tforce?: boolean,\n\t\topts?: Partial<IDisconnectPacket>,\n\t): Promise<void>\n\tpublic endAsync(\n\t\tforce?: boolean | Partial<IDisconnectPacket>,\n\t\topts?: Partial<IDisconnectPacket>,\n\t): Promise<void> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tthis.end(force as boolean, opts, (err) => {\n\t\t\t\tif (err) {\n\t\t\t\t\treject(err)\n\t\t\t\t} else {\n\t\t\t\t\tresolve()\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n\n\t/**\n\t * removeOutgoingMessage - remove a message in outgoing store\n\t * the outgoing callback will be called withe Error('Message removed') if the message is removed\n\t *\n\t * @param {Number} messageId - messageId to remove message\n\t * @returns {MqttClient} this - for chaining\n\t * @api public\n\t *\n\t * @example client.removeOutgoingMessage(client.getLastAllocated());\n\t */\n\tpublic removeOutgoingMessage(messageId: number): MqttClient {\n\t\tif (this.outgoing[messageId]) {\n\t\t\tconst { cb } = this.outgoing[messageId]\n\t\t\tthis._removeOutgoingAndStoreMessage(messageId, () => {\n\t\t\t\tcb(new Error('Message removed'))\n\t\t\t})\n\t\t}\n\t\treturn this\n\t}\n\n\t/**\n\t * reconnect - connect again using the same options as connect()\n\t *\n\t * @param {Object} [opts] - optional reconnect options, includes:\n\t *    {Store} incomingStore - a store for the incoming packets\n\t *    {Store} outgoingStore - a store for the outgoing packets\n\t *    if opts is not given, current stores are used\n\t * @returns {MqttClient} this - for chaining\n\t *\n\t * @api public\n\t */\n\tpublic reconnect(\n\t\topts?: Pick<IClientOptions, 'incomingStore' | 'outgoingStore'>,\n\t): MqttClient {\n\t\tthis.log('client reconnect')\n\t\tconst f = () => {\n\t\t\tif (opts) {\n\t\t\t\tthis.options.incomingStore = opts.incomingStore\n\t\t\t\tthis.options.outgoingStore = opts.outgoingStore\n\t\t\t} else {\n\t\t\t\tthis.options.incomingStore = null\n\t\t\t\tthis.options.outgoingStore = null\n\t\t\t}\n\t\t\tthis.incomingStore = this.options.incomingStore || new Store()\n\t\t\tthis.outgoingStore = this.options.outgoingStore || new Store()\n\t\t\tthis.disconnecting = false\n\t\t\tthis.disconnected = false\n\t\t\tthis._deferredReconnect = null\n\t\t\tthis._reconnect()\n\t\t}\n\n\t\tif (this.disconnecting && !this.disconnected) {\n\t\t\tthis._deferredReconnect = f\n\t\t} else {\n\t\t\tf()\n\t\t}\n\t\treturn this\n\t}\n\n\t/**\n\t * PRIVATE METHODS\n\t * =====================\n\t * */\n\n\t/**\n\t * Flush all outgoing messages marked as `volatile` in `outgoing` queue. Volatile messages\n\t * typically are subscription and unsubscription requests.\n\t */\n\tprivate _flushVolatile() {\n\t\tif (this.outgoing) {\n\t\t\tthis.log(\n\t\t\t\t'_flushVolatile :: deleting volatile messages from the queue and setting their callbacks as error function',\n\t\t\t)\n\t\t\tObject.keys(this.outgoing).forEach((messageId) => {\n\t\t\t\tif (\n\t\t\t\t\tthis.outgoing[messageId].volatile &&\n\t\t\t\t\ttypeof this.outgoing[messageId].cb === 'function'\n\t\t\t\t) {\n\t\t\t\t\tthis.outgoing[messageId].cb(new Error('Connection closed'))\n\t\t\t\t\tdelete this.outgoing[messageId]\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n\n\t/**\n\t * Flush all outgoing messages\n\t */\n\tprivate _flush() {\n\t\tif (this.outgoing) {\n\t\t\tthis.log('_flush: queue exists? %b', !!this.outgoing)\n\t\t\tObject.keys(this.outgoing).forEach((messageId) => {\n\t\t\t\tif (typeof this.outgoing[messageId].cb === 'function') {\n\t\t\t\t\tthis.outgoing[messageId].cb(new Error('Connection closed'))\n\t\t\t\t\t// This is suspicious.  Why do we only delete this if we have a callback?\n\t\t\t\t\t// If this is by-design, then adding no as callback would cause this to get deleted unintentionally.\n\t\t\t\t\tdelete this.outgoing[messageId]\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n\n\tprivate _removeTopicAliasAndRecoverTopicName(packet: IPublishPacket) {\n\t\tlet alias: number | undefined\n\n\t\tif (packet.properties) {\n\t\t\talias = packet.properties.topicAlias\n\t\t}\n\n\t\tlet topic = packet.topic.toString()\n\n\t\tthis.log(\n\t\t\t'_removeTopicAliasAndRecoverTopicName :: alias %d, topic %o',\n\t\t\talias,\n\t\t\ttopic,\n\t\t)\n\n\t\tif (topic.length === 0) {\n\t\t\t// restore topic from alias\n\t\t\tif (typeof alias === 'undefined') {\n\t\t\t\treturn new Error('Unregistered Topic Alias')\n\t\t\t}\n\t\t\ttopic = this.topicAliasSend.getTopicByAlias(alias)\n\t\t\tif (typeof topic === 'undefined') {\n\t\t\t\treturn new Error('Unregistered Topic Alias')\n\t\t\t}\n\t\t\tpacket.topic = topic\n\t\t}\n\t\tif (alias) {\n\t\t\tdelete packet.properties.topicAlias\n\t\t}\n\t}\n\n\tprivate _checkDisconnecting(callback?: GenericCallback<any>) {\n\t\tif (this.disconnecting) {\n\t\t\tif (callback && callback !== this.noop) {\n\t\t\t\tcallback(new Error('client disconnecting'))\n\t\t\t} else {\n\t\t\t\tthis.emit('error', new Error('client disconnecting'))\n\t\t\t}\n\t\t}\n\t\treturn this.disconnecting\n\t}\n\n\t/**\n\t * _reconnect - implement reconnection\n\t * @api private\n\t */\n\tprivate _reconnect() {\n\t\tthis.log('_reconnect: emitting reconnect to client')\n\t\tthis.emit('reconnect')\n\t\tif (this.connected) {\n\t\t\tthis.end(() => {\n\t\t\t\tthis.connect()\n\t\t\t})\n\t\t\tthis.log('client already connected. disconnecting first.')\n\t\t} else {\n\t\t\tthis.log('_reconnect: calling connect')\n\t\t\tthis.connect()\n\t\t}\n\t}\n\n\t/**\n\t * _setupReconnect - setup reconnect timer\n\t */\n\tprivate _setupReconnect() {\n\t\tif (\n\t\t\t!this.disconnecting &&\n\t\t\t!this.reconnectTimer &&\n\t\t\tthis.options.reconnectPeriod > 0\n\t\t) {\n\t\t\tif (!this.reconnecting) {\n\t\t\t\tthis.log('_setupReconnect :: emit `offline` state')\n\t\t\t\tthis.emit('offline')\n\t\t\t\tthis.log('_setupReconnect :: set `reconnecting` to `true`')\n\t\t\t\tthis.reconnecting = true\n\t\t\t}\n\t\t\tthis.log(\n\t\t\t\t'_setupReconnect :: setting reconnectTimer for %d ms',\n\t\t\t\tthis.options.reconnectPeriod,\n\t\t\t)\n\t\t\tthis.reconnectTimer = setInterval(() => {\n\t\t\t\tthis.log('reconnectTimer :: reconnect triggered!')\n\t\t\t\tthis._reconnect()\n\t\t\t}, this.options.reconnectPeriod)\n\t\t} else {\n\t\t\tthis.log('_setupReconnect :: doing nothing...')\n\t\t}\n\t}\n\n\t/**\n\t * _clearReconnect - clear the reconnect timer\n\t */\n\tprivate _clearReconnect() {\n\t\tthis.log('_clearReconnect : clearing reconnect timer')\n\t\tif (this.reconnectTimer) {\n\t\t\tclearInterval(this.reconnectTimer)\n\t\t\tthis.reconnectTimer = null\n\t\t}\n\t}\n\n\t/**\n\t * _cleanUp - clean up on connection end\n\t * @api private\n\t */\n\tprivate _cleanUp(forced: boolean, done?: DoneCallback, opts = {}) {\n\t\tif (done) {\n\t\t\tthis.log('_cleanUp :: done callback provided for on stream close')\n\t\t\tthis.stream.on('close', done)\n\t\t}\n\n\t\tthis.log('_cleanUp :: forced? %s', forced)\n\t\tif (forced) {\n\t\t\tif (this.options.reconnectPeriod === 0 && this.options.clean) {\n\t\t\t\tthis._flush()\n\t\t\t}\n\t\t\tthis.log(\n\t\t\t\t'_cleanUp :: (%s) :: destroying stream',\n\t\t\t\tthis.options.clientId,\n\t\t\t)\n\t\t\tthis.stream.destroy()\n\t\t} else {\n\t\t\tconst packet: IDisconnectPacket = { cmd: 'disconnect', ...opts }\n\t\t\tthis.log(\n\t\t\t\t'_cleanUp :: (%s) :: call _sendPacket with disconnect packet',\n\t\t\t\tthis.options.clientId,\n\t\t\t)\n\t\t\tthis._sendPacket(packet, () => {\n\t\t\t\tthis.log(\n\t\t\t\t\t'_cleanUp :: (%s) :: destroying stream',\n\t\t\t\t\tthis.options.clientId,\n\t\t\t\t)\n\t\t\t\tsetImmediate(() => {\n\t\t\t\t\tthis.stream.end(() => {\n\t\t\t\t\t\tthis.log(\n\t\t\t\t\t\t\t'_cleanUp :: (%s) :: stream destroyed',\n\t\t\t\t\t\t\tthis.options.clientId,\n\t\t\t\t\t\t)\n\t\t\t\t\t\t// once stream is closed the 'close' event will fire and that will\n\t\t\t\t\t\t// emit client `close` event and call `done` callback if done is provided\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\n\t\tif (!this.disconnecting && !this.reconnecting) {\n\t\t\tthis.log(\n\t\t\t\t'_cleanUp :: client not disconnecting/reconnecting. Clearing and resetting reconnect.',\n\t\t\t)\n\t\t\tthis._clearReconnect()\n\t\t\tthis._setupReconnect()\n\t\t}\n\n\t\tthis._destroyKeepaliveManager()\n\n\t\tif (done && !this.connected) {\n\t\t\tthis.log(\n\t\t\t\t'_cleanUp :: (%s) :: removing stream `done` callback `close` listener',\n\t\t\t\tthis.options.clientId,\n\t\t\t)\n\t\t\tthis.stream.removeListener('close', done)\n\t\t\tdone()\n\t\t}\n\t}\n\n\tprivate _storeAndSend(\n\t\tpacket: Packet,\n\t\tcb: DoneCallback,\n\t\tcbStorePut: DoneCallback,\n\t) {\n\t\tthis.log(\n\t\t\t'storeAndSend :: store packet with cmd %s to outgoingStore',\n\t\t\tpacket.cmd,\n\t\t)\n\t\tlet storePacket = packet\n\t\tlet err: Error | undefined\n\t\tif (storePacket.cmd === 'publish') {\n\t\t\t// The original packet is for sending.\n\t\t\t// The cloned storePacket is for storing to resend on reconnect.\n\t\t\t// Topic Alias must not be used after disconnected.\n\t\t\tstorePacket = clone(packet)\n\t\t\terr = this._removeTopicAliasAndRecoverTopicName(\n\t\t\t\tstorePacket as IPublishPacket,\n\t\t\t)\n\t\t\tif (err) {\n\t\t\t\treturn cb && cb(err)\n\t\t\t}\n\t\t}\n\t\tthis.outgoingStore.put(storePacket, (err2) => {\n\t\t\tif (err2) {\n\t\t\t\treturn cb && cb(err2)\n\t\t\t}\n\t\t\tcbStorePut()\n\t\t\tthis._writePacket(packet, cb)\n\t\t})\n\t}\n\n\tprivate _applyTopicAlias(packet: Packet) {\n\t\tif (this.options.protocolVersion === 5) {\n\t\t\tif (packet.cmd === 'publish') {\n\t\t\t\tlet alias: number\n\t\t\t\tif (packet.properties) {\n\t\t\t\t\talias = packet.properties.topicAlias\n\t\t\t\t}\n\t\t\t\tconst topic = packet.topic.toString()\n\t\t\t\tif (this.topicAliasSend) {\n\t\t\t\t\tif (alias) {\n\t\t\t\t\t\tif (topic.length !== 0) {\n\t\t\t\t\t\t\t// register topic alias\n\t\t\t\t\t\t\tthis.log(\n\t\t\t\t\t\t\t\t'applyTopicAlias :: register topic: %s - alias: %d',\n\t\t\t\t\t\t\t\ttopic,\n\t\t\t\t\t\t\t\talias,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tif (!this.topicAliasSend.put(topic, alias)) {\n\t\t\t\t\t\t\t\tthis.log(\n\t\t\t\t\t\t\t\t\t'applyTopicAlias :: error out of range. topic: %s - alias: %d',\n\t\t\t\t\t\t\t\t\ttopic,\n\t\t\t\t\t\t\t\t\talias,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\treturn new Error(\n\t\t\t\t\t\t\t\t\t'Sending Topic Alias out of range',\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (topic.length !== 0) {\n\t\t\t\t\t\tif (this.options.autoAssignTopicAlias) {\n\t\t\t\t\t\t\talias = this.topicAliasSend.getAliasByTopic(topic)\n\t\t\t\t\t\t\tif (alias) {\n\t\t\t\t\t\t\t\tpacket.topic = ''\n\t\t\t\t\t\t\t\tpacket.properties = {\n\t\t\t\t\t\t\t\t\t...packet.properties,\n\t\t\t\t\t\t\t\t\ttopicAlias: alias,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.log(\n\t\t\t\t\t\t\t\t\t'applyTopicAlias :: auto assign(use) topic: %s - alias: %d',\n\t\t\t\t\t\t\t\t\ttopic,\n\t\t\t\t\t\t\t\t\talias,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\talias = this.topicAliasSend.getLruAlias()\n\t\t\t\t\t\t\t\tthis.topicAliasSend.put(topic, alias)\n\t\t\t\t\t\t\t\tpacket.properties = {\n\t\t\t\t\t\t\t\t\t...packet.properties,\n\t\t\t\t\t\t\t\t\ttopicAlias: alias,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.log(\n\t\t\t\t\t\t\t\t\t'applyTopicAlias :: auto assign topic: %s - alias: %d',\n\t\t\t\t\t\t\t\t\ttopic,\n\t\t\t\t\t\t\t\t\talias,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (this.options.autoUseTopicAlias) {\n\t\t\t\t\t\t\talias = this.topicAliasSend.getAliasByTopic(topic)\n\t\t\t\t\t\t\tif (alias) {\n\t\t\t\t\t\t\t\tpacket.topic = ''\n\t\t\t\t\t\t\t\tpacket.properties = {\n\t\t\t\t\t\t\t\t\t...packet.properties,\n\t\t\t\t\t\t\t\t\ttopicAlias: alias,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tthis.log(\n\t\t\t\t\t\t\t\t\t'applyTopicAlias :: auto use topic: %s - alias: %d',\n\t\t\t\t\t\t\t\t\ttopic,\n\t\t\t\t\t\t\t\t\talias,\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (alias) {\n\t\t\t\t\tthis.log(\n\t\t\t\t\t\t'applyTopicAlias :: error out of range. topic: %s - alias: %d',\n\t\t\t\t\t\ttopic,\n\t\t\t\t\t\talias,\n\t\t\t\t\t)\n\t\t\t\t\treturn new Error('Sending Topic Alias out of range')\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate _noop(err?: Error) {\n\t\tthis.log('noop ::', err)\n\t}\n\n\t/** Writes the packet to stream and emits events */\n\tprivate _writePacket(packet: Packet, cb?: DoneCallback) {\n\t\tthis.log('_writePacket :: packet: %O', packet)\n\t\tthis.log('_writePacket :: emitting `packetsend`')\n\n\t\tthis.emit('packetsend', packet)\n\n\t\tthis.log('_writePacket :: writing to stream')\n\t\tconst result = mqttPacket.writeToStream(\n\t\t\tpacket,\n\t\t\tthis.stream,\n\t\t\tthis.options,\n\t\t)\n\t\tthis.log('_writePacket :: writeToStream result %s', result)\n\t\tif (!result && cb && cb !== this.noop) {\n\t\t\tthis.log(\n\t\t\t\t'_writePacket :: handle events on `drain` once through callback.',\n\t\t\t)\n\t\t\tthis.stream.once('drain', cb)\n\t\t} else if (cb) {\n\t\t\tthis.log('_writePacket :: invoking cb')\n\t\t\tcb()\n\t\t}\n\t}\n\n\t/**\n\t * _sendPacket - send or queue a packet\n\t * @param {Object} packet - packet options\n\t * @param {Function} cb - callback when the packet is sent\n\t * @param {Function} cbStorePut - called when message is put into outgoingStore\n\t * @param {Boolean} noStore - send without put to the store\n\t * @api private\n\t */\n\tprivate _sendPacket(\n\t\tpacket: Packet,\n\t\tcb?: DoneCallback,\n\t\tcbStorePut?: DoneCallback,\n\t\tnoStore?: boolean,\n\t) {\n\t\tthis.log('_sendPacket :: (%s) ::  start', this.options.clientId)\n\t\tcbStorePut = cbStorePut || this.noop\n\t\tcb = cb || this.noop\n\n\t\tconst err = this._applyTopicAlias(packet)\n\t\tif (err) {\n\t\t\tcb(err)\n\t\t\treturn\n\t\t}\n\n\t\tif (!this.connected) {\n\t\t\t// allow auth packets to be sent while authenticating with the broker (mqtt5 enhanced auth)\n\t\t\tif (packet.cmd === 'auth') {\n\t\t\t\tthis._writePacket(packet, cb)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.log(\n\t\t\t\t'_sendPacket :: client not connected. Storing packet offline.',\n\t\t\t)\n\t\t\tthis._storePacket(packet, cb, cbStorePut)\n\t\t\treturn\n\t\t}\n\n\t\t// If \"noStore\" is true, the message is sent without being recorded in the store.\n\t\t// Messages that have not received puback or pubcomp remain in the store after disconnection\n\t\t// and are resent from the store upon reconnection.\n\t\t// For resend upon reconnection, \"noStore\" is set to true. This is because the message is already stored in the store.\n\t\t// This is to avoid interrupting other processes while recording to the store.\n\t\tif (noStore) {\n\t\t\tthis._writePacket(packet, cb)\n\t\t\treturn\n\t\t}\n\n\t\tswitch (packet.cmd) {\n\t\t\tcase 'publish':\n\t\t\t\tbreak\n\t\t\tcase 'pubrel':\n\t\t\t\tthis._storeAndSend(packet, cb, cbStorePut)\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tthis._writePacket(packet, cb)\n\t\t\t\treturn\n\t\t}\n\n\t\tswitch (packet.qos) {\n\t\t\tcase 2:\n\t\t\tcase 1:\n\t\t\t\tthis._storeAndSend(packet, cb, cbStorePut)\n\t\t\t\tbreak\n\t\t\t/**\n\t\t\t * no need of case here since it will be caught by default\n\t\t\t * and jshint comply that before default it must be a break\n\t\t\t * anyway it will result in -1 evaluation\n\t\t\t */\n\t\t\tcase 0:\n\t\t\t/* falls through */\n\t\t\tdefault:\n\t\t\t\tthis._writePacket(packet, cb)\n\t\t\t\tbreak\n\t\t}\n\t\tthis.log('_sendPacket :: (%s) ::  end', this.options.clientId)\n\t}\n\n\t/**\n\t * _storePacket - queue a packet\n\t * @param {Object} packet - packet options\n\t * @param {Function} cb - callback when the packet is sent\n\t * @param {Function} cbStorePut - called when message is put into outgoingStore\n\t * @api private\n\t */\n\tprivate _storePacket(\n\t\tpacket: Packet,\n\t\tcb: DoneCallback,\n\t\tcbStorePut: DoneCallback,\n\t) {\n\t\tthis.log('_storePacket :: packet: %o', packet)\n\t\tthis.log('_storePacket :: cb? %s', !!cb)\n\t\tcbStorePut = cbStorePut || this.noop\n\n\t\tlet storePacket = packet\n\t\tif (storePacket.cmd === 'publish') {\n\t\t\t// The original packet is for sending.\n\t\t\t// The cloned storePacket is for storing to resend on reconnect.\n\t\t\t// Topic Alias must not be used after disconnected.\n\t\t\tstorePacket = clone(packet)\n\t\t\tconst err = this._removeTopicAliasAndRecoverTopicName(\n\t\t\t\tstorePacket as IPublishPacket,\n\t\t\t)\n\t\t\tif (err) {\n\t\t\t\treturn cb && cb(err)\n\t\t\t}\n\t\t}\n\n\t\tconst qos = (storePacket as IPublishPacket).qos || 0\n\t\t// check that the packet is not a qos of 0, or that the command is not a publish\n\t\tif ((qos === 0 && this.queueQoSZero) || storePacket.cmd !== 'publish') {\n\t\t\tthis.queue.push({ packet: storePacket, cb })\n\t\t} else if (qos > 0) {\n\t\t\tcb = this.outgoing[storePacket.messageId]\n\t\t\t\t? this.outgoing[storePacket.messageId].cb\n\t\t\t\t: null\n\t\t\tthis.outgoingStore.put(storePacket, (err) => {\n\t\t\t\tif (err) {\n\t\t\t\t\treturn cb && cb(err)\n\t\t\t\t}\n\t\t\t\tcbStorePut()\n\t\t\t})\n\t\t} else if (cb) {\n\t\t\tcb(new Error('No connection to broker'))\n\t\t}\n\t}\n\n\t/**\n\t * _setupKeepaliveManager - setup the keepalive manager\n\t */\n\tprivate _setupKeepaliveManager() {\n\t\tthis.log(\n\t\t\t'_setupKeepaliveManager :: keepalive %d (seconds)',\n\t\t\tthis.options.keepalive,\n\t\t)\n\n\t\tif (!this.keepaliveManager && this.options.keepalive) {\n\t\t\tthis.keepaliveManager = new KeepaliveManager(\n\t\t\t\tthis,\n\t\t\t\tthis.options.timerVariant,\n\t\t\t)\n\t\t}\n\t}\n\n\tprivate _destroyKeepaliveManager() {\n\t\tif (this.keepaliveManager) {\n\t\t\tthis.log('_destroyKeepaliveManager :: destroying keepalive manager')\n\t\t\tthis.keepaliveManager.destroy()\n\t\t\tthis.keepaliveManager = null\n\t\t}\n\t}\n\n\t/**\n\t * Reschedule the ping interval\n\t */\n\tpublic reschedulePing(force = false) {\n\t\tif (\n\t\t\tthis.keepaliveManager &&\n\t\t\tthis.options.keepalive &&\n\t\t\t(force || this.options.reschedulePings)\n\t\t) {\n\t\t\tthis._reschedulePing()\n\t\t}\n\t}\n\n\t/**\n\t * Mostly needed for test purposes\n\t */\n\tprivate _reschedulePing() {\n\t\tthis.log('_reschedulePing :: rescheduling ping')\n\t\tthis.keepaliveManager.reschedule()\n\t}\n\n\tpublic sendPing() {\n\t\tthis.log('_sendPing :: sending pingreq')\n\t\tthis._sendPacket({ cmd: 'pingreq' })\n\t}\n\n\tpublic onKeepaliveTimeout() {\n\t\tthis.emit('error', new Error('Keepalive timeout'))\n\t\tthis.log('onKeepaliveTimeout :: calling _cleanUp with force true')\n\t\tthis._cleanUp(true)\n\t}\n\n\t/**\n\t * _resubscribe\n\t * @api private\n\t */\n\tprivate _resubscribe() {\n\t\tthis.log('_resubscribe')\n\t\tconst _resubscribeTopicsKeys = Object.keys(this._resubscribeTopics)\n\t\tif (\n\t\t\t!this._firstConnection &&\n\t\t\t// Only resubscribe in case of clean connection or if the server does not have a stored session.\n\t\t\t// The Session Present flag is available since v3.1.1\n\t\t\t// https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349254\n\t\t\t(this.options.clean ||\n\t\t\t\t(this.options.protocolVersion >= 4 &&\n\t\t\t\t\t!this.connackPacket.sessionPresent)) &&\n\t\t\t_resubscribeTopicsKeys.length > 0\n\t\t) {\n\t\t\tif (this.options.resubscribe) {\n\t\t\t\tif (this.options.protocolVersion === 5) {\n\t\t\t\t\tthis.log('_resubscribe: protocolVersion 5')\n\t\t\t\t\tfor (\n\t\t\t\t\t\tlet topicI = 0;\n\t\t\t\t\t\ttopicI < _resubscribeTopicsKeys.length;\n\t\t\t\t\t\ttopicI++\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst resubscribeTopic: ISubscriptionMap = {}\n\t\t\t\t\t\tresubscribeTopic[_resubscribeTopicsKeys[topicI]] =\n\t\t\t\t\t\t\tthis._resubscribeTopics[\n\t\t\t\t\t\t\t\t_resubscribeTopicsKeys[topicI]\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\tresubscribeTopic.resubscribe = true\n\t\t\t\t\t\tthis.subscribe(resubscribeTopic, {\n\t\t\t\t\t\t\tproperties:\n\t\t\t\t\t\t\t\tresubscribeTopic[_resubscribeTopicsKeys[topicI]]\n\t\t\t\t\t\t\t\t\t.properties,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthis._resubscribeTopics.resubscribe = true\n\t\t\t\t\tthis.subscribe(this._resubscribeTopics)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis._resubscribeTopics = {}\n\t\t\t}\n\t\t}\n\n\t\tthis._firstConnection = false\n\t}\n\n\t/**\n\t * _onConnect\n\t *\n\t * @api private\n\t */\n\tprivate _onConnect(packet: IConnackPacket) {\n\t\tif (this.disconnected) {\n\t\t\tthis.emit('connect', packet)\n\t\t\treturn\n\t\t}\n\n\t\tthis.connackPacket = packet\n\t\tthis.messageIdProvider.clear()\n\t\tthis._setupKeepaliveManager()\n\n\t\tthis.connected = true\n\n\t\t/** check if there are packets in outgoing store and stream them */\n\t\tconst startStreamProcess = () => {\n\t\t\tlet outStore = this.outgoingStore.createStream()\n\n\t\t\t/** destroy the outgoing store stream */\n\t\t\tconst remove = () => {\n\t\t\t\toutStore.destroy()\n\t\t\t\toutStore = null\n\t\t\t\tthis._flushStoreProcessingQueue()\n\t\t\t\tclearStoreProcessing()\n\t\t\t}\n\n\t\t\t/** stop store processing and clear packets id processed */\n\t\t\tconst clearStoreProcessing = () => {\n\t\t\t\tthis._storeProcessing = false\n\t\t\t\tthis._packetIdsDuringStoreProcessing = {}\n\t\t\t}\n\n\t\t\tthis.once('close', remove)\n\t\t\toutStore.on('error', (err) => {\n\t\t\t\tclearStoreProcessing()\n\t\t\t\tthis._flushStoreProcessingQueue()\n\t\t\t\tthis.removeListener('close', remove)\n\t\t\t\tthis.emit('error', err)\n\t\t\t})\n\n\t\t\t/** Read next packet in outgoing store and send it */\n\t\t\tconst storeDeliver = () => {\n\t\t\t\t// edge case, we wrapped this twice\n\t\t\t\tif (!outStore) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tconst packet2 = outStore.read(1)\n\n\t\t\t\tlet cb: PacketCallback\n\n\t\t\t\tif (!packet2) {\n\t\t\t\t\t// read when data is available in the future\n\t\t\t\t\toutStore.once('readable', storeDeliver)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tthis._storeProcessing = true\n\n\t\t\t\t// Skip already processed store packets\n\t\t\t\tif (this._packetIdsDuringStoreProcessing[packet2.messageId]) {\n\t\t\t\t\tstoreDeliver()\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Avoid unnecessary stream read operations when disconnected\n\t\t\t\tif (!this.disconnecting && !this.reconnectTimer) {\n\t\t\t\t\tcb = this.outgoing[packet2.messageId]\n\t\t\t\t\t\t? this.outgoing[packet2.messageId].cb\n\t\t\t\t\t\t: null\n\t\t\t\t\tthis.outgoing[packet2.messageId] = {\n\t\t\t\t\t\tvolatile: false,\n\t\t\t\t\t\tcb(err, status) {\n\t\t\t\t\t\t\t// Ensure that the original callback passed in to publish gets invoked\n\t\t\t\t\t\t\tif (cb) {\n\t\t\t\t\t\t\t\tcb(err, status)\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tstoreDeliver()\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t\tthis._packetIdsDuringStoreProcessing[packet2.messageId] =\n\t\t\t\t\t\ttrue\n\t\t\t\t\tif (this.messageIdProvider.register(packet2.messageId)) {\n\t\t\t\t\t\tthis._sendPacket(packet2, undefined, undefined, true)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.log(\n\t\t\t\t\t\t\t'messageId: %d has already used.',\n\t\t\t\t\t\t\tpacket2.messageId,\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t} else if (outStore.destroy) {\n\t\t\t\t\toutStore.destroy()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\toutStore.on('end', () => {\n\t\t\t\tlet allProcessed = true\n\t\t\t\tfor (const id in this._packetIdsDuringStoreProcessing) {\n\t\t\t\t\tif (!this._packetIdsDuringStoreProcessing[id]) {\n\t\t\t\t\t\tallProcessed = false\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.removeListener('close', remove)\n\t\t\t\tif (allProcessed) {\n\t\t\t\t\tclearStoreProcessing()\n\t\t\t\t\tthis._invokeAllStoreProcessingQueue()\n\t\t\t\t\tthis.emit('connect', packet)\n\t\t\t\t} else {\n\t\t\t\t\tstartStreamProcess()\n\t\t\t\t}\n\t\t\t})\n\t\t\tstoreDeliver()\n\t\t}\n\t\t// start flowing\n\t\tstartStreamProcess()\n\t}\n\n\tprivate _invokeStoreProcessingQueue() {\n\t\t// If _storeProcessing is true, the message is resending.\n\t\t// During resend, processing is skipped to prevent new messages from interrupting. #1635\n\t\tif (!this._storeProcessing && this._storeProcessingQueue.length > 0) {\n\t\t\tconst f = this._storeProcessingQueue[0]\n\t\t\tif (f && f.invoke()) {\n\t\t\t\tthis._storeProcessingQueue.shift()\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\tprivate _invokeAllStoreProcessingQueue() {\n\t\twhile (this._invokeStoreProcessingQueue()) {\n\t\t\t/* empty */\n\t\t}\n\t}\n\n\tprivate _flushStoreProcessingQueue() {\n\t\tfor (const f of this._storeProcessingQueue) {\n\t\t\tif (f.cbStorePut) f.cbStorePut(new Error('Connection closed'))\n\t\t\tif (f.callback) f.callback(new Error('Connection closed'))\n\t\t}\n\t\tthis._storeProcessingQueue.splice(0)\n\t}\n\n\t/**\n\t * _removeOutgoingAndStoreMessage\n\t * @param {Number} messageId - messageId to remove message\n\t * @param {Function} cb - called when the message removed\n\t * @api private\n\t */\n\tprivate _removeOutgoingAndStoreMessage(\n\t\tmessageId: number,\n\t\tcb: PacketCallback,\n\t) {\n\t\tdelete this.outgoing[messageId]\n\t\tthis.outgoingStore.del({ messageId }, (err, packet) => {\n\t\t\tcb(err, packet)\n\t\t\tthis.messageIdProvider.deallocate(messageId)\n\t\t\tthis._invokeStoreProcessingQueue()\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "src/lib/connect/ali.ts",
    "content": "import { Buffer } from 'buffer'\nimport { Transform } from 'readable-stream'\nimport { type IStream, type StreamBuilder } from '../shared'\nimport { type IClientOptions } from '../client'\nimport type MqttClient from '../client'\nimport { BufferedDuplex } from '../BufferedDuplex'\n\nlet my: any\nlet proxy: Transform\nlet stream: BufferedDuplex\nlet isInitialized = false\n\nfunction buildProxy() {\n\tconst _proxy = new Transform()\n\t_proxy._write = (chunk, encoding, next) => {\n\t\tmy.sendSocketMessage({\n\t\t\tdata: chunk.buffer,\n\t\t\tsuccess() {\n\t\t\t\tnext()\n\t\t\t},\n\t\t\tfail() {\n\t\t\t\tnext(new Error())\n\t\t\t},\n\t\t})\n\t}\n\t_proxy._flush = (done) => {\n\t\tmy.closeSocket({\n\t\t\tsuccess() {\n\t\t\t\tdone()\n\t\t\t},\n\t\t})\n\t}\n\n\treturn _proxy\n}\n\nfunction setDefaultOpts(opts: IClientOptions) {\n\tif (!opts.hostname) {\n\t\topts.hostname = 'localhost'\n\t}\n\tif (!opts.path) {\n\t\topts.path = '/'\n\t}\n\n\tif (!opts.wsOptions) {\n\t\topts.wsOptions = {}\n\t}\n}\n\nfunction buildUrl(opts: IClientOptions, client: MqttClient) {\n\tconst protocol = opts.protocol === 'alis' ? 'wss' : 'ws'\n\tlet url = `${protocol}://${opts.hostname}${opts.path}`\n\tif (opts.port && opts.port !== 80 && opts.port !== 443) {\n\t\turl = `${protocol}://${opts.hostname}:${opts.port}${opts.path}`\n\t}\n\tif (typeof opts.transformWsUrl === 'function') {\n\t\turl = opts.transformWsUrl(url, opts, client)\n\t}\n\treturn url\n}\n\nfunction bindEventHandler() {\n\tif (isInitialized) return\n\n\tisInitialized = true\n\n\tmy.onSocketOpen(() => {\n\t\tstream.socketReady()\n\t})\n\n\tmy.onSocketMessage((res) => {\n\t\tif (typeof res.data === 'string') {\n\t\t\tconst buffer = Buffer.from(res.data, 'base64')\n\t\t\tproxy.push(buffer)\n\t\t} else {\n\t\t\tconst reader = new FileReader()\n\t\t\treader.addEventListener('load', () => {\n\t\t\t\tif (reader.result instanceof ArrayBuffer) {\n\t\t\t\t\tproxy.push(Buffer.from(reader.result))\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tproxy.push(Buffer.from(reader.result, 'utf-8'))\n\t\t\t})\n\t\t\treader.readAsArrayBuffer(res.data)\n\t\t}\n\t})\n\n\tmy.onSocketClose(() => {\n\t\tstream.end()\n\t\tstream.destroy()\n\t})\n\n\tmy.onSocketError((err) => {\n\t\tstream.destroy(err)\n\t})\n}\n\nconst buildStream: StreamBuilder = (client, opts): IStream => {\n\topts.hostname = opts.hostname || opts.host\n\n\tif (!opts.hostname) {\n\t\tthrow new Error('Could not determine host. Specify host manually.')\n\t}\n\n\tconst websocketSubProtocol =\n\t\topts.protocolId === 'MQIsdp' && opts.protocolVersion === 3\n\t\t\t? 'mqttv3.1'\n\t\t\t: 'mqtt'\n\n\tsetDefaultOpts(opts)\n\n\tconst url = buildUrl(opts, client)\n\tmy = opts.my\n\t// https://miniprogram.alipay.com/docs/miniprogram/mpdev/api_network_connectsocket\n\tmy.connectSocket({\n\t\turl,\n\t\tprotocols: websocketSubProtocol,\n\t})\n\n\tproxy = buildProxy()\n\tstream = new BufferedDuplex(opts, proxy, my)\n\n\tbindEventHandler()\n\n\treturn stream\n}\n\nexport default buildStream\n"
  },
  {
    "path": "src/lib/connect/index.ts",
    "content": "/* eslint-disable @typescript-eslint/no-require-imports */\nimport _debug from 'debug'\nimport url from 'url'\nimport MqttClient, {\n\ttype IClientOptions,\n\ttype MqttClientEventCallbacks,\n\ttype MqttProtocol,\n} from '../client'\nimport isBrowser from '../is-browser'\nimport { type StreamBuilder } from '../shared'\n\n// Handling the process.nextTick is not a function error in react-native applications.\nif (typeof process?.nextTick !== 'function') {\n\tprocess.nextTick = setImmediate\n}\n\nconst debug = _debug('mqttjs')\n\nlet protocols: Record<string, StreamBuilder> = null\n\n/**\n * Parse the auth attribute and merge username and password in the options object.\n *\n * @param {Object} [opts] option object\n */\nfunction parseAuthOptions(opts: IClientOptions) {\n\tlet matches: RegExpMatchArray | null\n\tif (opts.auth) {\n\t\tmatches = opts.auth.match(/^(.+):(.+)$/)\n\t\tif (matches) {\n\t\t\tconst [, username, password] = matches\n\t\t\topts.username = username\n\t\t\topts.password = password\n\t\t} else {\n\t\t\topts.username = opts.auth\n\t\t}\n\t}\n}\n\n/**\n * connect - connect to an MQTT broker.\n */\nfunction connect(brokerUrl: string): MqttClient\nfunction connect(opts: IClientOptions): MqttClient\nfunction connect(brokerUrl: string, opts?: IClientOptions): MqttClient\nfunction connect(\n\tbrokerUrl: string | IClientOptions,\n\topts?: IClientOptions,\n): MqttClient {\n\tdebug('connecting to an MQTT broker...')\n\tif (typeof brokerUrl === 'object' && !opts) {\n\t\topts = brokerUrl\n\t\tbrokerUrl = ''\n\t}\n\n\topts = opts || {}\n\n\t// try to parse the broker url\n\tif (brokerUrl && typeof brokerUrl === 'string') {\n\t\tconst parsedUrl = url.parse(brokerUrl, true)\n\t\tconst parsedOptions: Partial<IClientOptions> = {}\n\n\t\tif (parsedUrl.port != null) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t\t// @ts-ignore\n\t\t\tparsedOptions.port = Number(parsedUrl.port)\n\t\t}\n\n\t\tparsedOptions.host = parsedUrl.hostname\n\t\tparsedOptions.query = parsedUrl.query as Record<string, string>\n\t\tparsedOptions.auth = parsedUrl.auth\n\t\tparsedOptions.protocol = parsedUrl.protocol as MqttProtocol\n\t\tparsedOptions.path = parsedUrl.path\n\n\t\topts = { ...parsedOptions, ...opts }\n\n\t\t// when parsing an url expect the protocol to be set\n\t\tif (!opts.protocol) {\n\t\t\tthrow new Error('Missing protocol')\n\t\t}\n\n\t\topts.protocol = opts.protocol.replace(/:$/, '') as MqttProtocol\n\t}\n\n\topts.unixSocket = opts.unixSocket || opts.protocol?.includes('+unix')\n\n\tif (opts.unixSocket) {\n\t\topts.protocol = opts.protocol.replace('+unix', '') as MqttProtocol\n\t} else if (\n\t\t!opts.protocol?.startsWith('ws') &&\n\t\t!opts.protocol?.startsWith('wx')\n\t) {\n\t\t// consider path only with ws protocol or unix socket\n\t\t// url.parse could return path (for example when url ends with a `/`)\n\t\t// that could break the connection. See https://github.com/mqttjs/MQTT.js/pull/1874\n\t\tdelete opts.path\n\t}\n\n\t// merge in the auth options if supplied\n\tparseAuthOptions(opts)\n\n\t// support clientId passed in the query string of the url\n\tif (opts.query && typeof opts.query.clientId === 'string') {\n\t\topts.clientId = opts.query.clientId\n\t}\n\n\tif (isBrowser || opts.unixSocket) {\n\t\topts.socksProxy = undefined\n\t} else if (\n\t\topts.socksProxy === undefined &&\n\t\ttypeof process !== 'undefined'\n\t) {\n\t\topts.socksProxy = process.env['MQTTJS_SOCKS_PROXY']\n\t}\n\n\tif (opts.cert && opts.key) {\n\t\tif (opts.protocol) {\n\t\t\tif (['mqtts', 'wss', 'wxs', 'alis'].indexOf(opts.protocol) === -1) {\n\t\t\t\tswitch (opts.protocol) {\n\t\t\t\t\tcase 'mqtt':\n\t\t\t\t\t\topts.protocol = 'mqtts'\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'ws':\n\t\t\t\t\t\topts.protocol = 'wss'\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'wx':\n\t\t\t\t\t\topts.protocol = 'wxs'\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'ali':\n\t\t\t\t\t\topts.protocol = 'alis'\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`Unknown protocol for secure connection: \"${opts.protocol}\"!`,\n\t\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// A cert and key was provided, however no protocol was specified, so we will throw an error.\n\t\t\tthrow new Error('Missing secure protocol key')\n\t\t}\n\t}\n\n\t// only loads the protocols once\n\tif (!protocols) {\n\t\tprotocols = {}\n\t\tif (!isBrowser && !opts.forceNativeWebSocket) {\n\t\t\tprotocols.ws = require('./ws').streamBuilder\n\t\t\tprotocols.wss = require('./ws').streamBuilder\n\n\t\t\tprotocols.mqtt = require('./tcp').default\n\t\t\tprotocols.tcp = require('./tcp').default\n\t\t\tprotocols.ssl = require('./tls').default\n\t\t\tprotocols.tls = protocols.ssl\n\t\t\tprotocols.mqtts = require('./tls').default\n\t\t} else {\n\t\t\tprotocols.ws = require('./ws').browserStreamBuilder\n\t\t\tprotocols.wss = require('./ws').browserStreamBuilder\n\n\t\t\tprotocols.wx = require('./wx').default\n\t\t\tprotocols.wxs = require('./wx').default\n\n\t\t\tprotocols.ali = require('./ali').default\n\t\t\tprotocols.alis = require('./ali').default\n\t\t}\n\t}\n\n\tif (!protocols[opts.protocol]) {\n\t\tconst isSecure = ['mqtts', 'wss'].indexOf(opts.protocol) !== -1\n\t\t// returns the first available protocol based on available protocols (that depends on environment)\n\t\t// if no protocol is specified this will return mqtt on node and ws on browser\n\t\t// if secure it will return mqtts on node and wss on browser\n\t\topts.protocol = [\n\t\t\t'mqtt',\n\t\t\t'mqtts',\n\t\t\t'ws',\n\t\t\t'wss',\n\t\t\t'wx',\n\t\t\t'wxs',\n\t\t\t'ali',\n\t\t\t'alis',\n\t\t].filter((key, index) => {\n\t\t\tif (isSecure && index % 2 === 0) {\n\t\t\t\t// Skip insecure protocols when requesting a secure one.\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn typeof protocols[key] === 'function'\n\t\t})[0] as MqttProtocol\n\t}\n\n\tif (opts.clean === false && !opts.clientId) {\n\t\tthrow new Error('Missing clientId for unclean clients')\n\t}\n\n\tif (opts.protocol) {\n\t\topts.defaultProtocol = opts.protocol\n\t}\n\n\tfunction wrapper(client: MqttClient) {\n\t\tif (opts.servers) {\n\t\t\tif (\n\t\t\t\t!client._reconnectCount ||\n\t\t\t\tclient._reconnectCount === opts.servers.length\n\t\t\t) {\n\t\t\t\tclient._reconnectCount = 0\n\t\t\t}\n\n\t\t\topts.host = opts.servers[client._reconnectCount].host\n\t\t\topts.port = opts.servers[client._reconnectCount].port\n\t\t\topts.protocol = !opts.servers[client._reconnectCount].protocol\n\t\t\t\t? opts.defaultProtocol\n\t\t\t\t: opts.servers[client._reconnectCount].protocol\n\t\t\topts.hostname = opts.host\n\n\t\t\tclient._reconnectCount++\n\t\t}\n\n\t\tdebug('calling streambuilder for', opts.protocol)\n\t\treturn protocols[opts.protocol](client, opts)\n\t}\n\tconst client = new MqttClient(wrapper, opts)\n\tclient.on('error', () => {\n\t\t/* Automatically set up client error handling */\n\t})\n\n\treturn client\n}\n\nfunction connectAsync(brokerUrl: string): Promise<MqttClient>\nfunction connectAsync(opts: IClientOptions): Promise<MqttClient>\nfunction connectAsync(\n\tbrokerUrl: string,\n\topts?: IClientOptions,\n): Promise<MqttClient>\nfunction connectAsync(\n\tbrokerUrl: string,\n\topts: IClientOptions,\n\tallowRetries: boolean,\n): Promise<MqttClient>\nfunction connectAsync(\n\tbrokerUrl: string | IClientOptions,\n\topts?: IClientOptions,\n\tallowRetries = true,\n): Promise<MqttClient> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst client = connect(brokerUrl as string, opts)\n\n\t\tconst promiseResolutionListeners: Partial<MqttClientEventCallbacks> = {\n\t\t\tconnect: (connack) => {\n\t\t\t\tremovePromiseResolutionListeners()\n\t\t\t\tresolve(client) // Resolve on connect\n\t\t\t},\n\t\t\tend: () => {\n\t\t\t\tremovePromiseResolutionListeners()\n\t\t\t\tresolve(client) // Resolve on end\n\t\t\t},\n\t\t\terror: (err) => {\n\t\t\t\tremovePromiseResolutionListeners()\n\t\t\t\tclient.end()\n\t\t\t\treject(err) // Reject on error\n\t\t\t},\n\t\t}\n\n\t\t// If retries are not allowed, reject on close\n\t\tif (allowRetries === false) {\n\t\t\tpromiseResolutionListeners.close = () => {\n\t\t\t\tpromiseResolutionListeners.error(\n\t\t\t\t\tnew Error(\"Couldn't connect to server\"),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\t// Remove listeners added to client by this promise\n\t\tfunction removePromiseResolutionListeners() {\n\t\t\tObject.keys(promiseResolutionListeners).forEach((eventName) => {\n\t\t\t\tclient.off(\n\t\t\t\t\teventName as keyof MqttClientEventCallbacks,\n\t\t\t\t\tpromiseResolutionListeners[eventName],\n\t\t\t\t)\n\t\t\t})\n\t\t}\n\n\t\t// Add listeners to client\n\t\tObject.keys(promiseResolutionListeners).forEach((eventName) => {\n\t\t\tclient.on(\n\t\t\t\teventName as keyof MqttClientEventCallbacks,\n\t\t\t\tpromiseResolutionListeners[eventName],\n\t\t\t)\n\t\t})\n\t})\n}\n\nexport default connect\nexport { connectAsync }\n"
  },
  {
    "path": "src/lib/connect/socks.ts",
    "content": "import _debug from 'debug'\nimport { Duplex } from 'stream'\nimport { SocksClient, type SocksProxy } from 'socks'\nimport * as dns from 'dns'\nimport { type SocksProxyType } from 'socks/typings/common/constants'\nimport { promisify } from 'util'\nimport { type Socket } from 'net'\nimport assert from 'assert'\nimport { type IStream } from '../shared'\n\nconst debug = _debug('mqttjs:socks')\n\nexport interface SocksConnectionOptions {\n\ttimeout?: number\n\tlookup?: (hostname: string) => Promise<{ address: string }>\n}\n\nclass ProxyStream extends Duplex {\n\tprivate _flowing = false\n\n\tprivate _socket?: Socket\n\n\tconstructor() {\n\t\tsuper({ autoDestroy: false })\n\n\t\tthis.cork()\n\t}\n\n\t_start(socket: Socket): void {\n\t\tdebug('proxy stream started')\n\n\t\tassert(!this._socket)\n\n\t\tif (this.destroyed) {\n\t\t\tsocket.destroy(this.errored)\n\t\t\treturn\n\t\t}\n\n\t\tthis._socket = socket\n\n\t\tif (!this._flowing) socket.pause()\n\n\t\tsocket.on('data', this._onData)\n\t\tsocket.on('end', this._onEnd)\n\t\tsocket.on('error', this._onError)\n\t\tsocket.on('close', this._onClose)\n\n\t\tsocket.emit('connect')\n\n\t\tthis.uncork()\n\t}\n\n\t_write(\n\t\tchunk: any,\n\t\tencoding: BufferEncoding,\n\t\tcallback: (error?: Error | null) => void,\n\t): void {\n\t\tassert(this._socket)\n\n\t\tthis._socket.write(chunk, callback)\n\t}\n\n\t_read(size: number): void {\n\t\tthis._flowing = true\n\n\t\tthis._socket?.resume?.()\n\t}\n\n\t_destroy(\n\t\terror: Error | null,\n\t\tcallback: (error?: Error | null) => void,\n\t): void {\n\t\tthis._socket?.destroy?.(error)\n\n\t\tcallback(error)\n\t}\n\n\tprivate _onData = (chunk: any): void => {\n\t\tassert(this._socket)\n\n\t\tthis._flowing = this.push(chunk)\n\t\tif (!this._flowing) this._socket.pause()\n\t}\n\n\tprivate _onEnd = (): void => {\n\t\tdebug('proxy stream received EOF')\n\n\t\tthis.push(null)\n\t}\n\n\tprivate _onClose = (): void => {\n\t\tdebug('proxy stream closed')\n\n\t\tthis.destroy()\n\t}\n\n\tprivate _onError = (err: any): void => {\n\t\tdebug('proxy stream died with error %s', err)\n\n\t\tthis.destroy(err)\n\t}\n}\n\nfunction fatal<T>(e: T): T {\n\ttry {\n\t\tif ((e as any).code === undefined) (e as any).code = 'SOCKS'\n\t\treturn e\n\t} catch {\n\t\treturn e\n\t}\n}\n\nfunction typeFromProtocol(\n\tproto: string,\n): [SocksProxyType | undefined, boolean] {\n\tswitch (proto) {\n\t\tcase 'socks5h:':\n\t\t\treturn [5, true]\n\n\t\tcase 'socks4a:':\n\t\t\treturn [4, true]\n\n\t\tcase 'socks5:':\n\t\t\treturn [5, false]\n\n\t\tcase 'socks4:':\n\t\t\treturn [4, false]\n\n\t\tdefault:\n\t\t\treturn [undefined, false]\n\t}\n}\n\nfunction parseSocksUrl(url: string): [SocksProxy, boolean] {\n\tconst parsedUrl = new URL(url)\n\n\tif (parsedUrl.pathname || parsedUrl.hash || parsedUrl.search) {\n\t\tthrow fatal(new Error('bad SOCKS URL'))\n\t}\n\n\tconst [type, resolveThroughProxy] = typeFromProtocol(parsedUrl.protocol)\n\tif (!type) {\n\t\tthrow fatal(new Error('bad SOCKS URL: invalid protocol'))\n\t}\n\n\tconst port = parseInt(parsedUrl.port, 10)\n\tif (Number.isNaN(port)) {\n\t\tthrow fatal(new Error('bad SOCKS URL: invalid port'))\n\t}\n\n\tconst proxy: SocksProxy = {\n\t\thost: parsedUrl.hostname,\n\t\tport,\n\t\ttype,\n\t}\n\n\treturn [proxy, resolveThroughProxy]\n}\n\nasync function connectSocks(\n\tdestinationHost: string,\n\tdestinationPort: number,\n\tsocksUrl: string,\n\tstream: ProxyStream,\n\toptions: SocksConnectionOptions = {},\n): Promise<void> {\n\tconst lookup = options.lookup ?? promisify(dns.lookup)\n\n\tconst [proxy, resolveThroughProxy] = parseSocksUrl(socksUrl)\n\n\tif (!resolveThroughProxy) {\n\t\tdebug('resolving %s locally', destinationHost)\n\n\t\tdestinationHost = (\n\t\t\tawait lookup(destinationHost, {\n\t\t\t\tfamily: proxy.type === 4 ? 4 : 0,\n\t\t\t})\n\t\t).address\n\t}\n\n\tdebug(\n\t\t'establishing SOCKS%d connection to %s:%d via %s:%d',\n\t\tproxy.type,\n\t\tdestinationHost,\n\t\tdestinationPort,\n\t\tproxy.host,\n\t\tproxy.port,\n\t)\n\n\tconst socksClient = new SocksClient({\n\t\tcommand: 'connect',\n\t\tdestination: {\n\t\t\thost: destinationHost,\n\t\t\tport: destinationPort,\n\t\t},\n\t\tproxy: { ...proxy },\n\t\ttimeout: options.timeout,\n\t})\n\tsocksClient.connect()\n\n\tsocksClient.on('established', ({ socket }) => stream._start(socket))\n\n\tsocksClient.on('error', (e) => {\n\t\tdebug('SOCKS failed: %s', e)\n\t\tstream.destroy(fatal(e))\n\t})\n}\n\nexport default function openSocks(\n\tdestinationHost: string,\n\tdestinationPort: number,\n\tsocksUrl: string,\n\toptions?: SocksConnectionOptions,\n): IStream {\n\tdebug(\n\t\t'SOCKS connection to %s:%d via %s',\n\t\tdestinationHost,\n\t\tdestinationPort,\n\t\tsocksUrl,\n\t)\n\n\tconst stream = new ProxyStream()\n\n\tconnectSocks(\n\t\tdestinationHost,\n\t\tdestinationPort,\n\t\tsocksUrl,\n\t\tstream,\n\t\toptions,\n\t).catch((e) => {\n\t\tdebug('SOCKS failed: %s', e)\n\t\tstream.destroy(e)\n\t})\n\n\treturn stream\n}\n"
  },
  {
    "path": "src/lib/connect/tcp.ts",
    "content": "import net from 'net'\nimport _debug from 'debug'\nimport { type StreamBuilder } from '../shared'\nimport openSocks from './socks'\n\nconst debug = _debug('mqttjs:tcp')\n/*\n  variables port and host can be removed since\n  you have all required information in opts object\n*/\nconst buildStream: StreamBuilder = (client, opts) => {\n\topts.port = opts.port || 1883\n\topts.hostname = opts.hostname || opts.host || 'localhost'\n\n\tif (opts.socksProxy) {\n\t\treturn openSocks(opts.hostname, opts.port, opts.socksProxy, {\n\t\t\ttimeout: opts.socksTimeout,\n\t\t})\n\t}\n\n\tconst { port, path } = opts\n\tconst host = opts.hostname\n\n\tdebug('port %d and host %s', port, host)\n\treturn net.createConnection({ port, host, path })\n}\n\nexport default buildStream\n"
  },
  {
    "path": "src/lib/connect/tls.ts",
    "content": "import { type TLSSocket, connect as tlsConnect } from 'tls'\nimport net from 'net'\nimport _debug from 'debug'\nimport { type StreamBuilder } from '../shared'\nimport { type IClientOptions } from '../client'\nimport openSocks from './socks'\n\nconst debug = _debug('mqttjs:tls')\n\nfunction connect(opts: IClientOptions): TLSSocket {\n\tconst { host, port, socksProxy, ...rest } = opts\n\n\tif (socksProxy !== undefined) {\n\t\tconst socket = openSocks(host, port, socksProxy, {\n\t\t\ttimeout: opts.socksTimeout,\n\t\t})\n\n\t\treturn tlsConnect({\n\t\t\t...rest,\n\t\t\tsocket,\n\t\t})\n\t}\n\n\treturn tlsConnect(opts)\n}\n\nconst buildStream: StreamBuilder = (client, opts) => {\n\topts.port = opts.port || 8883\n\topts.host = opts.hostname || opts.host || 'localhost'\n\n\tif (net.isIP(opts.host) === 0) {\n\t\topts.servername = opts.host\n\t}\n\n\topts.rejectUnauthorized = opts.rejectUnauthorized !== false\n\n\tdelete opts.path\n\n\tdebug(\n\t\t'port %d host %s rejectUnauthorized %b',\n\t\topts.port,\n\t\topts.host,\n\t\topts.rejectUnauthorized,\n\t)\n\n\tconst connection = connect(opts)\n\tconnection.on('secureConnect', () => {\n\t\tif (opts.rejectUnauthorized && !connection.authorized) {\n\t\t\tconnection.emit('error', new Error('TLS not authorized'))\n\t\t} else {\n\t\t\tconnection.removeListener('error', handleTLSerrors)\n\t\t}\n\t})\n\n\tfunction handleTLSerrors(err: Error) {\n\t\t// How can I get verify this error is a tls error?\n\t\tif (opts.rejectUnauthorized) {\n\t\t\tclient.emit('error', err)\n\t\t}\n\n\t\t// close this connection to match the behaviour of net\n\t\t// otherwise all we get is an error from the connection\n\t\t// and close event doesn't fire. This is a work around\n\t\t// to enable the reconnect code to work the same as with\n\t\t// net.createConnection\n\t\tconnection.end()\n\t}\n\n\tconnection.on('error', handleTLSerrors)\n\treturn connection\n}\n\nexport default buildStream\n"
  },
  {
    "path": "src/lib/connect/ws.ts",
    "content": "import { Buffer } from 'buffer'\nimport Ws, { type ClientOptions } from 'ws'\nimport _debug from 'debug'\nimport { Transform } from 'readable-stream'\nimport { type IStream, type StreamBuilder } from '../shared'\nimport isBrowser from '../is-browser'\nimport { type IClientOptions } from '../client'\nimport type MqttClient from '../client'\nimport { BufferedDuplex, writev } from '../BufferedDuplex'\n\nconst debug = _debug('mqttjs:ws')\n\nconst WSS_OPTIONS = [\n\t'rejectUnauthorized',\n\t'ca',\n\t'cert',\n\t'key',\n\t'pfx',\n\t'passphrase',\n]\n\nfunction buildUrl(opts: IClientOptions, client: MqttClient) {\n\tlet url = `${opts.protocol}://${opts.hostname}:${opts.port}${opts.path}`\n\tif (typeof opts.transformWsUrl === 'function') {\n\t\turl = opts.transformWsUrl(url, opts, client)\n\t}\n\treturn url\n}\n\nfunction setDefaultOpts(opts: IClientOptions) {\n\tconst options = opts\n\n\tif (!opts.port) {\n\t\tif (opts.protocol === 'wss') {\n\t\t\toptions.port = 443\n\t\t} else {\n\t\t\toptions.port = 80\n\t\t}\n\t}\n\n\tif (!opts.path) {\n\t\toptions.path = '/'\n\t}\n\n\tif (!opts.wsOptions) {\n\t\toptions.wsOptions = {}\n\t}\n\tif (!isBrowser && !opts.forceNativeWebSocket && opts.protocol === 'wss') {\n\t\t// Add cert/key/ca etc options\n\t\tWSS_OPTIONS.forEach((prop) => {\n\t\t\tif (\n\t\t\t\tObject.prototype.hasOwnProperty.call(opts, prop) &&\n\t\t\t\t!Object.prototype.hasOwnProperty.call(opts.wsOptions, prop)\n\t\t\t) {\n\t\t\t\toptions.wsOptions[prop] = opts[prop]\n\t\t\t}\n\t\t})\n\t}\n\n\treturn options\n}\n\nfunction setDefaultBrowserOpts(opts: IClientOptions) {\n\tconst options = setDefaultOpts(opts)\n\n\tif (!options.hostname) {\n\t\toptions.hostname = options.host\n\t}\n\n\tif (!options.hostname) {\n\t\t// Throwing an error in a Web Worker if no `hostname` is given, because we\n\t\t// can not determine the `hostname` automatically.  If connecting to\n\t\t// localhost, please supply the `hostname` as an argument.\n\t\tif (typeof document === 'undefined') {\n\t\t\tthrow new Error('Could not determine host. Specify host manually.')\n\t\t}\n\t\tconst parsed = new URL(document.URL)\n\t\toptions.hostname = parsed.hostname\n\n\t\tif (!options.port) {\n\t\t\toptions.port = Number(parsed.port)\n\t\t}\n\t}\n\n\t// objectMode should be defined for logic\n\tif (options.objectMode === undefined) {\n\t\toptions.objectMode = !(\n\t\t\toptions.binary === true || options.binary === undefined\n\t\t)\n\t}\n\n\treturn options\n}\n\nfunction createWebSocket(\n\tclient: MqttClient,\n\turl: string,\n\topts: IClientOptions,\n) {\n\tdebug('createWebSocket')\n\tdebug(`protocol: ${opts.protocolId} ${opts.protocolVersion}`)\n\tconst websocketSubProtocol =\n\t\topts.protocolId === 'MQIsdp' && opts.protocolVersion === 3\n\t\t\t? 'mqttv3.1'\n\t\t\t: 'mqtt'\n\n\tdebug(\n\t\t`creating new Websocket for url: ${url} and protocol: ${websocketSubProtocol}`,\n\t)\n\tlet socket: Ws\n\tif (opts.createWebsocket) {\n\t\tsocket = opts.createWebsocket(url, [websocketSubProtocol], opts)\n\t} else {\n\t\tsocket = new Ws(\n\t\t\turl,\n\t\t\t[websocketSubProtocol],\n\t\t\topts.wsOptions as ClientOptions,\n\t\t)\n\t}\n\treturn socket\n}\n\n/* istanbul ignore next */\nfunction createBrowserWebSocket(client: MqttClient, opts: IClientOptions) {\n\tconst websocketSubProtocol =\n\t\topts.protocolId === 'MQIsdp' && opts.protocolVersion === 3\n\t\t\t? 'mqttv3.1'\n\t\t\t: 'mqtt'\n\n\tconst url = buildUrl(opts, client)\n\tlet socket: WebSocket\n\tif (opts.createWebsocket) {\n\t\tsocket = opts.createWebsocket(url, [websocketSubProtocol], opts)\n\t} else {\n\t\tsocket = new WebSocket(url, [websocketSubProtocol])\n\t}\n\tsocket.binaryType = 'arraybuffer'\n\treturn socket\n}\n\nconst streamBuilder: StreamBuilder = (client, opts): IStream => {\n\tdebug('streamBuilder')\n\tconst options = setDefaultOpts(opts)\n\n\toptions.hostname = options.hostname || options.host || 'localhost'\n\n\tconst url = buildUrl(options, client)\n\tconst socket = createWebSocket(client, url, options)\n\t// @ts-expect-error - This is a type confusion because of the overlap between browser oriented code and Node.js oriented code.\n\tconst webSocketStream = Ws.createWebSocketStream(socket, options.wsOptions)\n\n\twebSocketStream['url'] = url\n\tsocket.on('close', () => {\n\t\twebSocketStream.destroy()\n\t})\n\treturn webSocketStream\n}\n\n/* istanbul ignore next */\nconst browserStreamBuilder: StreamBuilder = (client, opts) => {\n\tdebug('browserStreamBuilder')\n\tlet stream: BufferedDuplex | (Transform & { socket?: WebSocket })\n\tconst options = setDefaultBrowserOpts(opts)\n\t// sets the maximum socket buffer size before throttling\n\tconst bufferSize = options.browserBufferSize || 1024 * 512\n\n\tconst bufferTimeout = opts.browserBufferTimeout || 1000\n\n\tconst coerceToBuffer = !opts.objectMode\n\n\t// the websocket connection\n\tconst socket = createBrowserWebSocket(client, opts)\n\n\t// the proxy is a transform stream that forwards data to the socket\n\t// it ensures data written to socket is a Buffer\n\tconst proxy = buildProxy(opts, socketWriteBrowser, socketEndBrowser)\n\n\tif (!opts.objectMode) {\n\t\tproxy._writev = writev.bind(proxy)\n\t}\n\tproxy.on('close', () => {\n\t\tsocket.close()\n\t})\n\n\tconst eventListenerSupport = typeof socket.addEventListener !== 'undefined'\n\n\t// was already open when passed in\n\tif (socket.readyState === socket.OPEN) {\n\t\tstream = proxy\n\t\tstream.socket = socket\n\t} else {\n\t\t// socket is not open. Use this to buffer writes until it is opened\n\t\tstream = new BufferedDuplex(opts, proxy, socket)\n\n\t\tif (eventListenerSupport) {\n\t\t\tsocket.addEventListener('open', onOpen)\n\t\t} else {\n\t\t\tsocket.onopen = onOpen\n\t\t}\n\t}\n\n\tif (eventListenerSupport) {\n\t\tsocket.addEventListener('close', onClose)\n\t\tsocket.addEventListener('error', onError)\n\t\tsocket.addEventListener('message', onMessage)\n\t} else {\n\t\tsocket.onclose = onClose\n\t\tsocket.onerror = onError\n\t\tsocket.onmessage = onMessage\n\t}\n\n\t// methods for browserStreamBuilder\n\n\tfunction buildProxy(\n\t\tpOptions: IClientOptions,\n\t\tsocketWrite: typeof socketWriteBrowser,\n\t\tsocketEnd: typeof socketEndBrowser,\n\t) {\n\t\tconst _proxy = new Transform({\n\t\t\tobjectMode: pOptions.objectMode,\n\t\t})\n\n\t\t_proxy._write = socketWrite\n\t\t_proxy._flush = socketEnd\n\n\t\treturn _proxy\n\t}\n\n\tfunction onOpen() {\n\t\tdebug('WebSocket onOpen')\n\t\tif (stream instanceof BufferedDuplex) {\n\t\t\tstream.socketReady()\n\t\t}\n\t}\n\n\t/**\n\t * https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close_event\n\t */\n\tfunction onClose(event: CloseEvent) {\n\t\tdebug('WebSocket onClose', event)\n\t\tstream.end()\n\t\tstream.destroy()\n\t}\n\n\t/**\n\t * https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/error_event\n\t */\n\tfunction onError(err: Event) {\n\t\tdebug('WebSocket onError', err)\n\t\tconst error = new Error('WebSocket error')\n\t\terror['event'] = err\n\t\tstream.destroy(error)\n\t}\n\n\t/**\n\t * https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/message_event\n\t */\n\tasync function onMessage(event: MessageEvent) {\n\t\tif (!proxy || !proxy.readable || !proxy.writable) {\n\t\t\treturn\n\t\t}\n\t\tlet { data } = event\n\t\tif (data instanceof ArrayBuffer) data = Buffer.from(data)\n\t\telse if (data instanceof Blob)\n\t\t\tdata = Buffer.from(await new Response(data).arrayBuffer())\n\t\telse data = Buffer.from(data as string, 'utf8')\n\t\tproxy.push(data)\n\t}\n\n\tfunction socketWriteBrowser(\n\t\tchunk: any,\n\t\tenc: string,\n\t\tnext: (err?: Error) => void,\n\t) {\n\t\tif (socket.bufferedAmount > bufferSize) {\n\t\t\t// throttle data until buffered amount is reduced.\n\t\t\tsetTimeout(socketWriteBrowser, bufferTimeout, chunk, enc, next)\n\t\t\treturn\n\t\t}\n\n\t\tif (coerceToBuffer && typeof chunk === 'string') {\n\t\t\tchunk = Buffer.from(chunk, 'utf8')\n\t\t}\n\n\t\ttry {\n\t\t\t// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send (note this doesn't have a cb as second arg)\n\t\t\tsocket.send(chunk)\n\t\t} catch (err) {\n\t\t\treturn next(err)\n\t\t}\n\n\t\tnext()\n\t}\n\n\tfunction socketEndBrowser(done: (error?: Error, data?: any) => void) {\n\t\tsocket.close()\n\t\tdone()\n\t}\n\n\t// end methods for browserStreamBuilder\n\n\treturn stream\n}\n\nexport { browserStreamBuilder, streamBuilder }\n"
  },
  {
    "path": "src/lib/connect/wx.ts",
    "content": "import { Buffer } from 'buffer'\nimport { Transform } from 'readable-stream'\nimport { type StreamBuilder } from '../shared'\nimport { type IClientOptions } from '../client'\nimport type MqttClient from '../client'\nimport { BufferedDuplex } from '../BufferedDuplex'\n\n/* global wx */\nlet socketTask: any\nlet proxy: Transform\nlet stream: BufferedDuplex\n\ndeclare global {\n\tconst wx: any\n}\n\nfunction buildProxy() {\n\tconst _proxy = new Transform()\n\t_proxy._write = (chunk, encoding, next) => {\n\t\tsocketTask.send({\n\t\t\tdata: chunk.buffer,\n\t\t\tsuccess() {\n\t\t\t\tnext()\n\t\t\t},\n\t\t\tfail(errMsg) {\n\t\t\t\tnext(new Error(errMsg))\n\t\t\t},\n\t\t})\n\t}\n\t_proxy._flush = (done) => {\n\t\tsocketTask.close({\n\t\t\tsuccess() {\n\t\t\t\tdone()\n\t\t\t},\n\t\t})\n\t}\n\n\treturn _proxy\n}\n\nfunction setDefaultOpts(opts) {\n\tif (!opts.hostname) {\n\t\topts.hostname = 'localhost'\n\t}\n\tif (!opts.path) {\n\t\topts.path = '/'\n\t}\n\n\tif (!opts.wsOptions) {\n\t\topts.wsOptions = {}\n\t}\n}\n\nfunction buildUrl(opts: IClientOptions, client: MqttClient) {\n\tconst protocol = opts.protocol === 'wxs' ? 'wss' : 'ws'\n\tlet url = `${protocol}://${opts.hostname}${opts.path}`\n\tif (opts.port && opts.port !== 80 && opts.port !== 443) {\n\t\turl = `${protocol}://${opts.hostname}:${opts.port}${opts.path}`\n\t}\n\tif (typeof opts.transformWsUrl === 'function') {\n\t\turl = opts.transformWsUrl(url, opts, client)\n\t}\n\treturn url\n}\n\nfunction bindEventHandler() {\n\tsocketTask.onOpen(() => {\n\t\tstream.socketReady()\n\t})\n\n\tsocketTask.onMessage((res) => {\n\t\tlet { data } = res\n\n\t\tif (data instanceof ArrayBuffer) data = Buffer.from(data)\n\t\telse data = Buffer.from(data, 'utf8')\n\t\tproxy.push(data)\n\t})\n\n\tsocketTask.onClose(() => {\n\t\tstream.emit('close')\n\t\tstream.end()\n\t\tstream.destroy()\n\t})\n\n\tsocketTask.onError((error) => {\n\t\tconst err = new Error(error.errMsg)\n\t\tstream.destroy(err)\n\t})\n}\n\nconst buildStream: StreamBuilder = (client, opts) => {\n\topts.hostname = opts.hostname || opts.host\n\n\tif (!opts.hostname) {\n\t\tthrow new Error('Could not determine host. Specify host manually.')\n\t}\n\n\tconst websocketSubProtocol =\n\t\topts.protocolId === 'MQIsdp' && opts.protocolVersion === 3\n\t\t\t? 'mqttv3.1'\n\t\t\t: 'mqtt'\n\n\tsetDefaultOpts(opts)\n\n\tconst url = buildUrl(opts, client)\n\t// https://github.com/wechat-miniprogram/api-typings/blob/master/types/wx/lib.wx.api.d.ts#L20984\n\tsocketTask = wx.connectSocket({\n\t\turl,\n\t\tprotocols: [websocketSubProtocol],\n\t})\n\n\tproxy = buildProxy()\n\tstream = new BufferedDuplex(opts, proxy, socketTask)\n\tstream._destroy = (err, cb) => {\n\t\tsocketTask.close({\n\t\t\tsuccess() {\n\t\t\t\tif (cb) cb(err)\n\t\t\t},\n\t\t})\n\t}\n\n\tconst destroyRef = stream.destroy\n\tstream.destroy = (err, cb) => {\n\t\tstream.destroy = destroyRef\n\n\t\tsetTimeout(() => {\n\t\t\tsocketTask.close({\n\t\t\t\tfail() {\n\t\t\t\t\tstream._destroy(err, cb)\n\t\t\t\t},\n\t\t\t})\n\t\t}, 0)\n\n\t\treturn stream\n\t}\n\n\tbindEventHandler()\n\n\treturn stream\n}\n\nexport default buildStream\n"
  },
  {
    "path": "src/lib/default-message-id-provider.ts",
    "content": "export interface IMessageIdProvider {\n\t/**\n\t * Allocate the first vacant messageId. The messageId become occupied status.\n\t * @return {Number} - The first vacant messageId. If all messageIds are occupied, return null.\n\t */\n\tallocate(): number | null\n\n\t/**\n\t * Get the last allocated messageId.\n\t * @return {Number} - messageId.\n\t */\n\tgetLastAllocated(): number | null\n\n\t/**\n\t * Register the messageId. The messageId become occupied status.\n\t * If the messageId has already been occupied, then return false.\n\t * @param {number} num - The messageId to request use.\n\t * @return {boolean} - If `num` was not occupied, then return true, otherwise return false.\n\t */\n\tregister(num: number): boolean\n\n\t/**\n\t * Deallocate the messageId. The messageId become vacant status.\n\t * @param {Number} num - The messageId to deallocate. The messageId must be occupied status.\n\t *                       In other words, the messageId must be allocated by allocate() or\n\t *                       occupied by register().\n\t */\n\tdeallocate(num: number): void\n\n\t/**\n\t * Clear all occupied messageIds.\n\t * The all messageIds are set to vacant status.\n\t */\n\tclear(): void\n}\n\n/**\n * DefaultMessageAllocator constructor\n * @constructor\n */\nexport default class DefaultMessageIdProvider implements IMessageIdProvider {\n\tprivate nextId: number\n\n\tconstructor() {\n\t\t/**\n\t\t * MessageIDs starting with 1\n\t\t * ensure that nextId is min. 1, see https://github.com/mqttjs/MQTT.js/issues/810\n\t\t */\n\t\tthis.nextId = Math.max(1, Math.floor(Math.random() * 65535))\n\t}\n\n\t/**\n\t * allocate\n\t *\n\t * Get the next messageId.\n\t * @return unsigned int\n\t */\n\tallocate() {\n\t\t// id becomes current state of this.nextId and increments afterwards\n\t\tconst id = this.nextId++\n\t\t// Ensure 16 bit unsigned int (max 65535, nextId got one higher)\n\t\tif (this.nextId === 65536) {\n\t\t\tthis.nextId = 1\n\t\t}\n\t\treturn id\n\t}\n\n\t/**\n\t * getLastAllocated\n\t * Get the last allocated messageId.\n\t * @return unsigned int\n\t */\n\tgetLastAllocated() {\n\t\treturn this.nextId === 1 ? 65535 : this.nextId - 1\n\t}\n\n\t/**\n\t * register\n\t * Register messageId. If success return true, otherwise return false.\n\t * @param { unsigned int } - messageId to register,\n\t * @return boolean\n\t */\n\tregister(messageId: number) {\n\t\treturn true\n\t}\n\n\t/**\n\t * deallocate\n\t * Deallocate messageId.\n\t * @param { unsigned int } - messageId to deallocate,\n\t */\n\tdeallocate(messageId: number) {}\n\n\t/**\n\t * clear\n\t * Deallocate all messageIds.\n\t */\n\tclear() {}\n}\n"
  },
  {
    "path": "src/lib/get-timer.ts",
    "content": "import { clearInterval as clearI, setInterval as setI } from 'worker-timers'\nimport isBrowser, { isWebWorker, isReactNativeBrowser } from './is-browser'\nimport type { TimerVariant } from './shared'\n\n// dont directly assign globals to class props otherwise this throws in web workers: Uncaught TypeError: Illegal invocation\n// See: https://stackoverflow.com/questions/9677985/uncaught-typeerror-illegal-invocation-in-chrome\n\nexport interface Timer {\n\tset: typeof setI\n\tclear: typeof clearI\n}\n\nconst workerTimer: Timer = {\n\tset: setI,\n\tclear: clearI,\n}\n\nconst nativeTimer: Timer = {\n\tset: (func, time) => setInterval(func, time),\n\tclear: (timerId) => clearInterval(timerId),\n}\n\nconst getTimer = (variant: TimerVariant): Timer => {\n\tswitch (variant) {\n\t\tcase 'native': {\n\t\t\treturn nativeTimer\n\t\t}\n\t\tcase 'worker': {\n\t\t\treturn workerTimer\n\t\t}\n\t\tcase 'auto':\n\t\tdefault: {\n\t\t\treturn isBrowser && !isWebWorker && !isReactNativeBrowser\n\t\t\t\t? workerTimer\n\t\t\t\t: nativeTimer\n\t\t}\n\t}\n}\n\nexport default getTimer\n"
  },
  {
    "path": "src/lib/handlers/ack.ts",
    "content": "// Other Socket Errors: EADDRINUSE, ECONNRESET, ENOTFOUND, ETIMEDOUT.\n\nimport { type PacketHandler, ErrorWithReasonCode } from '../shared'\n\nexport const ReasonCodes = {\n\t0: '',\n\t1: 'Unacceptable protocol version',\n\t2: 'Identifier rejected',\n\t3: 'Server unavailable',\n\t4: 'Bad username or password',\n\t5: 'Not authorized',\n\t16: 'No matching subscribers',\n\t17: 'No subscription existed',\n\t128: 'Unspecified error',\n\t129: 'Malformed Packet',\n\t130: 'Protocol Error',\n\t131: 'Implementation specific error',\n\t132: 'Unsupported Protocol Version',\n\t133: 'Client Identifier not valid',\n\t134: 'Bad User Name or Password',\n\t135: 'Not authorized',\n\t136: 'Server unavailable',\n\t137: 'Server busy',\n\t138: 'Banned',\n\t139: 'Server shutting down',\n\t140: 'Bad authentication method',\n\t141: 'Keep Alive timeout',\n\t142: 'Session taken over',\n\t143: 'Topic Filter invalid',\n\t144: 'Topic Name invalid',\n\t145: 'Packet identifier in use',\n\t146: 'Packet Identifier not found',\n\t147: 'Receive Maximum exceeded',\n\t148: 'Topic Alias invalid',\n\t149: 'Packet too large',\n\t150: 'Message rate too high',\n\t151: 'Quota exceeded',\n\t152: 'Administrative action',\n\t153: 'Payload format invalid',\n\t154: 'Retain not supported',\n\t155: 'QoS not supported',\n\t156: 'Use another server',\n\t157: 'Server moved',\n\t158: 'Shared Subscriptions not supported',\n\t159: 'Connection rate exceeded',\n\t160: 'Maximum connect time',\n\t161: 'Subscription Identifiers not supported',\n\t162: 'Wildcard Subscriptions not supported',\n}\n\nconst handleAck: PacketHandler = (client, packet) => {\n\t/* eslint no-fallthrough: \"off\" */\n\tconst { messageId } = packet\n\tconst type = packet.cmd\n\tlet response = null\n\tconst cb = client.outgoing[messageId] ? client.outgoing[messageId].cb : null\n\tlet err = null\n\n\t// Checking `!cb` happens to work, but it's not technically \"correct\".\n\t//\n\t// Why? client code assumes client \"no callback\" is the same as client \"we're not\n\t// waiting for responses\" (puback, pubrec, pubcomp, suback, or unsuback).\n\t//\n\t// It would be better to check `if (!client.outgoing[messageId])` here, but\n\t// there's no reason to change it and risk (another) regression.\n\t//\n\t// The only reason client code works is becaues code in MqttClient.publish,\n\t// MqttClinet.subscribe, and MqttClient.unsubscribe ensures client we will\n\t// have a callback even if the user doesn't pass one in.)\n\tif (!cb) {\n\t\tclient.log('_handleAck :: Server sent an ack in error. Ignoring.')\n\t\t// Server sent an ack in error, ignore it.\n\t\treturn\n\t}\n\n\t// Process\n\tclient.log('_handleAck :: packet type', type)\n\tswitch (type) {\n\t\tcase 'pubcomp':\n\t\t// same thing as puback for QoS 2\n\t\tcase 'puback': {\n\t\t\tconst pubackRC = packet.reasonCode\n\t\t\t// Callback - we're done\n\t\t\tif (pubackRC && pubackRC > 0 && pubackRC !== 16) {\n\t\t\t\terr = new ErrorWithReasonCode(\n\t\t\t\t\t`Publish error: ${ReasonCodes[pubackRC]}`,\n\t\t\t\t\tpubackRC,\n\t\t\t\t)\n\t\t\t\tclient['_removeOutgoingAndStoreMessage'](messageId, () => {\n\t\t\t\t\tcb(err, packet)\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tclient['_removeOutgoingAndStoreMessage'](messageId, cb)\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\t\tcase 'pubrec': {\n\t\t\tresponse = {\n\t\t\t\tcmd: 'pubrel',\n\t\t\t\tqos: 2,\n\t\t\t\tmessageId,\n\t\t\t}\n\t\t\tconst pubrecRC = packet.reasonCode\n\n\t\t\tif (pubrecRC && pubrecRC > 0 && pubrecRC !== 16) {\n\t\t\t\terr = new ErrorWithReasonCode(\n\t\t\t\t\t`Publish error: ${ReasonCodes[pubrecRC]}`,\n\t\t\t\t\tpubrecRC,\n\t\t\t\t)\n\t\t\t\tclient['_removeOutgoingAndStoreMessage'](messageId, () => {\n\t\t\t\t\tcb(err, packet)\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tclient['_sendPacket'](response)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tcase 'suback': {\n\t\t\tdelete client.outgoing[messageId]\n\t\t\tclient.messageIdProvider.deallocate(messageId)\n\t\t\tconst granted = packet.granted as number[]\n\t\t\tfor (let grantedI = 0; grantedI < granted.length; grantedI++) {\n\t\t\t\tconst subackRC = granted[grantedI]\n\t\t\t\tif ((subackRC & 0x80) !== 0) {\n\t\t\t\t\terr = new Error(`Subscribe error: ${ReasonCodes[subackRC]}`)\n\t\t\t\t\terr.code = subackRC\n\n\t\t\t\t\t// suback with Failure status\n\t\t\t\t\tconst topics = client.messageIdToTopic[messageId]\n\t\t\t\t\tif (topics) {\n\t\t\t\t\t\ttopics.forEach((topic) => {\n\t\t\t\t\t\t\tdelete client['_resubscribeTopics'][topic]\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tdelete client.messageIdToTopic[messageId]\n\t\t\tclient['_invokeStoreProcessingQueue']()\n\t\t\tcb(err, packet)\n\t\t\tbreak\n\t\t}\n\t\tcase 'unsuback': {\n\t\t\tdelete client.outgoing[messageId]\n\t\t\tclient.messageIdProvider.deallocate(messageId)\n\t\t\tclient['_invokeStoreProcessingQueue']()\n\t\t\tcb(null, packet)\n\t\t\tbreak\n\t\t}\n\t\tdefault:\n\t\t\tclient.emit('error', new Error('unrecognized packet type'))\n\t}\n\n\tif (client.disconnecting && Object.keys(client.outgoing).length === 0) {\n\t\tclient.emit('outgoingEmpty')\n\t}\n}\n\nexport default handleAck\n"
  },
  {
    "path": "src/lib/handlers/auth.ts",
    "content": "import { type IAuthPacket } from 'mqtt-packet'\nimport { ErrorWithReasonCode, type PacketHandler } from '../shared'\nimport { ReasonCodes } from './ack'\n\nconst handleAuth: PacketHandler = (\n\tclient,\n\tpacket: IAuthPacket & { returnCode: number },\n) => {\n\tconst { options } = client\n\tconst version = options.protocolVersion\n\tconst rc = version === 5 ? packet.reasonCode : packet.returnCode\n\n\tif (version !== 5) {\n\t\tconst err = new ErrorWithReasonCode(\n\t\t\t`Protocol error: Auth packets are only supported in MQTT 5. Your version:${version}`,\n\t\t\trc,\n\t\t)\n\t\tclient.emit('error', err)\n\t\treturn\n\t}\n\n\tclient.handleAuth(\n\t\tpacket,\n\t\t(err: ErrorWithReasonCode, packet2: IAuthPacket) => {\n\t\t\tif (err) {\n\t\t\t\tclient.emit('error', err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (rc === 24) {\n\t\t\t\tclient.reconnecting = false\n\t\t\t\tclient['_sendPacket'](packet2)\n\t\t\t} else {\n\t\t\t\tconst error = new ErrorWithReasonCode(\n\t\t\t\t\t`Connection refused: ${ReasonCodes[rc]}`,\n\t\t\t\t\trc,\n\t\t\t\t)\n\t\t\t\tclient.emit('error', error)\n\t\t\t}\n\t\t},\n\t)\n}\n\nexport default handleAuth\n"
  },
  {
    "path": "src/lib/handlers/connack.ts",
    "content": "import { type IConnackPacket } from 'mqtt-packet'\nimport { ReasonCodes } from './ack'\nimport TopicAliasSend from '../topic-alias-send'\nimport { ErrorWithReasonCode, type PacketHandler } from '../shared'\n\nconst handleConnack: PacketHandler = (client, packet: IConnackPacket) => {\n\tclient.log('_handleConnack')\n\tconst { options } = client\n\tconst version = options.protocolVersion\n\tconst rc = version === 5 ? packet.reasonCode : packet.returnCode\n\n\tclearTimeout(client['connackTimer'])\n\tdelete client['topicAliasSend']\n\n\tif (packet.properties) {\n\t\tif (packet.properties.topicAliasMaximum) {\n\t\t\tif (packet.properties.topicAliasMaximum > 0xffff) {\n\t\t\t\tclient.emit(\n\t\t\t\t\t'error',\n\t\t\t\t\tnew Error('topicAliasMaximum from broker is out of range'),\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (packet.properties.topicAliasMaximum > 0) {\n\t\t\t\tclient['topicAliasSend'] = new TopicAliasSend(\n\t\t\t\t\tpacket.properties.topicAliasMaximum,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tif (packet.properties.serverKeepAlive && options.keepalive) {\n\t\t\toptions.keepalive = packet.properties.serverKeepAlive\n\t\t}\n\n\t\tif (packet.properties.maximumPacketSize) {\n\t\t\tif (!options.properties) {\n\t\t\t\toptions.properties = {}\n\t\t\t}\n\t\t\toptions.properties.maximumPacketSize =\n\t\t\t\tpacket.properties.maximumPacketSize\n\t\t}\n\t}\n\n\tif (rc === 0) {\n\t\tclient.reconnecting = false\n\t\tclient['_onConnect'](packet)\n\t} else if (rc > 0) {\n\t\tconst err = new ErrorWithReasonCode(\n\t\t\t`Connection refused: ${ReasonCodes[rc]}`,\n\t\t\trc,\n\t\t)\n\t\tclient.emit('error', err)\n\t\tif (client.options.reconnectOnConnackError) {\n\t\t\tclient['_cleanUp'](true)\n\t\t}\n\t}\n}\n\nexport default handleConnack\n"
  },
  {
    "path": "src/lib/handlers/index.ts",
    "content": "import handlePublish from './publish'\nimport handleAuth from './auth'\nimport handleConnack from './connack'\nimport handleAck from './ack'\nimport handlePubrel from './pubrel'\nimport { type PacketHandler } from '../shared'\n\nconst handle: PacketHandler = (client, packet, done) => {\n\tconst { options } = client\n\n\tif (\n\t\toptions.protocolVersion === 5 &&\n\t\toptions.properties &&\n\t\toptions.properties.maximumPacketSize &&\n\t\toptions.properties.maximumPacketSize < packet.length\n\t) {\n\t\tclient.emit('error', new Error(`exceeding packets size ${packet.cmd}`))\n\t\tclient.end({\n\t\t\treasonCode: 149,\n\t\t\tproperties: { reasonString: 'Maximum packet size was exceeded' },\n\t\t})\n\t\treturn client\n\t}\n\n\tclient.log('_handlePacket :: emitting packetreceive')\n\tclient.emit('packetreceive', packet)\n\n\tswitch (packet.cmd) {\n\t\tcase 'publish':\n\t\t\t// DO NOT SHIFT PING HERE, this would lead to https://github.com/mqttjs/MQTT.js/issues/1861\n\t\t\thandlePublish(client, packet, done)\n\t\t\tbreak\n\t\tcase 'puback':\n\t\tcase 'pubrec':\n\t\tcase 'pubcomp':\n\t\tcase 'suback':\n\t\tcase 'unsuback':\n\t\t\tclient.reschedulePing()\n\t\t\thandleAck(client, packet)\n\t\t\tdone()\n\t\t\tbreak\n\t\tcase 'pubrel':\n\t\t\tclient.reschedulePing()\n\t\t\thandlePubrel(client, packet, done)\n\t\t\tbreak\n\t\tcase 'connack':\n\t\t\t// no need to reschedule ping here as keepalive manager is created after successll connect\n\t\t\t// (when onConnect is called at the end of handleConnack)\n\t\t\thandleConnack(client, packet)\n\t\t\tdone()\n\t\t\tbreak\n\t\tcase 'auth':\n\t\t\tclient.reschedulePing()\n\t\t\thandleAuth(client, packet)\n\t\t\tdone()\n\t\t\tbreak\n\t\tcase 'pingresp':\n\t\t\tclient.log('_handlePacket :: received pingresp')\n\t\t\tclient.reschedulePing(true)\n\t\t\tdone()\n\t\t\tbreak\n\t\tcase 'disconnect':\n\t\t\tclient.emit('disconnect', packet)\n\t\t\tdone()\n\t\t\tbreak\n\t\tdefault:\n\t\t\t// TODO: unknown packet received. Should we emit an error?\n\t\t\tclient.log('_handlePacket :: unknown command')\n\t\t\tdone()\n\t\t\tbreak\n\t}\n}\n\nexport default handle\n"
  },
  {
    "path": "src/lib/handlers/publish.ts",
    "content": "import { type IPublishPacket } from 'mqtt-packet'\nimport { type PacketHandler } from '../shared'\n\nconst validReasonCodes = [0, 16, 128, 131, 135, 144, 145, 151, 153]\n\n/*\n  those late 2 case should be rewrite to comply with coding style:\n\n  case 1:\n  case 0:\n    // do not wait sending a puback\n    // no callback passed\n    if (1 === qos) {\n      this._sendPacket({\n        cmd: 'puback',\n        messageId: messageId\n      });\n    }\n    // emit the message event for both qos 1 and 0\n    this.emit('message', topic, message, packet);\n    this.handleMessage(packet, done);\n    break;\n  default:\n    // do nothing but every switch mus have a default\n    // log or throw an error about unknown qos\n    break;\n\n  for now i just suppressed the warnings\n  */\nconst handlePublish: PacketHandler = (client, packet: IPublishPacket, done) => {\n\tclient.log('handlePublish: packet %o', packet)\n\tdone = typeof done !== 'undefined' ? done : client.noop\n\tlet topic = packet.topic.toString()\n\tconst message = packet.payload\n\tconst { qos } = packet\n\tconst { messageId } = packet\n\tconst { options } = client\n\tif (client.options.protocolVersion === 5) {\n\t\tlet alias: number\n\t\tif (packet.properties) {\n\t\t\talias = packet.properties.topicAlias\n\t\t}\n\t\tif (typeof alias !== 'undefined') {\n\t\t\tif (topic.length === 0) {\n\t\t\t\tif (alias > 0 && alias <= 0xffff) {\n\t\t\t\t\tconst gotTopic =\n\t\t\t\t\t\tclient['topicAliasRecv'].getTopicByAlias(alias)\n\t\t\t\t\tif (gotTopic) {\n\t\t\t\t\t\ttopic = gotTopic\n\t\t\t\t\t\tclient.log(\n\t\t\t\t\t\t\t'handlePublish :: topic complemented by alias. topic: %s - alias: %d',\n\t\t\t\t\t\t\ttopic,\n\t\t\t\t\t\t\talias,\n\t\t\t\t\t\t)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclient.log(\n\t\t\t\t\t\t\t'handlePublish :: unregistered topic alias. alias: %d',\n\t\t\t\t\t\t\talias,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tclient.emit(\n\t\t\t\t\t\t\t'error',\n\t\t\t\t\t\t\tnew Error('Received unregistered Topic Alias'),\n\t\t\t\t\t\t)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tclient.log(\n\t\t\t\t\t\t'handlePublish :: topic alias out of range. alias: %d',\n\t\t\t\t\t\talias,\n\t\t\t\t\t)\n\t\t\t\t\tclient.emit(\n\t\t\t\t\t\t'error',\n\t\t\t\t\t\tnew Error('Received Topic Alias is out of range'),\n\t\t\t\t\t)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else if (client['topicAliasRecv'].put(topic, alias)) {\n\t\t\t\tclient.log(\n\t\t\t\t\t'handlePublish :: registered topic: %s - alias: %d',\n\t\t\t\t\ttopic,\n\t\t\t\t\talias,\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tclient.log(\n\t\t\t\t\t'handlePublish :: topic alias out of range. alias: %d',\n\t\t\t\t\talias,\n\t\t\t\t)\n\t\t\t\tclient.emit(\n\t\t\t\t\t'error',\n\t\t\t\t\tnew Error('Received Topic Alias is out of range'),\n\t\t\t\t)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\tclient.log('handlePublish: qos %d', qos)\n\tswitch (qos) {\n\t\tcase 2: {\n\t\t\toptions.customHandleAcks(\n\t\t\t\ttopic,\n\t\t\t\tmessage as Buffer,\n\t\t\t\tpacket,\n\t\t\t\t(error, code) => {\n\t\t\t\t\tif (typeof error === 'number') {\n\t\t\t\t\t\tcode = error\n\t\t\t\t\t\terror = null\n\t\t\t\t\t}\n\t\t\t\t\tif (error) {\n\t\t\t\t\t\treturn client.emit('error', error as Error)\n\t\t\t\t\t}\n\t\t\t\t\tif (validReasonCodes.indexOf(code) === -1) {\n\t\t\t\t\t\treturn client.emit(\n\t\t\t\t\t\t\t'error',\n\t\t\t\t\t\t\tnew Error('Wrong reason code for pubrec'),\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\tif (code) {\n\t\t\t\t\t\tclient['_sendPacket'](\n\t\t\t\t\t\t\t{ cmd: 'pubrec', messageId, reasonCode: code },\n\t\t\t\t\t\t\tdone,\n\t\t\t\t\t\t)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclient.incomingStore.put(packet, () => {\n\t\t\t\t\t\t\tclient['_sendPacket'](\n\t\t\t\t\t\t\t\t{ cmd: 'pubrec', messageId },\n\t\t\t\t\t\t\t\tdone,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t)\n\t\t\tbreak\n\t\t}\n\t\tcase 1: {\n\t\t\t// emit the message event\n\t\t\toptions.customHandleAcks(\n\t\t\t\ttopic,\n\t\t\t\tmessage as Buffer,\n\t\t\t\tpacket,\n\t\t\t\t(error, code) => {\n\t\t\t\t\tif (typeof error === 'number') {\n\t\t\t\t\t\tcode = error\n\t\t\t\t\t\terror = null\n\t\t\t\t\t}\n\t\t\t\t\tif (error) {\n\t\t\t\t\t\treturn client.emit('error', error as Error)\n\t\t\t\t\t}\n\t\t\t\t\tif (validReasonCodes.indexOf(code) === -1) {\n\t\t\t\t\t\treturn client.emit(\n\t\t\t\t\t\t\t'error',\n\t\t\t\t\t\t\tnew Error('Wrong reason code for puback'),\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\tif (!code) {\n\t\t\t\t\t\tclient.emit('message', topic, message as Buffer, packet)\n\t\t\t\t\t}\n\t\t\t\t\tclient.handleMessage(packet, (err) => {\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\treturn done && done(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tclient['_sendPacket'](\n\t\t\t\t\t\t\t{ cmd: 'puback', messageId, reasonCode: code },\n\t\t\t\t\t\t\tdone,\n\t\t\t\t\t\t)\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t)\n\t\t\tbreak\n\t\t}\n\t\tcase 0:\n\t\t\t// emit the message event\n\t\t\tclient.emit('message', topic, message as Buffer, packet)\n\t\t\tclient.handleMessage(packet, done)\n\t\t\tbreak\n\t\tdefault:\n\t\t\t// do nothing\n\t\t\tclient.log('handlePublish: unknown QoS. Doing nothing.')\n\t\t\t// log or throw an error about unknown qos\n\t\t\tbreak\n\t}\n}\n\nexport default handlePublish\n"
  },
  {
    "path": "src/lib/handlers/pubrel.ts",
    "content": "import {\n\ttype IPubcompPacket,\n\ttype IPublishPacket,\n\ttype IPubrelPacket,\n} from 'mqtt-packet'\nimport { type PacketHandler } from '../shared'\n\nconst handlePubrel: PacketHandler = (client, packet: IPubrelPacket, done) => {\n\tclient.log('handling pubrel packet')\n\tconst callback = typeof done !== 'undefined' ? done : client.noop\n\tconst { messageId } = packet\n\n\tconst comp: IPubcompPacket = { cmd: 'pubcomp', messageId }\n\n\tclient.incomingStore.get(packet, (err, pub: IPublishPacket) => {\n\t\tif (!err) {\n\t\t\tclient.emit('message', pub.topic, pub.payload as Buffer, pub)\n\t\t\tclient.handleMessage(pub, (err2) => {\n\t\t\t\tif (err2) {\n\t\t\t\t\treturn callback(err2)\n\t\t\t\t}\n\t\t\t\tclient.incomingStore.del(pub, client.noop)\n\t\t\t\tclient['_sendPacket'](comp, callback)\n\t\t\t})\n\t\t} else {\n\t\t\tclient['_sendPacket'](comp, callback)\n\t\t}\n\t})\n}\n\nexport default handlePubrel\n"
  },
  {
    "path": "src/lib/is-browser.ts",
    "content": "// Global type declaration for Deno\ndeclare global {\n\tconst Deno: any\n}\n\nconst isStandardBrowserEnv = () => {\n\t// window is only defined when it is a browser\n\tif (typeof window !== 'undefined') {\n\t\t// Is the process an electron application\n\t\t// check if we are in electron `renderer`\n\t\tconst electronRenderCheck =\n\t\t\ttypeof navigator !== 'undefined' &&\n\t\t\tnavigator.userAgent?.toLowerCase().indexOf(' electron/') > -1\n\t\tif (electronRenderCheck && process?.versions) {\n\t\t\tconst electronMainCheck = Object.prototype.hasOwnProperty.call(\n\t\t\t\tprocess.versions,\n\t\t\t\t'electron',\n\t\t\t)\n\t\t\t// Both electron checks are only true if the following webPreferences are set in the main electron BrowserWindow()\n\t\t\t//   webPreferences: {\n\t\t\t//     sandbox: false,\n\t\t\t//     nodeIntegration: true\n\t\t\t//     contextIsolation: false\n\t\t\t// }\n\t\t\treturn !electronMainCheck\n\t\t}\n\t\treturn typeof window.document !== 'undefined'\n\t}\n\t// return false if nothing is detected\n\treturn false\n}\n\nconst isWebWorkerEnv = () =>\n\tBoolean(\n\t\ttypeof self === 'object' &&\n\t\t\tself?.constructor?.name?.includes('WorkerGlobalScope') &&\n\t\t\ttypeof Deno === 'undefined',\n\t)\n\nconst isReactNativeEnv = () =>\n\ttypeof navigator !== 'undefined' && navigator.product === 'ReactNative'\n\nconst isBrowser =\n\tisStandardBrowserEnv() || isWebWorkerEnv() || isReactNativeEnv()\n\nexport const isWebWorker = isWebWorkerEnv()\n\nexport const isReactNativeBrowser = isReactNativeEnv()\n\nexport default isBrowser\n"
  },
  {
    "path": "src/lib/shared.ts",
    "content": "import type { Packet, ISubackPacket } from 'mqtt-packet'\nimport type { Duplex as NativeDuplex } from 'node:stream'\nimport type { Duplex } from 'readable-stream'\nimport type MqttClient from './client'\nimport type { IClientOptions } from './client'\n\nexport type DoneCallback = (error?: Error) => void\n\nexport type GenericCallback<T> = (error?: Error, result?: T) => void\n\nexport type VoidCallback = () => void\n\nexport type IStream = (Duplex | NativeDuplex) & {\n\t/** only set on browsers, it's a [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)  */\n\tsocket?: any\n}\n\nexport type StreamBuilder = (\n\tclient: MqttClient,\n\topts?: IClientOptions,\n) => IStream\n\nexport type Callback = () => void\n\nexport type PacketHandler = (\n\tclient: MqttClient,\n\tpacket: Packet,\n\tdone?: DoneCallback,\n) => void\n\nexport type TimerVariant = 'auto' | 'worker' | 'native'\n\nexport class ErrorWithReasonCode extends Error {\n\tpublic code: number\n\n\tpublic constructor(message: string, code: number) {\n\t\tsuper(message)\n\t\tthis.code = code\n\n\t\t// We need to set the prototype explicitly\n\t\tObject.setPrototypeOf(this, ErrorWithReasonCode.prototype)\n\t\tObject.getPrototypeOf(this).name = 'ErrorWithReasonCode'\n\t}\n}\n\nexport class ErrorWithSubackPacket extends Error {\n\tpublic packet: ISubackPacket\n\n\tpublic constructor(message: string, packet: ISubackPacket) {\n\t\tsuper(message)\n\t\tthis.packet = packet\n\n\t\t// We need to set the prototype explicitly\n\t\tObject.setPrototypeOf(this, ErrorWithSubackPacket.prototype)\n\t\tObject.getPrototypeOf(this).name = 'ErrorWithSubackPacket'\n\t}\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport type Constructor<T = {}> = new (...args: any[]) => T\n\nexport function applyMixin(\n\ttarget: Constructor,\n\tmixin: Constructor,\n\tincludeConstructor = false,\n): void {\n\t// Figure out the inheritance chain of the mixin\n\tconst inheritanceChain: Constructor[] = [mixin]\n\n\twhile (true) {\n\t\tconst current = inheritanceChain[0]\n\t\tconst base = Object.getPrototypeOf(current)\n\t\tif (base?.prototype) {\n\t\t\tinheritanceChain.unshift(base)\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\tfor (const ctor of inheritanceChain) {\n\t\tfor (const prop of Object.getOwnPropertyNames(ctor.prototype)) {\n\t\t\t// Do not override the constructor\n\t\t\tif (includeConstructor || prop !== 'constructor') {\n\t\t\t\tObject.defineProperty(\n\t\t\t\t\ttarget.prototype,\n\t\t\t\t\tprop,\n\t\t\t\t\tObject.getOwnPropertyDescriptor(ctor.prototype, prop) ??\n\t\t\t\t\t\tObject.create(null),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n}\nexport const nextTick =\n\ttypeof process?.nextTick === 'function'\n\t\t? process.nextTick\n\t\t: (callback: () => void) => {\n\t\t\t\tsetTimeout(callback, 0)\n\t\t\t}\n\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nexport const MQTTJS_VERSION = require('../../package.json').version\n"
  },
  {
    "path": "src/lib/store.ts",
    "content": "/**\n * Module dependencies\n */\nimport { Readable } from 'readable-stream'\nimport { type Packet } from 'mqtt-packet'\nimport { type DoneCallback } from './shared'\n\nconst streamsOpts = { objectMode: true }\nconst defaultStoreOptions = {\n\tclean: true,\n}\n\nexport interface IStoreOptions {\n\t/**\n\t * true, clear _inflights at close\n\t */\n\tclean?: boolean\n}\n\nexport type PacketCallback = (error?: Error, packet?: Packet) => void\n\nexport interface IStore {\n\t/**\n\t * Adds a packet to the store, a packet is\n\t * anything that has a messageId property.\n\t *\n\t */\n\tput(packet: Packet, cb: DoneCallback): IStore\n\n\t/**\n\t * Creates a stream with all the packets in the store\n\t *\n\t */\n\tcreateStream(): Readable\n\n\t/**\n\t * deletes a packet from the store.\n\t */\n\tdel(packet: Pick<Packet, 'messageId'>, cb: PacketCallback): IStore\n\n\t/**\n\t * get a packet from the store.\n\t */\n\tget(packet: Pick<Packet, 'messageId'>, cb: PacketCallback): IStore\n\n\t/**\n\t * Close the store\n\t */\n\tclose(cb: DoneCallback): void\n}\n\n/**\n * In-memory implementation of the message store\n * This can actually be saved into files.\n *\n * @param {Object} [options] - store options\n */\nexport default class Store implements IStore {\n\tprivate options: IStoreOptions\n\n\tprivate _inflights: Map<number, Packet>\n\n\tconstructor(options?: IStoreOptions) {\n\t\tthis.options = options || {}\n\n\t\t// Defaults\n\t\tthis.options = { ...defaultStoreOptions, ...options }\n\n\t\tthis._inflights = new Map()\n\t}\n\n\t/**\n\t * Adds a packet to the store, a packet is\n\t * anything that has a messageId property.\n\t *\n\t */\n\tput(packet: Packet, cb: DoneCallback) {\n\t\tthis._inflights.set(packet.messageId, packet)\n\n\t\tif (cb) {\n\t\t\tcb()\n\t\t}\n\n\t\treturn this\n\t}\n\n\t/**\n\t * Creates a stream with all the packets in the store\n\t *\n\t */\n\tcreateStream() {\n\t\tconst stream = new Readable(streamsOpts)\n\t\tconst values = []\n\t\tlet destroyed = false\n\t\tlet i = 0\n\n\t\tthis._inflights.forEach((value, key) => {\n\t\t\tvalues.push(value)\n\t\t})\n\n\t\tstream._read = () => {\n\t\t\tif (!destroyed && i < values.length) {\n\t\t\t\tstream.push(values[i++])\n\t\t\t} else {\n\t\t\t\tstream.push(null)\n\t\t\t}\n\t\t}\n\n\t\tstream.destroy = (err) => {\n\t\t\tif (destroyed) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdestroyed = true\n\n\t\t\tsetTimeout(() => {\n\t\t\t\tstream.emit('close')\n\t\t\t}, 0)\n\n\t\t\treturn stream\n\t\t}\n\n\t\treturn stream\n\t}\n\n\t/**\n\t * deletes a packet from the store.\n\t */\n\tdel(packet: Pick<Packet, 'messageId'>, cb: PacketCallback) {\n\t\tconst toDelete = this._inflights.get(packet.messageId)\n\t\tif (toDelete) {\n\t\t\tthis._inflights.delete(packet.messageId)\n\t\t\tcb(null, toDelete)\n\t\t} else if (cb) {\n\t\t\tcb(new Error('missing packet'))\n\t\t}\n\n\t\treturn this\n\t}\n\n\t/**\n\t * get a packet from the store.\n\t */\n\tget(packet: Pick<Packet, 'messageId'>, cb: PacketCallback) {\n\t\tconst storedPacket = this._inflights.get(packet.messageId)\n\t\tif (storedPacket) {\n\t\t\tcb(null, storedPacket)\n\t\t} else if (cb) {\n\t\t\tcb(new Error('missing packet'))\n\t\t}\n\n\t\treturn this\n\t}\n\n\t/**\n\t * Close the store\n\t */\n\tclose(cb: DoneCallback) {\n\t\tif (this.options.clean) {\n\t\t\tthis._inflights = null\n\t\t}\n\t\tif (cb) {\n\t\t\tcb()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/lib/topic-alias-recv.ts",
    "content": "/**\n * Topic Alias receiving manager\n * This holds alias to topic map\n * @param {Number} [max] - topic alias maximum entries\n */\nexport default class TopicAliasRecv {\n\tprivate aliasToTopic: Record<number, string>\n\n\tpublic max: number\n\n\tpublic length: number\n\n\tconstructor(max: number) {\n\t\tthis.aliasToTopic = {}\n\t\tthis.max = max\n\t}\n\n\t/**\n\t * Insert or update topic - alias entry.\n\t * @param {String} [topic] - topic\n\t * @param {Number} [alias] - topic alias\n\t * @returns {Boolean} - if success return true otherwise false\n\t */\n\tput(topic: string, alias: number): boolean {\n\t\tif (alias === 0 || alias > this.max) {\n\t\t\treturn false\n\t\t}\n\t\tthis.aliasToTopic[alias] = topic\n\t\tthis.length = Object.keys(this.aliasToTopic).length\n\t\treturn true\n\t}\n\n\t/**\n\t * Get topic by alias\n\t * @param {String} [topic] - topic\n\t * @returns {Number} - if mapped topic exists return topic alias, otherwise return undefined\n\t */\n\tgetTopicByAlias(alias: number): string {\n\t\treturn this.aliasToTopic[alias]\n\t}\n\n\t/**\n\t * Clear all entries\n\t */\n\tclear() {\n\t\tthis.aliasToTopic = {}\n\t}\n}\n"
  },
  {
    "path": "src/lib/topic-alias-send.ts",
    "content": "/**\n * Module dependencies\n */\nimport { LRUCache } from 'lru-cache'\nimport { NumberAllocator } from 'number-allocator'\n\n/**\n * Topic Alias sending manager\n * This holds both topic to alias and alias to topic map\n * @param {Number} [max] - topic alias maximum entries\n */\nexport default class TopicAliasSend {\n\tprivate aliasToTopic: LRUCache<number, string>\n\n\tprivate topicToAlias: Record<string, number>\n\n\tprivate max: number\n\n\tprivate numberAllocator: NumberAllocator\n\n\tpublic length: number\n\n\tconstructor(max: number) {\n\t\tif (max > 0) {\n\t\t\tthis.aliasToTopic = new LRUCache<number, string>({ max })\n\t\t\tthis.topicToAlias = {}\n\t\t\tthis.numberAllocator = new NumberAllocator(1, max)\n\t\t\tthis.max = max\n\t\t\tthis.length = 0\n\t\t}\n\t}\n\n\t/**\n\t * Insert or update topic - alias entry.\n\t * @param {String} [topic] - topic\n\t * @param {Number} [alias] - topic alias\n\t * @returns {Boolean} - if success return true otherwise false\n\t */\n\tput(topic: string, alias: number): boolean {\n\t\tif (alias === 0 || alias > this.max) {\n\t\t\treturn false\n\t\t}\n\t\tconst entry = this.aliasToTopic.get(alias)\n\t\tif (entry) {\n\t\t\tdelete this.topicToAlias[entry]\n\t\t}\n\t\tthis.aliasToTopic.set(alias, topic)\n\t\tthis.topicToAlias[topic] = alias\n\t\tthis.numberAllocator.use(alias)\n\t\tthis.length = this.aliasToTopic.size\n\t\treturn true\n\t}\n\n\t/**\n\t * Get topic by alias\n\t * @param {Number} [alias] - topic alias\n\t * @returns {String} - if mapped topic exists return topic, otherwise return undefined\n\t */\n\tgetTopicByAlias(alias: number): string {\n\t\treturn this.aliasToTopic.get(alias)\n\t}\n\n\t/**\n\t * Get topic by alias\n\t * @param {String} [topic] - topic\n\t * @returns {Number} - if mapped topic exists return topic alias, otherwise return undefined\n\t */\n\tgetAliasByTopic(topic: string): number | undefined {\n\t\tconst alias = this.topicToAlias[topic]\n\t\tif (typeof alias !== 'undefined') {\n\t\t\tthis.aliasToTopic.get(alias) // LRU update\n\t\t}\n\t\treturn alias\n\t}\n\n\t/**\n\t * Clear all entries\n\t */\n\tclear() {\n\t\tthis.aliasToTopic.clear()\n\t\tthis.topicToAlias = {}\n\t\tthis.numberAllocator.clear()\n\t\tthis.length = 0\n\t}\n\n\t/**\n\t * Get Least Recently Used (LRU) topic alias\n\t * @returns {Number} - if vacant alias exists then return it, otherwise then return LRU alias\n\t */\n\tgetLruAlias(): number {\n\t\tconst alias = this.numberAllocator.firstVacant()\n\t\tif (alias) return alias\n\t\t// get last alias (key) from LRU cache\n\t\treturn [...this.aliasToTopic.keys()][this.aliasToTopic.size - 1]\n\t}\n}\n"
  },
  {
    "path": "src/lib/unique-message-id-provider.ts",
    "content": "import { NumberAllocator } from 'number-allocator'\nimport { type IMessageIdProvider } from './default-message-id-provider'\n\n/**\n * UniqueMessageAllocator constructor\n * @constructor\n */\nexport default class UniqueMessageIdProvider implements IMessageIdProvider {\n\tprivate numberAllocator: NumberAllocator\n\n\tprivate lastId: number\n\n\tconstructor() {\n\t\tthis.numberAllocator = new NumberAllocator(1, 65535)\n\t}\n\n\t/**\n\t * allocate\n\t *\n\t * Get the next messageId.\n\t * @return if messageId is fully allocated then return null,\n\t *         otherwise return the smallest usable unsigned int messageId.\n\t */\n\tallocate() {\n\t\tthis.lastId = this.numberAllocator.alloc()\n\t\treturn this.lastId\n\t}\n\n\t/**\n\t * getLastAllocated\n\t * Get the last allocated messageId.\n\t * @return unsigned int\n\t */\n\tgetLastAllocated() {\n\t\treturn this.lastId\n\t}\n\n\t/**\n\t * register\n\t * Register messageId. If success return true, otherwise return false.\n\t * @param { unsigned int } - messageId to register,\n\t * @return boolean\n\t */\n\tregister(messageId: number) {\n\t\treturn this.numberAllocator.use(messageId) as boolean\n\t}\n\n\t/**\n\t * deallocate\n\t * Deallocate messageId.\n\t * @param { unsigned int } - messageId to deallocate,\n\t */\n\tdeallocate(messageId: number) {\n\t\tthis.numberAllocator.free(messageId)\n\t}\n\n\t/**\n\t * clear\n\t * Deallocate all messageIds.\n\t */\n\tclear() {\n\t\tthis.numberAllocator.clear()\n\t}\n}\n"
  },
  {
    "path": "src/lib/validations.ts",
    "content": "/**\n * Validate a topic to see if it's valid or not.\n * A topic is valid if it follow below rules:\n * - Rule #1: If any part of the topic is not `+` or `#`, then it must not contain `+` and '#'\n * - Rule #2: Part `#` must be located at the end of the mailbox\n *\n * @param {String} topic - A topic\n * @returns {Boolean} If the topic is valid, returns true. Otherwise, returns false.\n */\nexport function validateTopic(topic: string): boolean {\n\tconst parts = topic.split('/')\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tif (parts[i] === '+') {\n\t\t\tcontinue\n\t\t}\n\n\t\tif (parts[i] === '#') {\n\t\t\t// for Rule #2\n\t\t\treturn i === parts.length - 1\n\t\t}\n\n\t\tif (parts[i].indexOf('+') !== -1 || parts[i].indexOf('#') !== -1) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\n/**\n * Validate an array of topics to see if any of them is valid or not\n * @param {Array} topics - Array of topics\n * @returns {String} If the topics is valid, returns null. Otherwise, returns the invalid one\n */\nexport function validateTopics(topics: string[]): string | null {\n\tif (topics.length === 0) {\n\t\treturn 'empty_topic_list'\n\t}\n\tfor (let i = 0; i < topics.length; i++) {\n\t\tif (!validateTopic(topics[i])) {\n\t\t\treturn topics[i]\n\t\t}\n\t}\n\treturn null\n}\n"
  },
  {
    "path": "src/mqtt.ts",
    "content": "/*\n * Copyright (c) 2015-2015 MQTT.js contributors.\n * Copyright (c) 2011-2014 Adam Rudd.\n *\n * See LICENSE for more information\n */\nimport MqttClient from './lib/client'\nimport DefaultMessageIdProvider from './lib/default-message-id-provider'\nimport UniqueMessageIdProvider from './lib/unique-message-id-provider'\nimport Store, { IStore } from './lib/store'\nimport connect, { connectAsync } from './lib/connect'\nimport KeepaliveManager from './lib/KeepaliveManager'\n\nexport const Client = MqttClient\nexport {\n\tconnect,\n\tconnectAsync,\n\tMqttClient,\n\tStore,\n\tDefaultMessageIdProvider,\n\tUniqueMessageIdProvider,\n\tIStore,\n\tKeepaliveManager,\n}\nexport * from './lib/client'\nexport * from './lib/shared'\nexport * from './lib/validations'\nexport { ReasonCodes } from './lib/handlers/ack'\nexport type { Timer } from './lib/get-timer'\n"
  },
  {
    "path": "test/browser/certs/server-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFCTCCAvGgAwIBAgIUfsHWL7ricrpv7uEMmHoDNUFt0W0wDQYJKoZIhvcNAQEL\nBQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMTAyNDA5NTYwMFoXDTI0MTAy\nMzA5NTYwMFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF\nAAOCAg8AMIICCgKCAgEAsdar/fZYadOItHPwI+7suwVc3p8TN7Gd0opRjAHj03es\nuVwOQ42DQgOpn1uq8nqHgZ5/F0rEvb39CXDMtgChPZDjZVqUX7ciW235klPYROeJ\n0ytFJIZQG1ejgHGmwy7Setc+9QJhCm5H9qdfRYDv6G7S/Q771cXmB0g3vp0VDkk6\n+4Av2NbNsCe4P1LTi1rvLh23wfHV/FovtLBl25PgC5RmLbBSIO14bJqreRbfyHSG\nyc/RvU+yJghoCaJPV5lkuhHstxekAGUCwuI7R73Hia9+VJsCbDDEvoH1Peu4MPzG\nBk1nksJFcfYJmSgusSqVq0PIdF8ZZcIStaC45YDmu5D9whhHf0wC2yhNbAPCZvkh\nGwpaK9v6km6v+0LOzkh0rjk+HRjIpXi/E8WGRXwfEljslx3fb/mKDxNYu0l7vNRm\nt1ToMJ273ccABhj5qxjmef1JeUK+XWCjBhPiRmzWEQqgjmiEQVONRFcLcY5EurZP\nnEs36RINmr3ErrpWdjugUiRBixnCFOlKPRS6hg23vwPJb2U1F1LQx2tHJnJ+E4rY\nqin0tImbQyljXMoW5MEhF3jNpPguGVsfqQd/z/7QHjjP2/ZFXKSfiB2pwfU7pcNM\n2Gi5dC9/gTAgLPKiMyikh558c5IYh9foNA5oVPCBFdnQ/DoQYXL7P99OC6yj+VMC\nAwEAAaNTMFEwHQYDVR0OBBYEFODr4e1O3Ujjn5xKJP/WWCoEN24mMB8GA1UdIwQY\nMBaAFODr4e1O3Ujjn5xKJP/WWCoEN24mMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI\nhvcNAQELBQADggIBAI14f2RXKbBZlR1yry9FJCX/IvmwgwYyVYL/rjFFIWNqfs8C\nVoYEmmF62ujgv6vYTXhQpdn7INRqsCGVI2DTRz4Y63diek7ShamznQt0NiQGHl//\nP4PO0XB5YpLRJy22X9RuUj0PmZyG4HHqnqBg40YWbYB0ttkbR5D5QmxEJA1iDrX4\nZ7qq64izwRF4l0x1ubOpU5YkPWSE5VgOY88sy2dnxKTEcwGF0JcBBAvAiByvIbDX\nuxn3xXA6AMne+B7UhMHiIowfHCJs8dDK6/SVMSpz+A7roZP5RZK0OCLqYCklnSOo\nQSflXpd6+FsytwETEPpWouNWDIple9/rUgLLBofFoo4vMNCW4syNVEmdNvAYo5M3\nnLSzx32IkSoF4VhKIJnbT3ROTVB37ZvQNbJXzP/jbgzgEJa/EKZwms5ZwE9aX2GM\nWhJcTBr1Wkde8M+1kw7RxcIBTiFVEhxug6EuzMN33m4CwngJQj+pqnctrCWi5P8D\nKcGoZoHSZ+6AEBsCRJHWNl5NApcKVfiRtmDH1zNjdj+bDpT4G6c5CKt06nN79heO\nrjwNoHQ08/DRyvXAarefu4vOHB3VbMNmU34Px36gd47b0PxATGLqwSmlTBhQqp/e\nxb5l9K4cENFew08PwZGMzjwFQ4MK3AQC0g0HSgKVQATkJWZwUR6H29wfr1I3\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/browser/certs/server-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCx1qv99lhp04i0\nc/Aj7uy7BVzenxM3sZ3SilGMAePTd6y5XA5DjYNCA6mfW6ryeoeBnn8XSsS9vf0J\ncMy2AKE9kONlWpRftyJbbfmSU9hE54nTK0UkhlAbV6OAcabDLtJ61z71AmEKbkf2\np19FgO/obtL9DvvVxeYHSDe+nRUOSTr7gC/Y1s2wJ7g/UtOLWu8uHbfB8dX8Wi+0\nsGXbk+ALlGYtsFIg7Xhsmqt5Ft/IdIbJz9G9T7ImCGgJok9XmWS6Eey3F6QAZQLC\n4jtHvceJr35UmwJsMMS+gfU967gw/MYGTWeSwkVx9gmZKC6xKpWrQ8h0XxllwhK1\noLjlgOa7kP3CGEd/TALbKE1sA8Jm+SEbClor2/qSbq/7Qs7OSHSuOT4dGMileL8T\nxYZFfB8SWOyXHd9v+YoPE1i7SXu81Ga3VOgwnbvdxwAGGPmrGOZ5/Ul5Qr5dYKMG\nE+JGbNYRCqCOaIRBU41EVwtxjkS6tk+cSzfpEg2avcSuulZ2O6BSJEGLGcIU6Uo9\nFLqGDbe/A8lvZTUXUtDHa0cmcn4TitiqKfS0iZtDKWNcyhbkwSEXeM2k+C4ZWx+p\nB3/P/tAeOM/b9kVcpJ+IHanB9Tulw0zYaLl0L3+BMCAs8qIzKKSHnnxzkhiH1+g0\nDmhU8IEV2dD8OhBhcvs/304LrKP5UwIDAQABAoICABQ64+N8Glr7fcHCreQgjJx1\nGTdCkvM9yMLHtPa/dkplgugDwU73nPatp6Y2+CTPlx+XHJpVM+PpELektcd/Hdh8\n5WlFBbuM/821HnQCBrLxQIadlabN1/SsTQSnqRKV/nU2jB+0pBOwL46EQ/xflQBK\nBHAAvC7Owtfm5AnSdlqNg7X9bd6gXNNM9BLRWhAwXs9OVnYrtlhtF/Uu2yoV0cxE\nwYoxVGkQ1SdMQZ+rivcTeQvMZhhqJaKu8nV6GcV7b8USP0J6tXoiriQ7i7EhtROu\nu35mCGoEnYpAYASpuREViB9Kad2ngjKFtwTNreC92TsbdwT0ln/dX7clRTM9AmzO\nPIrNk5/rslc/S9wsNQgsNFOuFFzo8WElMZYEdQ5AuqJHGG+tILs1DtHLgwWKXgub\ngdf7lRpe9SQ5C3oIk0zSDpeMWBesMwAjQYwPKvJzvN0jJw+cFM4n0QcwFU2q6bjm\nGkvGR6uFD4a0xNOmqZu7A6gBESf1+9daIW3shVMIlVcIzo4/XS8vNwku70PDcfeA\nPiDIR7ovZR/kipDbDYKiY/4zFLB+EJ6p5lsKAu91zeCkLRnAGyJmZFgL3Xb4espt\n5zV31TU2itw349N99SjyKJICbSckgp50grCZp1316g69wXP7XGIoidj8Wi44MYyT\nTSKgojvRKeYOyd3+7EExAoIBAQDfq66CxWqL3ayLVM7aEweycfpDVnHwgSMCh+tf\n2dMugoNF2WWk0mtulW9bJRRbNXUcpq7MrhXP5TQph1Fykfe7jQ0w/A9cHC34G9ud\nZbrMRSFCPqhNoE7k6sseB8bIYKZjlPW7hds+WfmXnl/rHZ6COpcxtvVlPKXRLG77\nQhVZ/gCQXhbwPD8FjajTw3KVnCYYmupeEmZ1XP6XG/KWjeM+wW5zN/O71Dmzhlfh\nr+zNwxP6opxoBDJL2qAVluLoym9ZekGFF5WALjNInUmuzQt2bEjiIBP66wkiR6jV\nbKawIzBTZULflZLdjPZeYcccjiLFuF7DMM1m0FZZRPyYn5OxAoIBAQDLixmhFqQx\n3ChBfUsqaRuqrxA2VUZqyr2EdF3bSRYHu7TenfAzdzVKga4WcIQ+ROSODs/Bkwki\nCCUMulFq0LNZUvBwebgFer/uyozK4H+JVfXi63NLSBcgb4SnJN4OAIvBVhS+hoeS\ne7EFWBLW67a6ie5HmQig1UiHOSV5LjpSgmcRkI76SWLu4iYnXhRPGX+yDJ7wgwMI\nerd7apVvFXCK3PI0KykiB3Q8USGrxFA0PoEOSlRzOF7QirXH+HnHZpWDLSDTJYMd\nXc6Aoab3Q9B+K3EP0+jIFXl8c35rVKNtiI7mT8K5DsQSLnT1zTknO9pDNoJhDlIQ\nTzkwPLwFM/JDAoIBAQC9sEADr6E4lPrr/zSeRV1VvMvdp8ZhjWM/lj3LwFQVMaJa\n7pABRGLQGfOifMPSxndXoRTFiS3+bgfa4GP2okxSxsDVNi9gkpV5wUZzKf9NlaMr\nJzBdBQNjqKwx/65Z6X2zHnV4X4EcU/oU17/JCgmPsFjhVKSxAsotp11HwTeSZpPb\n26/gdXeNIiJ31V2r/A1i+J6iwUAbcI6JYK5PeG2snycol98hAE8fLWQChsxxhGvB\nN38lx8lQA02YgapWergMTl6anPGxD312q839bO+3swXsn//R4NqSM+u/dLG41174\nmvwn2hX/xrh6Oro+QVzECiRzrUPFWhKhCpyeY1dBAoIBAQCuS9cOkozYBWaTbe9H\nyenfJzoCzNMX77SKD/uvI86xoJMpbqWZ7KK9wn6IyKD1kZkF7LkLUhWoBsFzPLkr\ndrud6d/q8NAcIRjqKpiVwdh0ih+cyXJZLMphBB3b4z4jT2RdkpEA9cJyNWlI4jp/\nwCkhY6ufs/QnhXwuD0l/k45tySzpZZiJyv3lAdMaMv3BHlyy4wvXeNjIEg28qXB9\nRyawQrNE6RC4CxoF7JOguuLTWhK5Yf0fDU5j1eQD8UqW7clqIuLQ7HZyVznV/Jc1\ndeS7pgeTPis/6uO1JlFDJ58q1J1Jq1NblMFhsFaHhc1DQ4WLuMm8wr6TKMQYAmof\nKnRxAoIBAE6+Aa3YYr5FHYIi11zFFyN15BABWSnXB1vhjD+t7usWwypakmM0DEWQ\nwrnAnSPosWjz3l+Oq/5j9H5kkkd5VXxVkj7w33NIHTbEjQt42cBOI6qDuRyO0rKF\nkh8ZgusQ1/qPJVa7r2d7u65F+UnB3LGUiXjWk9Pi62yq9JzvHUUk5jqidrQC30h2\nOPOWF4gcaQ8xuv4ENu1rt4WvAnPNadBI1JQah+RYi5k0P1PyUzBwbKsfTLa+ogjM\nt8lM7ZkMqC5GYD5LkxR9tmTkQzqTDli6g+Vk7Z/g2fpN6u347Qm1sP5Iz3kcadU5\n/qk0BiP0ZuvLwsPUJeqhrsC20tQWLMo=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "test/browser/test.js",
    "content": "import { expect } from '@esm-bundle/chai';\nimport mqtt from '../../'; // this will resolve to mqtt/dist/mqtt.esm.js\n\n// needed to test no-esm version /dist/mqtt.js\n/** @type { import('../../src') }*/\nconst mqtt2 = window.mqtt\n\n// get browser name\nconst userAgent = navigator.userAgent.toLowerCase().replace(/ /g, '_').replace(/\\//g, '_')\n\nlet browser = 'unknown'\n\nif (userAgent.includes('chrome')) {\n\tbrowser = 'chrome'\n} else if (userAgent.includes('firefox')) {\n\tbrowser = 'firefox'\n} else if (userAgent.includes('safari')) {\n\tbrowser = 'safari'\n}\n\nconst browserTopic = `test/${browser}`\nconsole.log('User Agent:', userAgent)\nconsole.log('Browser:', browser)\n\nfunction testProto(proto, port, cb = () => { }) {\n\tconst testTopic = `${browserTopic}/${proto}`\n\n\tdescribe('MQTT.js browser test with ' + proto.toUpperCase(), () => {\n\t\tafter(() => {\n\t\t\tif (client) {\n\t\t\t\tclient.end(() => {\n\t\t\t\t\tcb()\n\t\t\t\t\tclient = null;\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tcb()\n\t\t\t}\n\t\t})\n\n\t\t/** @type { import('../../src').MqttClient }*/\n\t\tlet client = null;\n\n\t\tit('should connect-publish-subscribe', (done) => {\n\t\t\t\n\t\t\texpect(typeof mqtt.Client.VERSION).to.equal('string')\n\t\t\t\n\t\t\tclient = mqtt.connect(`${proto}://localhost:${port}`, {\n\t\t\t\t// log: console.log.bind(console),\n\t\t\t\tclientId: `testClient-${browser}-${proto}`,\n\t\t\t})\n\t\t\tclient.on('offline', () => {\n\t\t\t\tconsole.log('client offline')\n\t\t\t\tdone(new Error('client offline'))\n\t\t\t})\n\t\t\tclient.on('connect', () => {\n\t\t\t\tconsole.log('client connect')\n\t\t\t})\n\t\t\tclient.on('reconnect', () => {\n\t\t\t\tconsole.log('client reconnect')\n\t\t\t})\n\n\t\t\tconst payload = 'Hello World!'\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.on('message', (topic, msg) => {\n\t\t\t\t\texpect(topic).to.equal(testTopic);\n\t\t\t\t\texpect(msg.toString()).to.equal(payload);\n\t\t\t\t\tclient.end(() => {\n\t\t\t\t\t\tclient = null;\n\t\t\t\t\t\tdone();\n\t\t\t\t\t});\n\t\t\t\t});\n\n\t\t\t\tclient.subscribe(testTopic, (err) => {\n\t\t\t\t\texpect(err).to.not.exist;\n\t\t\t\t\tif (!err) {\n\t\t\t\t\t\tclient.publish(testTopic, payload, (err2) => {\n\t\t\t\t\t\t\texpect(err2).to.not.exist;\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\n\t\t\tclient.on('error', (err) => {\n\t\t\t\tdone(err);\n\t\t\t});\n\t\t})\n\t})\n}\n\ndescribe('MQTT.js browser tests', () => {\n\tit('should work with ESM version', (done) => {\n\t\texpect(mqtt2).to.exist\n\t\texpect(mqtt2.connect).to.be.a('function')\n\t\texpect(mqtt2.Client).to.be.a('function')\n\t\tdone()\n\t})\n\n\tit('should work in a Web Worker', (done) => {\n\t\tconst worker = new Worker('test/browser/worker.js')\n\t\tlet ready = false\n\t\tworker.onmessage = (e) => {\n\t\t\tif (e.data === 'worker ready') {\n\t\t\t\tready = true\n\t\t\t} else if(e.data === 'keepalive'){\n\t\t\t\tworker.onerror = null\n\t\t\t\t// worker.terminate()\n\t\t\t\texpect(ready).to.be.true\n\t\t\t\tdone()\n\t\t\t }else {\n\t\t\t\tdone(Error(e.data))\n\t\t\t}\n\t\t}\n\n\t\tworker.onerror = (e) => {\n\t\t\tdone(Error(e.message))\n\t\t}\n\t})\n\n\ttestProto('ws', window.wsPort, () => {\n\t\ttestProto('wss', window.wssPort)\n\t})\n})\n\n\n"
  },
  {
    "path": "test/browser/worker.js",
    "content": "importScripts('/dist/mqtt.js');\n\n/** @type { import('../../src') }*/\nconst MQTT = mqtt;\n\nconsole.log('worker start');\nconsole.log('worker MQTT', MQTT);\n\nconst client = MQTT.connect(`ws://localhost:4000`, {\n    clientId: `testClient-worker_` + Math.random().toString(16).substr(2, 8),\n    keepalive: 2,\n});\n\nclient.on('offline', () => {\n    console.log('worker client offline');\n})\n\nclient.on('reconnect', () => {\n    console.log('worker client reconnect');\n})\n\nclient.on('error', (err) => {\n    console.log('worker client error', err);\n})\n\nclient.on('packetsend', (packet) => {\n    if (packet.cmd === 'pingreq') {\n        postMessage('keepalive');\n        client.end(() => {\n            console.log('worker client end');\n\n        });\n    }\n})\n\nclient.on('connect', () => {\n    console.log('worker client connect');\n    postMessage('worker ready');\n\n})"
  },
  {
    "path": "test/node/abstract_client.ts",
    "content": "/**\n * Testing dependencies\n */\nimport { assert } from 'chai'\nimport sinon from 'sinon'\nimport fs from 'fs'\nimport levelStore from 'mqtt-level-store'\nimport {\n\ttype IPublishPacket,\n\ttype IPubrelPacket,\n\ttype ISubackPacket,\n\ttype QoS,\n} from 'mqtt-packet'\nimport { type DoneCallback, ErrorWithReasonCode } from 'src/lib/shared'\nimport { fail } from 'assert'\nimport { describe, it, beforeEach, afterEach, after } from 'node:test'\nimport Store from '../../src/lib/store'\nimport serverBuilderFn from './server_helpers_for_client_tests'\nimport handlePubrel from '../../src/lib/handlers/pubrel'\nimport TeardownHelper from './helpers/TeardownHelper'\nimport handle from '../../src/lib/handlers/index'\nimport handlePublish from '../../src/lib/handlers/publish'\nimport mqtt, {\n\ttype IClientOptions,\n\ttype IClientPublishOptions,\n\ttype IClientSubscribeOptions,\n\ttype ISubscriptionMap,\n\ttype ISubscriptionRequest,\n} from '../../src'\n\n/**\n * These tests try to be consistent with names for servers (brokers) and clients,\n * but it can be confusing. To make it easier, here is a handy translation\n * chart:\n *\n * name           | meaning\n * ---------------|--------\n * client         | The MQTT.js client object being tested. A new instance is created for each test (by calling the `connect` function.)\n * server         | A mock broker that you can control. The same server instance is used for all tests, so only use this if you plan to clean up when you're done.\n * serverBuilder  | A factory that can make mock test servers (MQTT brokers). Useful if you need to do things that you can't (or don't want to) clean up after your test is done.\n * server2        | The name used for mock brokers that are created for an individual test and then destroyed.\n * serverClient   | An socket on the mock broker. This gets created when your client connects and gets collected when you're done with it.\n *\n * Also worth noting:\n *\n * `serverClient.disconnect()` does not disconnect that socket. Instead, it sends an MQTT disconnect packet.\n * If you want to disconnect the socket from the broker side, you probably want to use `serverClient.destroy()`\n * or `serverClient.stream.destroy()`.\n *\n */\n\nconst fakeTimersOptions = {\n\tshouldClearNativeTimers: true,\n}\n\nexport default function abstractTest(server, config, ports) {\n\tconst version = config.protocolVersion || 4\n\tconst teardownHelper = new TeardownHelper()\n\n\tfunction connect(opts?: IClientOptions | string) {\n\t\tif (typeof opts === 'string') {\n\t\t\topts = { host: opts }\n\t\t}\n\t\topts = { ...config, ...opts } as IClientOptions\n\t\tconst instance = mqtt.connect(opts)\n\t\tteardownHelper.addClient(instance)\n\t\treturn instance\n\t}\n\n\tfunction serverBuilder(...args: Parameters<typeof serverBuilderFn>) {\n\t\tconst instance = serverBuilderFn(...args)\n\t\tteardownHelper.addServer(instance)\n\t\treturn instance\n\t}\n\n\tasync function beforeEachExec() {\n\t\tawait teardownHelper.runAll()\n\t\tteardownHelper.reset({ removeOnce: true })\n\t}\n\n\tasync function afterExec() {\n\t\tawait teardownHelper.runAll()\n\t\tteardownHelper.reset()\n\t}\n\n\tafter(afterExec)\n\n\tdescribe('closing', () => {\n\t\tbeforeEach(beforeEachExec)\n\t\tafter(afterExec)\n\n\t\tit('should emit close if stream closes', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.stream.end()\n\t\t\t})\n\t\t\tclient.once('close', () => {\n\t\t\t\tclient.end((err) => done(err))\n\t\t\t})\n\t\t})\n\n\t\tit('should mark the client as disconnected', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.once('close', () => {\n\t\t\t\tclient.end((err) => {\n\t\t\t\t\tif (!client.connected) {\n\t\t\t\t\t\tdone(err)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdone(new Error('Not marked as disconnected'))\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tassert.isFalse(client.connected)\n\t\t\t})\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.stream.end()\n\t\t\t})\n\t\t})\n\n\t\tit('should destroy keepalive manager if stream closes', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.once('close', () => {\n\t\t\t\tassert.notExists(client.keepaliveManager)\n\n\t\t\t\tclient.end(true, (err) => done(err))\n\t\t\t})\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tassert.exists(client.keepaliveManager)\n\n\t\t\t\tclient.stream.end()\n\t\t\t})\n\t\t})\n\n\t\tit('should emit close after end called', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.once('close', () => {\n\t\t\t\tdone()\n\t\t\t})\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.end()\n\t\t\t})\n\t\t})\n\n\t\tit('should emit end after end called and client must be disconnected', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.once('end', () => {\n\t\t\t\tif (client.disconnected) {\n\t\t\t\t\treturn done()\n\t\t\t\t}\n\t\t\t\tdone(new Error('client must be disconnected'))\n\t\t\t})\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.end()\n\t\t\t})\n\t\t})\n\n\t\tit('should pass store close error to end callback but not to end listeners (incomingStore)', function _test(t, done) {\n\t\t\tconst store = new Store()\n\t\t\tconst client = connect({ incomingStore: store })\n\n\t\t\tstore.close = (cb) => {\n\t\t\t\tcb(new Error('test'))\n\t\t\t}\n\t\t\tclient.once('end', (...args) => {\n\t\t\t\tif (args.length === 0) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthrow new Error('no argument should be passed to event')\n\t\t\t})\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.end((testError) => {\n\t\t\t\t\tif (testError && testError.message === 'test') {\n\t\t\t\t\t\treturn done()\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error('bad argument passed to callback')\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should pass store close error to end callback but not to end listeners (outgoingStore)', function _test(t, done) {\n\t\t\tconst store = new Store()\n\t\t\tconst client = connect({ outgoingStore: store })\n\n\t\t\tstore.close = (cb) => {\n\t\t\t\tcb(new Error('test'))\n\t\t\t}\n\t\t\tclient.once('end', (...args) => {\n\t\t\t\tif (args.length === 0) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthrow new Error('no argument should be passed to event')\n\t\t\t})\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.end((testError) => {\n\t\t\t\t\tif (testError && testError.message === 'test') {\n\t\t\t\t\t\treturn done()\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error('bad argument passed to callback')\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should return `this` if end called twice', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.end()\n\t\t\t\tconst value = client.end()\n\t\t\t\tif (value === client) {\n\t\t\t\t\tdone()\n\t\t\t\t} else {\n\t\t\t\t\tdone(new Error('Not returning client.'))\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tit('should emit end only on first client end', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.once('end', () => {\n\t\t\t\tconst timeout = setTimeout(() => done(), 200)\n\t\t\t\tclient.once('end', () => {\n\t\t\t\t\tclearTimeout(timeout)\n\t\t\t\t\tdone(new Error('end was emitted twice'))\n\t\t\t\t})\n\t\t\t\tclient.end()\n\t\t\t})\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.end()\n\t\t\t})\n\t\t})\n\n\t\tit('should destroy keepalive manager after end called', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tassert.exists(client.keepaliveManager)\n\t\t\t\tclient.end((err) => {\n\t\t\t\t\tassert.notExists(client.keepaliveManager)\n\t\t\t\t\tdone(err)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should be able to end even on a failed connection', function _test(t, done) {\n\t\t\tconst client = connect({ host: 'this_hostname_should_not_exist' })\n\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tdone(new Error('Failed to end a disconnected client'))\n\t\t\t}, 500)\n\n\t\t\tsetTimeout(() => {\n\t\t\t\tclient.end((err) => {\n\t\t\t\t\tclearTimeout(timeout)\n\t\t\t\t\tdone(err)\n\t\t\t\t})\n\t\t\t}, 200)\n\t\t})\n\n\t\tit('should emit end even on a failed connection', function _test(t, done) {\n\t\t\tconst client = connect({ host: 'this_hostname_should_not_exist' })\n\t\t\tlet timeoutEmitted = false\n\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\ttimeoutEmitted = true\n\t\t\t\tdone(new Error('Disconnected client has failed to emit end'))\n\t\t\t}, 500)\n\n\t\t\tclient.once('end', () => {\n\t\t\t\t// Prevent hanging test if `end` is not emitted before timeout\n\t\t\t\tif (!timeoutEmitted) {\n\t\t\t\t\tclearTimeout(timeout)\n\t\t\t\t\tdone()\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// after 200ms manually invoke client.end\n\t\t\tsetTimeout(() => {\n\t\t\t\tclient.end.call(client)\n\t\t\t}, 200)\n\t\t})\n\n\t\tit.skip('should emit end only once for a reconnecting client', function _test(t, done) {\n\t\t\t// I want to fix this test, but it will take signficant work, so I am marking it as a skipping test right now.\n\t\t\t// Reason for it is that there are overlaps in the reconnectTimer and connectTimer. In the PR for this code\n\t\t\t// there will be gists showing the difference between a successful test here and a failed test. For now we\n\t\t\t// will add the retries syntax because of the flakiness.\n\t\t\tconst client = connect({\n\t\t\t\thost: 'this_hostname_should_not_exist',\n\t\t\t\tconnectTimeout: 10,\n\t\t\t\treconnectPeriod: 20,\n\t\t\t})\n\t\t\tsetTimeout(() => done(), 1000)\n\t\t\tconst endCallback = () => {\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\tspy.callCount,\n\t\t\t\t\t1,\n\t\t\t\t\t'end was emitted more than once for reconnecting client',\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst spy = sinon.spy(endCallback)\n\t\t\tclient.on('end', spy)\n\t\t\tsetTimeout(() => {\n\t\t\t\tclient.end.call(client)\n\t\t\t}, 300)\n\t\t})\n\t})\n\n\tdescribe('connecting', () => {\n\t\tbeforeEach(beforeEachExec)\n\t\tafter(afterExec)\n\n\t\tit('should connect to the broker', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.on('error', done)\n\n\t\t\tserver.once('client', () => {\n\t\t\t\tclient.end((err) => done(err))\n\t\t\t})\n\t\t})\n\n\t\tit('should send a default client id', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.on('error', done)\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('connect', (packet) => {\n\t\t\t\t\tassert.include(packet.clientId, 'mqttjs')\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should send be clean by default', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.on('error', done)\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('connect', (packet) => {\n\t\t\t\t\tassert.strictEqual(packet.clean, true)\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should connect with the given client id', function _test(t, done) {\n\t\t\tconst client = connect({ clientId: 'testclient' })\n\t\t\tclient.on('error', (err) => {\n\t\t\t\tthrow err\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('connect', (packet) => {\n\t\t\t\t\tassert.include(packet.clientId, 'testclient')\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should connect with the client id and unclean state', function _test(t, done) {\n\t\t\tconst client = connect({ clientId: 'testclient', clean: false })\n\t\t\tclient.on('error', (err) => {\n\t\t\t\tthrow err\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('connect', (packet) => {\n\t\t\t\t\tassert.include(packet.clientId, 'testclient')\n\t\t\t\t\tassert.isFalse(packet.clean)\n\t\t\t\t\tclient.end(false, (err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should require a clientId with clean=false', function _test(t, done) {\n\t\t\tlet errorCaught = false\n\n\t\t\ttry {\n\t\t\t\tconst client = connect({ clean: false })\n\t\t\t\tclient.on('error', (err) => {\n\t\t\t\t\tdone(err)\n\t\t\t\t})\n\t\t\t} catch (err) {\n\t\t\t\terrorCaught = true\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\terr.message,\n\t\t\t\t\t'Missing clientId for unclean clients',\n\t\t\t\t)\n\t\t\t\tdone()\n\t\t\t} finally {\n\t\t\t\tif (!errorCaught) {\n\t\t\t\t\tdone(new Error('Client should have thrown an error'))\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tit('should default to localhost', function _test(t, done) {\n\t\t\tconst client = connect({ clientId: 'testclient' })\n\t\t\tclient.on('error', (err) => {\n\t\t\t\tthrow err\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('connect', (packet) => {\n\t\t\t\t\tassert.include(packet.clientId, 'testclient')\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should emit connect', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.once('connect', (packet: mqtt.IConnackPacket) => {\n\t\t\t\tassert.equal(packet.cmd, 'connack')\n\t\t\t\tclient.end(true, (err) => done(err))\n\t\t\t})\n\t\t\tclient.once('error', done)\n\t\t})\n\n\t\tit('should provide connack packet with connect event', function _test(t, done) {\n\t\t\tconst connack =\n\t\t\t\tversion === 5\n\t\t\t\t\t? { reasonCode: 0, sessionPresent: undefined }\n\t\t\t\t\t: { returnCode: 0, sessionPresent: undefined }\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tconnack.sessionPresent = true\n\t\t\t\tserverClient.connack(connack)\n\t\t\t\tserver.once('client', (serverClient2) => {\n\t\t\t\t\tconnack.sessionPresent = false\n\t\t\t\t\tserverClient2.connack(connack)\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tconst client = connect()\n\t\t\tclient.once('connect', (packet) => {\n\t\t\t\tassert.strictEqual(packet.sessionPresent, true)\n\t\t\t\tclient.once('connect', (packet2) => {\n\t\t\t\t\tassert.strictEqual(packet2.sessionPresent, false)\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should mark the client as connected', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.once('connect', () => {\n\t\t\t\tassert.isTrue(client.connected)\n\t\t\t\tclient.end((err) => done(err))\n\t\t\t})\n\t\t})\n\n\t\tit('should emit error on invalid clientId', function _test(t, done) {\n\t\t\tconst client = connect({ clientId: 'invalid' })\n\t\t\tclient.once('connect', () => {\n\t\t\t\tdone(new Error('Should not emit connect'))\n\t\t\t})\n\t\t\tclient.once('error', (error: ErrorWithReasonCode) => {\n\t\t\t\tconst value = version === 5 ? 128 : 2\n\t\t\t\tassert.strictEqual(error.code, value) // code for clientID identifer rejected\n\t\t\t\tclient.end((err) => done(err))\n\t\t\t})\n\t\t})\n\n\t\tit('should emit error event if the socket refuses the connection', function _test(t, done) {\n\t\t\t// fake a port\n\t\t\tconst client = connect({ port: 4557 })\n\n\t\t\tclient.on('error', (e: any) => {\n\t\t\t\tassert.equal(e.code, 'ECONNREFUSED')\n\t\t\t\tclient.end((err) => done(err))\n\t\t\t})\n\t\t})\n\n\t\tit('should have different client ids', function _test(t, done) {\n\t\t\t// bug identified in this test: the client.end callback is invoked twice, once when the `end`\n\t\t\t// method completes closing the stores and invokes the callback, and another time when the\n\t\t\t// stream is closed. When the stream is closed, for some reason the closeStores method is called\n\t\t\t// a second time.\n\t\t\tconst client1 = connect()\n\t\t\tconst client2 = connect()\n\n\t\t\tassert.notStrictEqual(\n\t\t\t\tclient1.options.clientId,\n\t\t\t\tclient2.options.clientId,\n\t\t\t)\n\t\t\tclient1.end(true, () => {\n\t\t\t\tclient2.end(true, () => {\n\t\t\t\t\tdone()\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n\n\tdescribe('handling offline states', () => {\n\t\tbeforeEach(beforeEachExec)\n\t\tafter(afterExec)\n\n\t\tit('should emit offline event once when the client transitions from connected states to disconnected ones', function _test(t, done) {\n\t\t\tconst client = connect({ reconnectPeriod: 20 })\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.stream.end()\n\t\t\t})\n\n\t\t\tclient.on('offline', () => {\n\t\t\t\tclient.end(true, done)\n\t\t\t})\n\t\t})\n\n\t\tit('should emit offline event once when the client (at first) can NOT connect to servers', function _test(t, done) {\n\t\t\t// fake a port\n\t\t\tconst client = connect({ reconnectPeriod: 20, port: 4557 })\n\n\t\t\tclient.on('error', () => {})\n\n\t\t\tclient.on('offline', () => {\n\t\t\t\tclient.end(true, done)\n\t\t\t})\n\t\t})\n\t})\n\n\tdescribe('topic validations when subscribing', () => {\n\t\tbeforeEach(beforeEachExec)\n\t\tafter(afterExec)\n\n\t\tit('should be ok for well-formated topics', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.subscribe(\n\t\t\t\t[\n\t\t\t\t\t'+',\n\t\t\t\t\t'+/event',\n\t\t\t\t\t'event/+',\n\t\t\t\t\t'#',\n\t\t\t\t\t'event/#',\n\t\t\t\t\t'system/event/+',\n\t\t\t\t\t'system/+/event',\n\t\t\t\t\t'system/registry/event/#',\n\t\t\t\t\t'system/+/event/#',\n\t\t\t\t\t'system/registry/event/new_device',\n\t\t\t\t\t'system/+/+/new_device',\n\t\t\t\t],\n\t\t\t\t(err) => {\n\t\t\t\t\tclient.end(() => {\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\treturn done(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdone()\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t)\n\t\t})\n\n\t\tit('should return an error (via callbacks) for topic #/event', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.subscribe(['#/event', 'event#', 'event+'], (err) => {\n\t\t\t\tclient.end(false, () => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treturn done()\n\t\t\t\t\t}\n\t\t\t\t\tdone(new Error('Validations do NOT work'))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should return an empty array for duplicate subs', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.subscribe('event', (err, granted1) => {\n\t\t\t\tif (err) {\n\t\t\t\t\treturn done(err)\n\t\t\t\t}\n\t\t\t\tclient.subscribe('event', (err2, granted2) => {\n\t\t\t\t\tif (err2) {\n\t\t\t\t\t\treturn done(err2)\n\t\t\t\t\t}\n\t\t\t\t\tassert.isArray(granted2)\n\t\t\t\t\tassert.isEmpty(granted2)\n\t\t\t\t\tclient.end((err3) => done(err3))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should return an error (via callbacks) for topic #/event', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.subscribe('#/event', (err) => {\n\t\t\t\tclient.end(() => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treturn done()\n\t\t\t\t\t}\n\t\t\t\t\tdone(new Error('Validations do NOT work'))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should return an error (via callbacks) for topic event#', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.subscribe('event#', (err) => {\n\t\t\t\tclient.end(() => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treturn done()\n\t\t\t\t\t}\n\t\t\t\t\tdone(new Error('Validations do NOT work'))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should return an error (via callbacks) for topic system/#/event', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.subscribe('system/#/event', (err) => {\n\t\t\t\tclient.end(() => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\treturn done()\n\t\t\t\t\t}\n\t\t\t\t\tdone(new Error('Validations do NOT work'))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should return an error (via callbacks) for empty topic list', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.subscribe([], (subErr) => {\n\t\t\t\tclient.end((endErr) => {\n\t\t\t\t\tif (subErr) {\n\t\t\t\t\t\treturn done(endErr)\n\t\t\t\t\t}\n\t\t\t\t\tdone(new Error('Validations do NOT work'))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should return an error (via callbacks) for topic system/+/#/event', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.subscribe('system/+/#/event', (subErr) => {\n\t\t\t\tclient.end(true, (endErr) => {\n\t\t\t\t\tif (subErr) {\n\t\t\t\t\t\treturn done(endErr)\n\t\t\t\t\t}\n\t\t\t\t\tdone(new Error('Validations do NOT work'))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n\n\tdescribe('offline messages', () => {\n\t\tbeforeEach(beforeEachExec)\n\t\tafter(afterExec)\n\n\t\tit('should queue message until connected', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.publish('test', 'test')\n\t\t\tclient.subscribe('test')\n\t\t\tclient.unsubscribe('test')\n\t\t\tassert.strictEqual(client.queue.length, 3)\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tassert.strictEqual(client.queue.length, 0)\n\t\t\t\tclient.end((err) => done(err))\n\t\t\t})\n\t\t})\n\n\t\tit('should not queue qos 0 messages if queueQoSZero is false', function _test(t, done) {\n\t\t\tconst client = connect({ queueQoSZero: false })\n\n\t\t\tclient.publish('test', 'test', { qos: 0 })\n\t\t\tassert.strictEqual(client.queue.length, 0)\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.end((err) => done(err))\n\t\t\t})\n\t\t})\n\n\t\tit('should queue qos != 0 messages', function _test(t, done) {\n\t\t\tconst client = connect({ queueQoSZero: false })\n\n\t\t\tclient.publish('test', 'test', { qos: 1 })\n\t\t\tclient.publish('test', 'test', { qos: 2 })\n\t\t\tclient.subscribe('test')\n\t\t\tclient.unsubscribe('test')\n\t\t\tassert.strictEqual(client.queue.length, 2)\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.end((err) => done(err))\n\t\t\t})\n\t\t})\n\n\t\tit('should not interrupt messages', function _test(t, done) {\n\t\t\tlet client: mqtt.MqttClient | null = null\n\t\t\tlet publishCount = 0\n\t\t\tconst incomingStore = new mqtt.Store({ clean: false })\n\t\t\tconst outgoingStore = new mqtt.Store({ clean: false })\n\t\t\tconst server2 = serverBuilder(config.protocol, (serverClient) => {\n\t\t\t\tserverClient.on('connect', () => {\n\t\t\t\t\tconst connack =\n\t\t\t\t\t\tversion === 5 ? { reasonCode: 0 } : { returnCode: 0 }\n\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t})\n\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\tif (packet.qos !== 0) {\n\t\t\t\t\t\tserverClient.puback({ messageId: packet.messageId })\n\t\t\t\t\t}\n\t\t\t\t\tswitch (publishCount++) {\n\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\t\t\t'payload1',\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\t\t\t'payload2',\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\t\t\t'payload3',\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 3:\n\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\t\t\t'payload4',\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tclient.end(false, done)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver2.listen(ports.PORTAND50, () => {\n\t\t\t\tclient = connect({\n\t\t\t\t\tport: ports.PORTAND50,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tclean: false,\n\t\t\t\t\tclientId: 'cid1',\n\t\t\t\t\treconnectPeriod: 0,\n\t\t\t\t\tincomingStore,\n\t\t\t\t\toutgoingStore,\n\t\t\t\t\tqueueQoSZero: true,\n\t\t\t\t})\n\t\t\t\tclient.on('packetreceive', (packet) => {\n\t\t\t\t\tif (packet.cmd === 'connack') {\n\t\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\t\tclient.publish('test', 'payload3', { qos: 1 })\n\t\t\t\t\t\t\tclient.publish('test', 'payload4', { qos: 0 })\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tclient.publish('test', 'payload1', { qos: 2 })\n\t\t\t\tclient.publish('test', 'payload2', { qos: 2 })\n\t\t\t})\n\t\t})\n\n\t\tit('should not overtake the messages stored in the level-db-store', function _test(t, done) {\n\t\t\tteardownHelper.add({ executeOnce: true }, async () => {\n\t\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\t\tfs.rm(storePath, { recursive: true }, () => {\n\t\t\t\t\t\tresolve()\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tconst storePath = fs.mkdtempSync('test-store_')\n\t\t\tconst store = levelStore(storePath)\n\t\t\tlet client: mqtt.MqttClient | null = null\n\t\t\tconst incomingStore = store.incoming\n\t\t\tconst outgoingStore = store.outgoing\n\t\t\tlet publishCount = 0\n\t\t\tconst server2 = serverBuilder(config.protocol, (serverClient) => {\n\t\t\t\tserverClient.on('connect', () => {\n\t\t\t\t\tconst connack =\n\t\t\t\t\t\tversion === 5 ? { reasonCode: 0 } : { returnCode: 0 }\n\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t})\n\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\tif (packet.qos !== 0) {\n\t\t\t\t\t\tserverClient.puback({ messageId: packet.messageId })\n\t\t\t\t\t}\n\n\t\t\t\t\tswitch (publishCount++) {\n\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\t\t\t'payload1',\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\t\t\t'payload2',\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\t\t\t'payload3',\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\tclient.end(false, done)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tconst clientOptions = {\n\t\t\t\tport: ports.PORTAND72,\n\t\t\t\thost: 'localhost',\n\t\t\t\tclean: false,\n\t\t\t\tclientId: 'cid1',\n\t\t\t\treconnectPeriod: 0,\n\t\t\t\tincomingStore,\n\t\t\t\toutgoingStore,\n\t\t\t\tqueueQoSZero: true,\n\t\t\t}\n\n\t\t\tserver2.listen(ports.PORTAND72, () => {\n\t\t\t\tclient = connect(clientOptions)\n\n\t\t\t\tclient.once('close', () => {\n\t\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\t\tclient.publish('test', 'payload2', { qos: 1 }, () => {\n\t\t\t\t\t\t\tclient.publish('test', 'payload3', { qos: 1 })\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t\t// reconecting\n\t\t\t\t\tclient.reconnect(clientOptions)\n\t\t\t\t})\n\n\t\t\t\t// publish and close\n\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\tclient.publish('test', 'payload1', {\n\t\t\t\t\t\tqos: 1,\n\t\t\t\t\t\tcbStorePut() {\n\t\t\t\t\t\t\tclient.end(true)\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should call cb if an outgoing QoS 0 message is not sent', function _test(t, done) {\n\t\t\tconst client = connect({ queueQoSZero: false })\n\t\t\tlet called = false\n\n\t\t\tclient.publish('test', 'test', { qos: 0 }, () => {\n\t\t\t\tcalled = true\n\t\t\t})\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tassert.isTrue(called)\n\t\t\t\tclient.end((err) => done(err))\n\t\t\t})\n\t\t})\n\n\t\tit('should delay ending up until all inflight messages are delivered', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tlet subscribeCalled = false\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.subscribe('test', () => {\n\t\t\t\t\tsubscribeCalled = true\n\t\t\t\t})\n\t\t\t\tclient.publish('test', 'test', () => {\n\t\t\t\t\tclient.end(false, () => {\n\t\t\t\t\t\tassert.strictEqual(subscribeCalled, true)\n\t\t\t\t\t\tdone()\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('wait QoS 1 publish messages', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tlet messageReceived = false\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.subscribe('test')\n\t\t\t\tclient.publish('test', 'test', { qos: 1 }, () => {\n\t\t\t\t\tclient.end(false, () => {\n\t\t\t\t\t\tassert.strictEqual(messageReceived, true)\n\t\t\t\t\t\tdone()\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tclient.on('message', () => {\n\t\t\t\t\tmessageReceived = true\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\t\tserverClient.publish(packet)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('does not wait acks when force-closing', function _test(t, done) {\n\t\t\t// non-running broker\n\t\t\tconst client = connect('mqtt://localhost:8993')\n\t\t\tclient.publish('test', 'test', { qos: 1 })\n\t\t\tclient.end(true, done)\n\t\t})\n\n\t\tit('should call cb if store.put fails', function _test(t, done) {\n\t\t\tconst store = new Store()\n\t\t\tstore.put = (packet, cb) => {\n\t\t\t\tprocess.nextTick(cb, new Error('oops there is an error'))\n\t\t\t\treturn store\n\t\t\t}\n\t\t\tconst client = connect({\n\t\t\t\tincomingStore: store,\n\t\t\t\toutgoingStore: store,\n\t\t\t})\n\t\t\tclient.publish('test', 'test', { qos: 2 }, (err) => {\n\t\t\t\tif (err) {\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t})\n\n\tdescribe('publishing', () => {\n\t\tbeforeEach(beforeEachExec)\n\t\tafter(afterExec)\n\n\t\tit('should publish a message (offline)', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst payload = 'test'\n\t\t\tconst topic = 'test'\n\t\t\t// don't wait on connect to send publish\n\t\t\tclient.publish(topic, payload)\n\n\t\t\tserver.on('client', onClient)\n\n\t\t\tfunction onClient(serverClient) {\n\t\t\t\tserverClient.once('connect', () => {\n\t\t\t\t\tserver.removeListener('client', onClient)\n\t\t\t\t})\n\n\t\t\t\tserverClient.once('publish', (packet) => {\n\t\t\t\t\tassert.strictEqual(packet.topic, topic)\n\t\t\t\t\tassert.strictEqual(packet.payload.toString(), payload)\n\t\t\t\t\tassert.strictEqual(packet.qos, 0)\n\t\t\t\t\tassert.strictEqual(packet.retain, false)\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\n\t\tit('should publish a message (online)', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst payload = 'test'\n\t\t\tconst topic = 'test'\n\t\t\t// block on connect before sending publish\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.publish(topic, payload)\n\t\t\t})\n\n\t\t\tserver.on('client', onClient)\n\n\t\t\tfunction onClient(serverClient) {\n\t\t\t\tserverClient.once('connect', () => {\n\t\t\t\t\tserver.removeListener('client', onClient)\n\t\t\t\t})\n\n\t\t\t\tserverClient.once('publish', (packet) => {\n\t\t\t\t\tassert.strictEqual(packet.topic, topic)\n\t\t\t\t\tassert.strictEqual(packet.payload.toString(), payload)\n\t\t\t\t\tassert.strictEqual(packet.qos, 0)\n\t\t\t\t\tassert.strictEqual(packet.retain, false)\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\n\t\tit('should publish a message (retain, offline)', function _test(t, done) {\n\t\t\tconst client = connect({ queueQoSZero: true })\n\t\t\tconst payload = 'test'\n\t\t\tconst topic = 'test'\n\t\t\tlet called = false\n\n\t\t\tclient.publish(topic, payload, { retain: true }, () => {\n\t\t\t\tcalled = true\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('publish', (packet) => {\n\t\t\t\t\tassert.strictEqual(packet.topic, topic)\n\t\t\t\t\tassert.strictEqual(packet.payload.toString(), payload)\n\t\t\t\t\tassert.strictEqual(packet.qos, 0)\n\t\t\t\t\tassert.strictEqual(packet.retain, true)\n\t\t\t\t\tassert.strictEqual(called, true)\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should emit a packetsend event', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst payload = 'test_payload'\n\t\t\tconst topic = 'testTopic'\n\n\t\t\tclient.on('packetsend', (packet) => {\n\t\t\t\tif (packet.cmd === 'publish') {\n\t\t\t\t\tassert.strictEqual(packet.topic, topic)\n\t\t\t\t\tassert.strictEqual(packet.payload.toString(), payload)\n\t\t\t\t\tassert.strictEqual(packet.qos, 0)\n\t\t\t\t\tassert.strictEqual(packet.retain, false)\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t} else {\n\t\t\t\t\tdone(new Error('packet.cmd was not publish!'))\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tclient.publish(topic, payload)\n\t\t})\n\n\t\tit('should accept options', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst payload = 'test'\n\t\t\tconst topic = 'test'\n\t\t\tconst opts: IClientPublishOptions = {\n\t\t\t\tretain: true,\n\t\t\t\tqos: 1,\n\t\t\t}\n\t\t\tlet received = false\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.publish(topic, payload, opts, (err) => {\n\t\t\t\t\tassert(received)\n\t\t\t\t\tclient.end(() => {\n\t\t\t\t\t\tdone(err)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('publish', (packet) => {\n\t\t\t\t\tassert.strictEqual(packet.topic, topic)\n\t\t\t\t\tassert.strictEqual(packet.payload.toString(), payload)\n\t\t\t\t\tassert.strictEqual(packet.qos, opts.qos, 'incorrect qos')\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tpacket.retain,\n\t\t\t\t\t\topts.retain,\n\t\t\t\t\t\t'incorrect ret',\n\t\t\t\t\t)\n\t\t\t\t\tassert.strictEqual(packet.dup, false, 'incorrect dup')\n\t\t\t\t\treceived = true\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should publish with the default options for an empty parameter', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst payload = 'test'\n\t\t\tconst topic = 'test'\n\t\t\tconst defaultOpts = { qos: 0, retain: false, dup: false }\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.publish(topic, payload, {})\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('publish', (packet) => {\n\t\t\t\t\tassert.strictEqual(packet.topic, topic)\n\t\t\t\t\tassert.strictEqual(packet.payload.toString(), payload)\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tpacket.qos,\n\t\t\t\t\t\tdefaultOpts.qos,\n\t\t\t\t\t\t'incorrect qos',\n\t\t\t\t\t)\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tpacket.retain,\n\t\t\t\t\t\tdefaultOpts.retain,\n\t\t\t\t\t\t'incorrect ret',\n\t\t\t\t\t)\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tpacket.dup,\n\t\t\t\t\t\tdefaultOpts.dup,\n\t\t\t\t\t\t'incorrect dup',\n\t\t\t\t\t)\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should mark a message as duplicate when \"dup\" option is set', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst payload = 'duplicated-test'\n\t\t\tconst topic = 'test'\n\t\t\tconst opts: IClientPublishOptions = {\n\t\t\t\tretain: true,\n\t\t\t\tqos: 1,\n\t\t\t\tdup: true,\n\t\t\t}\n\t\t\tlet received = false\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.publish(topic, payload, opts, (err) => {\n\t\t\t\t\tassert(received)\n\t\t\t\t\tclient.end(() => {\n\t\t\t\t\t\tdone(err)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('publish', (packet) => {\n\t\t\t\t\tassert.strictEqual(packet.topic, topic)\n\t\t\t\t\tassert.strictEqual(packet.payload.toString(), payload)\n\t\t\t\t\tassert.strictEqual(packet.qos, opts.qos, 'incorrect qos')\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tpacket.retain,\n\t\t\t\t\t\topts.retain,\n\t\t\t\t\t\t'incorrect ret',\n\t\t\t\t\t)\n\t\t\t\t\tassert.strictEqual(packet.dup, opts.dup, 'incorrect dup')\n\t\t\t\t\treceived = true\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should fire a callback (qos 0)', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\t// callback args can be typed\n\t\t\t\tclient.publish('a', 'b', (_, packet?: mqtt.Packet) => {\n\t\t\t\t\tassert.isUndefined(packet)\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should fire a callback (qos 1)', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst opts: IClientPublishOptions = { qos: 1 }\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.publish('a', 'b', opts, (_, packet?: mqtt.Packet) => {\n\t\t\t\t\tassert.exists(packet)\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should fire a callback (qos 1) on error', function _test(t, done) {\n\t\t\t// 145 = Packet Identifier in use\n\t\t\tconst pubackReasonCode = 145\n\t\t\tconst pubOpts: IClientPublishOptions = { qos: 1 }\n\t\t\tlet client: mqtt.MqttClient | null = null\n\n\t\t\tconst server2 = serverBuilder(config.protocol, (serverClient) => {\n\t\t\t\tserverClient.on('connect', () => {\n\t\t\t\t\tconst connack =\n\t\t\t\t\t\tversion === 5 ? { reasonCode: 0 } : { returnCode: 0 }\n\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t})\n\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\tif (packet.qos === 1) {\n\t\t\t\t\t\tif (version === 5) {\n\t\t\t\t\t\t\tserverClient.puback({\n\t\t\t\t\t\t\t\tmessageId: packet.messageId,\n\t\t\t\t\t\t\t\treasonCode: pubackReasonCode,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tserverClient.puback({ messageId: packet.messageId })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver2.listen(ports.PORTAND72, () => {\n\t\t\t\tclient = connect({\n\t\t\t\t\tport: ports.PORTAND72,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tclean: true,\n\t\t\t\t\tclientId: 'cid1',\n\t\t\t\t\treconnectPeriod: 0,\n\t\t\t\t})\n\n\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\tclient.publish(\n\t\t\t\t\t\t'a',\n\t\t\t\t\t\t'b',\n\t\t\t\t\t\tpubOpts,\n\t\t\t\t\t\t(err, packet?: mqtt.Packet) => {\n\t\t\t\t\t\t\tassert.exists(packet)\n\t\t\t\t\t\t\tif (version === 5) {\n\t\t\t\t\t\t\t\tif (err instanceof ErrorWithReasonCode) {\n\t\t\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\t\t\terr.code,\n\t\t\t\t\t\t\t\t\t\tpubackReasonCode,\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tassert.instanceOf(err, ErrorWithReasonCode)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tassert.ifError(err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdone()\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should fire a callback (qos 2)', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst opts: IClientPublishOptions = { qos: 2 }\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.publish('a', 'b', opts, (_, packet?: mqtt.Packet) => {\n\t\t\t\t\tassert.exists(packet)\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should fire a callback (qos 2) on error', function _test(t, done) {\n\t\t\t// 145 = Packet Identifier in use\n\t\t\tconst pubrecReasonCode = 145\n\t\t\tconst pubOpts: IClientPublishOptions = { qos: 2 }\n\t\t\tlet client: mqtt.MqttClient | null = null\n\t\t\tconst server2 = serverBuilder(config.protocol, (serverClient) => {\n\t\t\t\tserverClient.on('connect', () => {\n\t\t\t\t\tconst connack =\n\t\t\t\t\t\tversion === 5 ? { reasonCode: 0 } : { returnCode: 0 }\n\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t})\n\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\tif (packet.qos === 2) {\n\t\t\t\t\t\tif (version === 5) {\n\t\t\t\t\t\t\tserverClient.pubrec({\n\t\t\t\t\t\t\t\tmessageId: packet.messageId,\n\t\t\t\t\t\t\t\treasonCode: pubrecReasonCode,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tserverClient.pubrec({ messageId: packet.messageId })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tserverClient.on('pubrel', (packet) => {\n\t\t\t\t\tif (!serverClient.writable) return false\n\t\t\t\t\tserverClient.pubcomp(packet)\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver2.listen(ports.PORTAND103, () => {\n\t\t\t\tclient = connect({\n\t\t\t\t\tport: ports.PORTAND103,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tclean: true,\n\t\t\t\t\tclientId: 'cid1',\n\t\t\t\t\treconnectPeriod: 0,\n\t\t\t\t})\n\n\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\tclient.publish(\n\t\t\t\t\t\t'a',\n\t\t\t\t\t\t'b',\n\t\t\t\t\t\tpubOpts,\n\t\t\t\t\t\t(err, packet?: mqtt.Packet) => {\n\t\t\t\t\t\t\tassert.exists(packet)\n\t\t\t\t\t\t\tif (version === 5) {\n\t\t\t\t\t\t\t\tif (err instanceof ErrorWithReasonCode) {\n\t\t\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\t\t\terr.code,\n\t\t\t\t\t\t\t\t\t\tpubrecReasonCode,\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tassert.instanceOf(err, ErrorWithReasonCode)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tassert.ifError(err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdone()\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should support UTF-8 characters in topic', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.publish('中国', 'hello', () => {\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should support UTF-8 characters in payload', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.publish('hello', '中国', () => {\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should publish 10 QoS 2 and receive them', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tlet countSent = 0\n\t\t\tlet countReceived = 0\n\n\t\t\tfunction publishNext() {\n\t\t\t\tclient.publish('test', 'test', { qos: 2 }, (err) => {\n\t\t\t\t\tassert.ifError(err)\n\t\t\t\t\tcountSent++\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.subscribe('test', (err) => {\n\t\t\t\t\tassert.ifError(err)\n\t\t\t\t\tpublishNext()\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tclient.on('message', () => {\n\t\t\t\tcountReceived++\n\t\t\t\tif (countSent >= 10 && countReceived >= 10) {\n\t\t\t\t\tclient.end(done)\n\t\t\t\t} else {\n\t\t\t\t\tpublishNext()\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.on('offline', () => {\n\t\t\t\t\tclient.end()\n\t\t\t\t\tdone('error went offline... didnt see this happen')\n\t\t\t\t})\n\n\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\t\tserverClient.publish(packet)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tfunction testQosHandleMessage(qos, done) {\n\t\t\tteardownHelper.add({ executeOnce: true, order: 1 }, () => {\n\t\t\t\tif (clock) {\n\t\t\t\t\tclock.restore()\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst clock = sinon.useFakeTimers({\n\t\t\t\t...fakeTimersOptions,\n\t\t\t\ttoFake: ['setTimeout'],\n\t\t\t})\n\n\t\t\tconst client = connect()\n\n\t\t\tlet messageEventCount = 0\n\t\t\tlet handleMessageCount = 0\n\n\t\t\tclient.handleMessage = (packet, callback) => {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\thandleMessageCount++\n\t\t\t\t\t// next message event should not emit until handleMessage completes\n\t\t\t\t\tassert.strictEqual(handleMessageCount, messageEventCount)\n\t\t\t\t\tif (handleMessageCount === 10) {\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t\t}, 10)\n\n\t\t\t\t\t\tclock.tick(10)\n\t\t\t\t\t}\n\t\t\t\t\tcallback()\n\t\t\t\t}, 10)\n\n\t\t\t\tclock.tick(10)\n\t\t\t}\n\n\t\t\tclient.on('message', (topic, message, packet) => {\n\t\t\t\tmessageEventCount++\n\t\t\t})\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.subscribe('test')\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.on('offline', () => {\n\t\t\t\t\tclient.end(true, () => {\n\t\t\t\t\t\tdone('error went offline... didnt see this happen')\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\tfor (let i = 0; i < 10; i++) {\n\t\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\t\tmessageId: i,\n\t\t\t\t\t\t\ttopic: 'test',\n\t\t\t\t\t\t\tpayload: `test${i}`,\n\t\t\t\t\t\t\tqos,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\n\t\tconst qosTests = [0, 1, 2]\n\t\tqosTests.forEach((qos) => {\n\t\t\tit(`should publish 10 QoS ${qos} and receive them only when \\`handleMessage\\` finishes`, function _test(t, done) {\n\t\t\t\ttestQosHandleMessage(qos, done)\n\t\t\t})\n\t\t})\n\n\t\tit('should not send a `puback` if the execution of `handleMessage` fails for messages with QoS `1`', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.handleMessage = (packet, callback) => {\n\t\t\t\tcallback(new Error('Error thrown by the application'))\n\t\t\t}\n\n\t\t\tconst sendSpy = sinon.spy()\n\n\t\t\tclient['_sendPacket'] = sendSpy\n\n\t\t\thandlePublish(\n\t\t\t\tclient,\n\t\t\t\t{\n\t\t\t\t\tcmd: 'publish',\n\t\t\t\t\tmessageId: Math.floor(65535 * Math.random()),\n\t\t\t\t\ttopic: 'test',\n\t\t\t\t\tpayload: 'test',\n\t\t\t\t\tqos: 1,\n\t\t\t\t\tdup: false,\n\t\t\t\t\tretain: false,\n\t\t\t\t},\n\t\t\t\t(err) => {\n\t\t\t\t\tassert.exists(err)\n\t\t\t\t},\n\t\t\t)\n\n\t\t\tassert.strictEqual(sendSpy.callCount, 0)\n\t\t\tclient.end()\n\t\t\tclient.on('connect', () => {\n\t\t\t\tdone()\n\t\t\t})\n\t\t})\n\n\t\tit(\n\t\t\t'should silently ignore errors thrown by `handleMessage` and return when no callback is passed ' +\n\t\t\t\t'into `handlePublish` method',\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst client = connect()\n\n\t\t\t\tclient.handleMessage = (packet, callback) => {\n\t\t\t\t\tcallback(new Error('Error thrown by the application'))\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\thandlePublish(client, {\n\t\t\t\t\t\tcmd: 'publish',\n\t\t\t\t\t\tmessageId: Math.floor(65535 * Math.random()),\n\t\t\t\t\t\ttopic: 'test',\n\t\t\t\t\t\tpayload: 'test',\n\t\t\t\t\t\tqos: 1,\n\t\t\t\t\t\tdup: false,\n\t\t\t\t\t\tretain: false,\n\t\t\t\t\t})\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t} catch (err) {\n\t\t\t\t\tclient.end(true, () => {\n\t\t\t\t\t\tdone(err)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t},\n\t\t)\n\n\t\tit('should handle error with async incoming store in QoS 1 `handlePublish` method', function _test(t, done) {\n\t\t\tclass AsyncStore extends Store {\n\t\t\t\tput(packet, cb) {\n\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\tcb(null, 'Error')\n\t\t\t\t\t})\n\n\t\t\t\t\treturn this\n\t\t\t\t}\n\n\t\t\t\tclose(cb) {\n\t\t\t\t\tcb()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst store = new AsyncStore()\n\t\t\tconst client = connect({ incomingStore: store })\n\n\t\t\thandlePublish(\n\t\t\t\tclient,\n\t\t\t\t{\n\t\t\t\t\tcmd: 'publish',\n\t\t\t\t\tmessageId: 1,\n\t\t\t\t\ttopic: 'test',\n\t\t\t\t\tpayload: 'test',\n\t\t\t\t\tqos: 1,\n\t\t\t\t\tdup: false,\n\t\t\t\t\tretain: false,\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t},\n\t\t\t)\n\t\t})\n\n\t\tit('should handle error with async incoming store in QoS 2 `handlePublish` method', function _test(t, done) {\n\t\t\tclass AsyncStore extends Store {\n\t\t\t\tput(packet, cb) {\n\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\tcb(null, 'Error')\n\t\t\t\t\t})\n\n\t\t\t\t\treturn this\n\t\t\t\t}\n\n\t\t\t\tdel(packet, cb) {\n\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\tcb(new Error('Error'))\n\t\t\t\t\t})\n\n\t\t\t\t\treturn this\n\t\t\t\t}\n\n\t\t\t\tget(packet, cb) {\n\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\tcb(null, { cmd: 'publish' })\n\t\t\t\t\t})\n\t\t\t\t\treturn this\n\t\t\t\t}\n\n\t\t\t\tclose(cb) {\n\t\t\t\t\tcb()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst store = new AsyncStore()\n\t\t\tconst client = connect({ incomingStore: store })\n\n\t\t\thandlePublish(\n\t\t\t\tclient,\n\t\t\t\t{\n\t\t\t\t\tcmd: 'publish',\n\t\t\t\t\tdup: false,\n\t\t\t\t\tretain: false,\n\t\t\t\t\tmessageId: 1,\n\t\t\t\t\ttopic: 'test',\n\t\t\t\t\tpayload: 'test',\n\t\t\t\t\tqos: 2,\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t},\n\t\t\t)\n\t\t})\n\n\t\tit('should handle error with async incoming store in QoS 2 `handlePubrel` method', function _test(t, done) {\n\t\t\tclass AsyncStore extends Store {\n\t\t\t\tput(packet, cb) {\n\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\tcb(null, 'Error')\n\t\t\t\t\t})\n\n\t\t\t\t\treturn this\n\t\t\t\t}\n\n\t\t\t\tdel(packet, cb) {\n\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\tcb(new Error('Error'))\n\t\t\t\t\t})\n\n\t\t\t\t\treturn this\n\t\t\t\t}\n\n\t\t\t\tget(packet, cb) {\n\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\tcb(null, { cmd: 'publish' })\n\t\t\t\t\t})\n\n\t\t\t\t\treturn this\n\t\t\t\t}\n\n\t\t\t\tclose(cb) {\n\t\t\t\t\tcb()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst store = new AsyncStore()\n\t\t\tconst client = connect({ incomingStore: store })\n\n\t\t\thandlePubrel(\n\t\t\t\tclient,\n\t\t\t\t{\n\t\t\t\t\tcmd: 'pubrel',\n\t\t\t\t\tmessageId: 1,\n\t\t\t\t\t// qos: 2,\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tclient.end(true, (err) => done(err))\n\t\t\t\t},\n\t\t\t)\n\t\t})\n\n\t\tit('should handle success with async incoming store in QoS 2 `handlePubrel` method', function _test(t, done) {\n\t\t\tlet delComplete = false\n\t\t\tclass AsyncStore extends Store {\n\t\t\t\tput(packet, cb) {\n\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\tcb(null, 'Error')\n\t\t\t\t\t})\n\n\t\t\t\t\treturn this\n\t\t\t\t}\n\n\t\t\t\tdel(packet, cb) {\n\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\tdelComplete = true\n\t\t\t\t\t\tcb(null)\n\t\t\t\t\t})\n\n\t\t\t\t\treturn this\n\t\t\t\t}\n\n\t\t\t\tget(packet, cb) {\n\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\tcb(null, { cmd: 'publish' })\n\t\t\t\t\t})\n\t\t\t\t\treturn this\n\t\t\t\t}\n\n\t\t\t\tclose(cb) {\n\t\t\t\t\tcb()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst store = new AsyncStore()\n\t\t\tconst client = connect({ incomingStore: store })\n\n\t\t\thandlePubrel(\n\t\t\t\tclient,\n\t\t\t\t{\n\t\t\t\t\tcmd: 'pubrel',\n\t\t\t\t\tmessageId: 1,\n\t\t\t\t\t// qos: 2,\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tassert.isTrue(delComplete)\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t},\n\t\t\t)\n\t\t})\n\n\t\tit('should not send a `pubcomp` if the execution of `handleMessage` fails for messages with QoS `2`', function _test(t, done) {\n\t\t\tconst store = new Store()\n\t\t\tconst client = connect({ incomingStore: store })\n\n\t\t\tconst messageId = Math.floor(65535 * Math.random())\n\t\t\tconst topic = 'testTopic'\n\t\t\tconst payload = 'testPayload'\n\t\t\tconst qos = 2\n\n\t\t\tclient.handleMessage = (packet, callback) => {\n\t\t\t\tcallback(new Error('Error thrown by the application'))\n\t\t\t}\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(topic, { qos: 2 })\n\n\t\t\t\tstore.put(\n\t\t\t\t\t{\n\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\ttopic,\n\t\t\t\t\t\tpayload,\n\t\t\t\t\t\tqos,\n\t\t\t\t\t\tcmd: 'publish',\n\t\t\t\t\t\tdup: false,\n\t\t\t\t\t\tretain: false,\n\t\t\t\t\t},\n\t\t\t\t\t() => {\n\t\t\t\t\t\tconst spy = sinon.spy()\n\t\t\t\t\t\t// cleans up the client\n\t\t\t\t\t\tclient['_sendPacket'] = spy\n\t\t\t\t\t\thandlePubrel(\n\t\t\t\t\t\t\tclient,\n\t\t\t\t\t\t\t{ cmd: 'pubrel', messageId },\n\t\t\t\t\t\t\t(err) => {\n\t\t\t\t\t\t\t\tassert.exists(err)\n\t\t\t\t\t\t\t\tassert.strictEqual(spy.callCount, 0)\n\t\t\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t)\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t})\n\t\t})\n\n\t\tit(\n\t\t\t'should silently ignore errors thrown by `handleMessage` and return when no callback is passed ' +\n\t\t\t\t'into `handlePubrel` method',\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst store = new Store()\n\t\t\t\tconst client = connect({ incomingStore: store })\n\n\t\t\t\tconst messageId = Math.floor(65535 * Math.random())\n\t\t\t\tconst topic = 'test'\n\t\t\t\tconst payload = 'test'\n\t\t\t\tconst qos = 2\n\n\t\t\t\tclient.handleMessage = (packet, callback) => {\n\t\t\t\t\tcallback(new Error('Error thrown by the application'))\n\t\t\t\t}\n\n\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\tclient.subscribe(topic, { qos: 2 })\n\n\t\t\t\t\tstore.put(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\t\ttopic,\n\t\t\t\t\t\t\tpayload,\n\t\t\t\t\t\t\tqos,\n\t\t\t\t\t\t\tcmd: 'publish',\n\t\t\t\t\t\t\tdup: false,\n\t\t\t\t\t\t\tretain: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\thandlePubrel(client, {\n\t\t\t\t\t\t\t\t\tcmd: 'pubrel',\n\t\t\t\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\tclient.end(true, () => {\n\t\t\t\t\t\t\t\t\tdone(err)\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\n\t\tit('should keep message order', function _test(t, done) {\n\t\t\tlet publishCount = 0\n\t\t\tlet reconnect = false\n\t\t\tlet client: mqtt.MqttClient\n\t\t\tconst incomingStore = new Store({ clean: false })\n\t\t\tconst outgoingStore = new Store({ clean: false })\n\t\t\tconst server2 = serverBuilder(config.protocol, (serverClient) => {\n\t\t\t\t// errors are not interesting for this test\n\t\t\t\t// but they might happen on some platforms\n\t\t\t\tserverClient.on('error', () => {})\n\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tconst connack =\n\t\t\t\t\t\tversion === 5 ? { reasonCode: 0 } : { returnCode: 0 }\n\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t})\n\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\tserverClient.puback({ messageId: packet.messageId })\n\t\t\t\t\tif (reconnect) {\n\t\t\t\t\t\tswitch (publishCount++) {\n\t\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\t\t\t\t'payload1',\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\t\t\t\t'payload2',\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\t\t\t\t'payload3',\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tdone()\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver2.listen(ports.PORTAND50, () => {\n\t\t\t\tclient = connect({\n\t\t\t\t\tport: ports.PORTAND50,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tclean: false,\n\t\t\t\t\tclientId: 'cid1',\n\t\t\t\t\treconnectPeriod: 0,\n\t\t\t\t\tincomingStore,\n\t\t\t\t\toutgoingStore,\n\t\t\t\t})\n\n\t\t\t\tclient.on('connect', () => {\n\t\t\t\t\tif (!reconnect) {\n\t\t\t\t\t\tclient.publish('topic', 'payload1', { qos: 1 })\n\t\t\t\t\t\tclient.publish('topic', 'payload2', { qos: 1 })\n\t\t\t\t\t\tclient.end(true)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclient.publish('topic', 'payload3', { qos: 1 })\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tclient.on('close', () => {\n\t\t\t\t\tif (!reconnect) {\n\t\t\t\t\t\tclient.reconnect({\n\t\t\t\t\t\t\t// clean: false, TODO: should we handle this?\n\t\t\t\t\t\t\tincomingStore,\n\t\t\t\t\t\t\toutgoingStore,\n\t\t\t\t\t\t})\n\t\t\t\t\t\treconnect = true\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tfunction testCallbackStorePutByQoS(\n\t\t\tqos: number,\n\t\t\tclean: boolean,\n\t\t\texpected: string[],\n\t\t\tdone: DoneCallback,\n\t\t) {\n\t\t\tconst client = connect({\n\t\t\t\tclean,\n\t\t\t\tclientId: 'testId',\n\t\t\t})\n\n\t\t\tconst callbacks = []\n\n\t\t\tfunction cbStorePut() {\n\t\t\t\tcallbacks.push('storeput')\n\t\t\t}\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.publish(\n\t\t\t\t\t'test',\n\t\t\t\t\t'test',\n\t\t\t\t\t{ qos: qos as QoS, cbStorePut },\n\t\t\t\t\t(err) => {\n\t\t\t\t\t\tif (err) done(err)\n\t\t\t\t\t\tcallbacks.push('publish')\n\t\t\t\t\t\tassert.deepEqual(callbacks, expected)\n\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t})\n\t\t}\n\n\t\tconst callbackStorePutByQoSParameters = [\n\t\t\t{ args: [0, true], expected: ['publish'] },\n\t\t\t{ args: [0, false], expected: ['publish'] },\n\t\t\t{ args: [1, true], expected: ['storeput', 'publish'] },\n\t\t\t{ args: [1, false], expected: ['storeput', 'publish'] },\n\t\t\t{ args: [2, true], expected: ['storeput', 'publish'] },\n\t\t\t{ args: [2, false], expected: ['storeput', 'publish'] },\n\t\t]\n\n\t\tcallbackStorePutByQoSParameters.forEach((test) => {\n\t\t\tif (test.args[0] === 0) {\n\t\t\t\t// QoS 0\n\t\t\t\tit(`should not call cbStorePut when publishing message with QoS \\`${test.args[0]}\\` and clean \\`${test.args[1]}\\``, function _test(t, done) {\n\t\t\t\t\ttestCallbackStorePutByQoS(\n\t\t\t\t\t\ttest.args[0] as number,\n\t\t\t\t\t\ttest.args[1] as boolean,\n\t\t\t\t\t\ttest.expected,\n\t\t\t\t\t\tdone,\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\t// QoS 1 and 2\n\t\t\t\tit(`should call cbStorePut before publish completes when publishing message with QoS \\`${test.args[0]}\\` and clean \\`${test.args[1]}\\``, function _test(t, done) {\n\t\t\t\t\ttestCallbackStorePutByQoS(\n\t\t\t\t\t\ttest.args[0] as number,\n\t\t\t\t\t\ttest.args[1] as boolean,\n\t\t\t\t\t\ttest.expected,\n\t\t\t\t\t\tdone,\n\t\t\t\t\t)\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t})\n\n\tdescribe('unsubscribing', () => {\n\t\tbeforeEach(beforeEachExec)\n\t\tafter(afterExec)\n\n\t\tit('should send an unsubscribe packet (offline)', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tlet received = false\n\n\t\t\tclient.unsubscribe('test', (err) => {\n\t\t\t\tassert.ifError(err)\n\t\t\t\tassert(received)\n\t\t\t\tclient.end(done)\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('unsubscribe', (packet) => {\n\t\t\t\t\tassert.include(packet.unsubscriptions, 'test')\n\t\t\t\t\treceived = true\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should send an unsubscribe packet', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst topic = 'topic'\n\t\t\tlet received = false\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.unsubscribe(topic, (err) => {\n\t\t\t\t\tassert.ifError(err)\n\t\t\t\t\tassert(received)\n\t\t\t\t\tclient.end(done)\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('unsubscribe', (packet) => {\n\t\t\t\t\tassert.include(packet.unsubscriptions, topic)\n\t\t\t\t\treceived = true\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should emit a packetsend event', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst testTopic = 'testTopic'\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(testTopic)\n\t\t\t})\n\n\t\t\tclient.on('packetsend', (packet) => {\n\t\t\t\tif (packet.cmd === 'subscribe') {\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tit('should emit a packetreceive event', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst testTopic = 'testTopic'\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(testTopic)\n\t\t\t})\n\n\t\t\tclient.on('packetreceive', (packet) => {\n\t\t\t\tif (packet.cmd === 'suback') {\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tit('should accept an array of unsubs', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst topics = ['topic1', 'topic2']\n\t\t\tlet received = false\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.unsubscribe(topics, (err) => {\n\t\t\t\t\tassert.ifError(err)\n\t\t\t\t\tassert(received)\n\t\t\t\t\tclient.end(done)\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('unsubscribe', (packet) => {\n\t\t\t\t\tassert.deepStrictEqual(packet.unsubscriptions, topics)\n\t\t\t\t\treceived = true\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should fire a callback on unsuback', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst topic = 'topic'\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\t// callback args can be typed\n\t\t\t\tclient.unsubscribe(topic, (_, packet?: mqtt.Packet) => {\n\t\t\t\t\tassert.isDefined(packet)\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('unsubscribe', (packet) => {\n\t\t\t\t\tserverClient.unsuback(packet)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should unsubscribe from a chinese topic', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst topic = '中国'\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.unsubscribe(topic, () => {\n\t\t\t\t\tclient.end((err) => {\n\t\t\t\t\t\tdone(err)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('unsubscribe', (packet) => {\n\t\t\t\t\tassert.include(packet.unsubscriptions, topic)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n\n\tdescribe('keepalive', () => {\n\t\tlet clock: sinon.SinonFakeTimers\n\n\t\tbeforeEach(async () => {\n\t\t\tawait beforeEachExec()\n\t\t\tclock = sinon.useFakeTimers(fakeTimersOptions)\n\t\t})\n\n\t\tafterEach(() => {\n\t\t\tclock.restore()\n\t\t})\n\n\t\tafter(afterExec)\n\n\t\tit('should send ping at keepalive interval', function _test(t, done) {\n\t\t\tconst interval = 3000\n\t\t\tconst client = connect({ keepalive: interval / 1000 })\n\n\t\t\tconst spy = sinon.spy(client, 'sendPing')\n\n\t\t\tclient.on('error', (err) => {\n\t\t\t\tclient.end(true, () => {\n\t\t\t\t\tdone(err)\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tlet pingReceived = 0\n\n\t\t\tclient.on('packetreceive', (packet) => {\n\t\t\t\tif (packet.cmd === 'pingresp') {\n\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\tpingReceived++\n\t\t\t\t\t\tassert.strictEqual(spy.callCount, pingReceived)\n\n\t\t\t\t\t\tif (pingReceived === 3) {\n\t\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tclock.tick(interval)\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\tclock.tick(1)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclock.tick(interval)\n\t\t\t})\n\t\t})\n\n\t\tit('should not shift ping on publish', function _test(t, done) {\n\t\t\tconst intervalMs = 3000\n\n\t\t\tconst client = connect({ keepalive: intervalMs / 1000 })\n\n\t\t\tconst spy = sinon.spy(client, '_reschedulePing' as any)\n\n\t\t\tlet serverClient\n\n\t\t\tfunction fakePub() {\n\t\t\t\tclient.publish('foo', 'bar')\n\t\t\t\tserverClient.publish({\n\t\t\t\t\ttopic: 'foo',\n\t\t\t\t\tpayload: 'bar',\n\t\t\t\t})\n\t\t\t\tclock.tick(1)\n\t\t\t}\n\n\t\t\tserver.once('client', (_serverClient) => {\n\t\t\t\t// send fake packet to client\n\t\t\t\tserverClient = _serverClient\n\n\t\t\t\tserverClient.on('publish', () => {\n\t\t\t\t\t// needed to trigger the setImmediate inside server publish listener and send suback\n\t\t\t\t\tclock.tick(1)\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tlet received = 0\n\n\t\t\tclient.on('packetreceive', (packet) => {\n\t\t\t\tif (packet.cmd === 'publish') {\n\t\t\t\t\tclock.tick(intervalMs)\n\t\t\t\t\treceived++\n\t\t\t\t\tassert.strictEqual(spy.callCount, 0)\n\t\t\t\t\tif (received === 2) {\n\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tfakePub()\n\t\t\t\tfakePub()\n\t\t\t})\n\t\t})\n\n\t\tconst reschedulePing = (reschedulePings: boolean) => {\n\t\t\tit(\n\t\t\t\t`should ${\n\t\t\t\t\t!reschedulePings ? 'not ' : ''\n\t\t\t\t}reschedule pings if publishing at a higher rate than keepalive and reschedulePings===${reschedulePings}`,\n\t\t\t\t{\n\t\t\t\t\ttimeout: 4000,\n\t\t\t\t},\n\t\t\t\tfunction _test(t, done) {\n\t\t\t\t\tclock.restore()\n\n\t\t\t\t\tteardownHelper.add(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\texecuteOnce: true,\n\t\t\t\t\t\t\torder: 1,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\tif (localClock) {\n\t\t\t\t\t\t\t\tlocalClock.restore()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\n\t\t\t\t\tconst localClock = sinon.useFakeTimers({\n\t\t\t\t\t\t...fakeTimersOptions,\n\t\t\t\t\t\ttoFake: ['setTimeout'],\n\t\t\t\t\t})\n\t\t\t\t\tconst intervalMs = 3000\n\t\t\t\t\tconst client = connect({\n\t\t\t\t\t\tkeepalive: intervalMs / 1000,\n\t\t\t\t\t\treschedulePings,\n\t\t\t\t\t})\n\n\t\t\t\t\tconst spyReschedule = sinon.spy(\n\t\t\t\t\t\tclient,\n\t\t\t\t\t\t'_reschedulePing' as any,\n\t\t\t\t\t)\n\n\t\t\t\t\tlet received = 0\n\n\t\t\t\t\tclient.on('packetreceive', (packet) => {\n\t\t\t\t\t\tif (packet.cmd === 'puback') {\n\t\t\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\t\t\tlocalClock.tick(intervalMs)\n\n\t\t\t\t\t\t\t\t++received\n\n\t\t\t\t\t\t\t\tif (received === 2) {\n\t\t\t\t\t\t\t\t\tif (reschedulePings) {\n\t\t\t\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\t\t\t\tspyReschedule.callCount,\n\t\t\t\t\t\t\t\t\t\t\treceived,\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\t\t\t\tspyReschedule.callCount,\n\t\t\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\n\t\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\t\t// reset call count (it's called also on connack)\n\t\t\t\t\t\tspyReschedule.resetHistory()\n\t\t\t\t\t\t// use qos1 so the puback is received (to reschedule ping)\n\t\t\t\t\t\tclient.publish('foo', 'bar', { qos: 1 })\n\t\t\t\t\t\tclient.publish('foo', 'bar', { qos: 1 })\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\n\t\treschedulePing(true)\n\t\treschedulePing(false)\n\n\t\tconst pingresp = (reschedulePings: boolean) => {\n\t\t\tit(`should shift ping on pingresp when reschedulePings===${reschedulePings}`, function _test(t, done) {\n\t\t\t\tconst intervalMs = 3000\n\n\t\t\t\tconst client = connect({\n\t\t\t\t\tkeepalive: intervalMs / 1000,\n\t\t\t\t\treschedulePings,\n\t\t\t\t})\n\n\t\t\t\tconst spy = sinon.spy(client, '_reschedulePing' as any)\n\n\t\t\t\tclient.on('packetreceive', (packet) => {\n\t\t\t\t\tif (packet.cmd === 'pingresp') {\n\t\t\t\t\t\tprocess.nextTick(() => {\n\t\t\t\t\t\t\tassert.strictEqual(spy.callCount, 1)\n\t\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tclient.on('error', (err) => {\n\t\t\t\t\tclient.end(true, () => {\n\t\t\t\t\t\tdone(err)\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\tclock.tick(intervalMs)\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\t\tpingresp(true)\n\t\tpingresp(false)\n\t})\n\n\tdescribe('pinging', () => {\n\t\tbeforeEach(beforeEachExec)\n\t\tafter(afterExec)\n\n\t\tit('should setup keepalive manager', function _test(t, done) {\n\t\t\tconst client = connect({ keepalive: 3 })\n\t\t\tclient.once('connect', () => {\n\t\t\t\tassert.exists(client.keepaliveManager)\n\t\t\t\tclient.end(true, done)\n\t\t\t})\n\t\t})\n\n\t\tit('should not setup keepalive manager if keepalive=0', function _test(t, done) {\n\t\t\tconst client = connect({ keepalive: 0 })\n\t\t\tclient.on('connect', () => {\n\t\t\t\tassert.notExists(client.keepaliveManager)\n\t\t\t\tclient.end(true, done)\n\t\t\t})\n\t\t})\n\n\t\tit(\n\t\t\t'should reconnect on keepalive timeout',\n\t\t\t{\n\t\t\t\ttimeout: 10000,\n\t\t\t},\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst clock = sinon.useFakeTimers(fakeTimersOptions)\n\n\t\t\t\tt.after(() => {\n\t\t\t\t\tclock.restore()\n\t\t\t\t})\n\n\t\t\t\tconst options: IClientOptions = {\n\t\t\t\t\tkeepalive: 60,\n\t\t\t\t\treconnectPeriod: 5000,\n\t\t\t\t}\n\n\t\t\t\tconst client = connect(options)\n\n\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\tclient.once('error', (err) => {\n\t\t\t\t\t\tassert.equal(err.message, 'Keepalive timeout')\n\t\t\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t\t\tclock.tick(100)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\n\t\t\t\t\tclient.once('close', () => {\n\t\t\t\t\t\t// Wait for the reconnect to happen\n\t\t\t\t\t\tclock.tick(client.options.reconnectPeriod)\n\t\t\t\t\t})\n\n\t\t\t\t\tconst timeoutTimestamp =\n\t\t\t\t\t\tclient.keepaliveManager.keepaliveTimeoutTimestamp\n\n\t\t\t\t\tclock.tick(timeoutTimestamp - Date.now())\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\n\t\tit(\n\t\t\t'should not reconnect if pingresp is successful',\n\t\t\t{ timeout: 1000 },\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst clock = sinon.useFakeTimers(fakeTimersOptions)\n\n\t\t\t\tt.after(() => {\n\t\t\t\t\tclock.restore()\n\t\t\t\t})\n\n\t\t\t\tconst client = connect({ keepalive: 10 })\n\t\t\t\tclient.once('close', () => {\n\t\t\t\t\tdone(new Error('Client closed connection'))\n\t\t\t\t})\n\n\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\t// make keepalive check trigger\n\t\t\t\t\t\tconst timeoutTimestamp =\n\t\t\t\t\t\t\tclient.keepaliveManager.keepaliveTimeoutTimestamp\n\n\t\t\t\t\t\tclock.tick(timeoutTimestamp - Date.now())\n\t\t\t\t\t})\n\n\t\t\t\t\tclient.on('packetsend', (packet) => {\n\t\t\t\t\t\tif (packet.cmd === 'pingreq') {\n\t\t\t\t\t\t\tclient.removeAllListeners('close')\n\t\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t\t\tclock.tick(100)\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\n\t\t\t\t\tclock.tick(1)\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\t})\n\n\tdescribe('subscribing', () => {\n\t\tbeforeEach(beforeEachExec)\n\t\tafter(afterExec)\n\n\t\tit('should send a subscribe message (offline)', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.subscribe('test')\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', () => {\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should send a subscribe message', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst topic = 'test'\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(topic)\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', (packet) => {\n\t\t\t\t\tconst result: ISubscriptionRequest = {\n\t\t\t\t\t\ttopic,\n\t\t\t\t\t\tqos: 0,\n\t\t\t\t\t}\n\t\t\t\t\tif (version === 5) {\n\t\t\t\t\t\tresult.nl = false\n\t\t\t\t\t\tresult.rap = false\n\t\t\t\t\t\tresult.rh = 0\n\t\t\t\t\t}\n\t\t\t\t\tassert.include(packet.subscriptions[0], result)\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should emit a packetsend event', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst testTopic = 'testTopic'\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(testTopic)\n\t\t\t})\n\n\t\t\tclient.on('packetsend', (packet) => {\n\t\t\t\tif (packet.cmd === 'subscribe') {\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tit('should emit a packetreceive event', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst testTopic = 'testTopic'\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(testTopic)\n\t\t\t})\n\n\t\t\tclient.on('packetreceive', (packet) => {\n\t\t\t\tif (packet.cmd === 'suback') {\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tit('should accept an array of subscriptions', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst subs = ['test1', 'test2']\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(subs)\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', (packet) => {\n\t\t\t\t\t// i.e. [{topic: 'a', qos: 0}, {topic: 'b', qos: 0}]\n\t\t\t\t\tconst expected = subs.map((i) => {\n\t\t\t\t\t\tconst result: ISubscriptionRequest = {\n\t\t\t\t\t\t\ttopic: i,\n\t\t\t\t\t\t\tqos: 0,\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (version === 5) {\n\t\t\t\t\t\t\tresult.nl = false\n\t\t\t\t\t\t\tresult.rap = false\n\t\t\t\t\t\t\tresult.rh = 0\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn result\n\t\t\t\t\t})\n\n\t\t\t\t\tassert.deepStrictEqual(packet.subscriptions, expected)\n\t\t\t\t\tclient.end(done)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should accept a hash of subscriptions', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst topics: ISubscriptionMap = {\n\t\t\t\ttest1: { qos: 0 },\n\t\t\t\ttest2: { qos: 1 },\n\t\t\t}\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(topics)\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', (packet) => {\n\t\t\t\t\tconst expected = []\n\n\t\t\t\t\tfor (const k in topics) {\n\t\t\t\t\t\tif (Object.prototype.hasOwnProperty.call(topics, k)) {\n\t\t\t\t\t\t\tconst result: ISubscriptionRequest = {\n\t\t\t\t\t\t\t\ttopic: k,\n\t\t\t\t\t\t\t\tqos: topics[k].qos,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (version === 5) {\n\t\t\t\t\t\t\t\tresult.nl = false\n\t\t\t\t\t\t\t\tresult.rap = false\n\t\t\t\t\t\t\t\tresult.rh = 0\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\texpected.push(result)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tassert.deepStrictEqual(packet.subscriptions, expected)\n\t\t\t\t\tclient.end(done)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should accept an options parameter', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst topic = 'test'\n\t\t\tconst opts: IClientSubscribeOptions = { qos: 1 }\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(topic, opts)\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', (packet) => {\n\t\t\t\t\tconst expected: ISubscriptionRequest[] = [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttopic,\n\t\t\t\t\t\t\tqos: 1,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\n\t\t\t\t\tif (version === 5) {\n\t\t\t\t\t\texpected[0].nl = false\n\t\t\t\t\t\texpected[0].rap = false\n\t\t\t\t\t\texpected[0].rh = 0\n\t\t\t\t\t}\n\n\t\t\t\t\tassert.deepStrictEqual(packet.subscriptions, expected)\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should subscribe with the default options for an empty options parameter', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst topic = 'test'\n\t\t\tconst defaultOpts = { qos: 0 }\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(topic, {})\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', (packet) => {\n\t\t\t\t\tconst result: ISubscriptionRequest = {\n\t\t\t\t\t\ttopic,\n\t\t\t\t\t\tqos: defaultOpts.qos as QoS,\n\t\t\t\t\t}\n\t\t\t\t\tif (version === 5) {\n\t\t\t\t\t\tresult.nl = false\n\t\t\t\t\t\tresult.rap = false\n\t\t\t\t\t\tresult.rh = 0\n\t\t\t\t\t}\n\n\t\t\t\t\tassert.include(packet.subscriptions[0], result)\n\t\t\t\t\tclient.end((err) => done(err))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should fire a callback on suback', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst topic = 'test'\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(topic, { qos: 2 }, (err, granted, suback) => {\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\tdone(err)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tassert.exists(granted, 'granted not given')\n\t\t\t\t\t\tconst expectedResult: ISubscriptionRequest = {\n\t\t\t\t\t\t\ttopic: 'test',\n\t\t\t\t\t\t\tqos: 2,\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (version === 5) {\n\t\t\t\t\t\t\texpectedResult.nl = false\n\t\t\t\t\t\t\texpectedResult.rap = false\n\t\t\t\t\t\t\texpectedResult.rh = 0\n\t\t\t\t\t\t\texpectedResult.properties = undefined\n\t\t\t\t\t\t}\n\t\t\t\t\t\tassert.include(granted[0], expectedResult)\n\t\t\t\t\t\tassert.exists(suback, 'suback not given')\n\t\t\t\t\t\tassert.deepStrictEqual(suback.granted, [2])\n\t\t\t\t\t\tclient.end((err2) => done(err2))\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should fire a callback with error if disconnected (options provided)', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst topic = 'test'\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.end(true, () => {\n\t\t\t\t\tclient.subscribe(topic, { qos: 2 }, (err, granted) => {\n\t\t\t\t\t\tassert.notExists(granted, 'granted given')\n\t\t\t\t\t\tassert.exists(err, 'no error given')\n\t\t\t\t\t\tdone()\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should fire a callback with error if disconnected (options not provided)', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst topic = 'test'\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.end(true, () => {\n\t\t\t\t\tclient.subscribe(topic, (err, granted) => {\n\t\t\t\t\t\tassert.notExists(granted, 'granted given')\n\t\t\t\t\t\tassert.exists(err, 'no error given')\n\t\t\t\t\t\tdone()\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should subscribe with a chinese topic', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst topic = '中国'\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(topic)\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', (packet) => {\n\t\t\t\t\tconst result: ISubscriptionRequest = {\n\t\t\t\t\t\ttopic,\n\t\t\t\t\t\tqos: 0,\n\t\t\t\t\t}\n\t\t\t\t\tif (version === 5) {\n\t\t\t\t\t\tresult.nl = false\n\t\t\t\t\t\tresult.rap = false\n\t\t\t\t\t\tresult.rh = 0\n\t\t\t\t\t}\n\t\t\t\t\tassert.include(packet.subscriptions[0], result)\n\t\t\t\t\tclient.end(done)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should send multiple subscribe packets when topic count exceeds batchSize', function _test(t, done) {\n\t\t\tconst client = connect({ subscribeBatchSize: 2 })\n\t\t\tconst subs = ['test1', 'test2', 'test3']\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(subs)\n\t\t\t})\n\n\t\t\tconst spy = sinon.spy()\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.on('subscribe', spy)\n\t\t\t})\n\t\t\tclient.on('end', () => {\n\t\t\t\tassert.strictEqual(spy.callCount, 2)\n\t\t\t\tfor (let i = 0; i < 2; i++) {\n\t\t\t\t\t// i.e. [{topic: 'a', qos: 0}, {topic: 'b', qos: 0}]\n\t\t\t\t\tconst expected = subs\n\t\t\t\t\t\t.slice(i * 2, i * 2 + 2)\n\t\t\t\t\t\t.map((topic) => {\n\t\t\t\t\t\t\tconst result: ISubscriptionRequest = {\n\t\t\t\t\t\t\t\ttopic,\n\t\t\t\t\t\t\t\tqos: 0,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (version === 5) {\n\t\t\t\t\t\t\t\tresult.nl = false\n\t\t\t\t\t\t\t\tresult.rap = false\n\t\t\t\t\t\t\t\tresult.rh = 0\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn result\n\t\t\t\t\t\t})\n\n\t\t\t\t\tassert.deepStrictEqual(\n\t\t\t\t\t\tspy.getCall(i).args[0].subscriptions,\n\t\t\t\t\t\texpected,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tdone()\n\t\t\t})\n\t\t\tsetTimeout(() => {\n\t\t\t\tclient.end()\n\t\t\t}, 300)\n\t\t})\n\t})\n\n\tdescribe('receiving messages', () => {\n\t\tbeforeEach(beforeEachExec)\n\t\tafter(afterExec)\n\n\t\tit('should fire the message event', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst testPacket = {\n\t\t\t\ttopic: 'test',\n\t\t\t\tpayload: 'message',\n\t\t\t\tretain: true,\n\t\t\t\tqos: 1,\n\t\t\t\tmessageId: 5,\n\t\t\t}\n\n\t\t\t//\n\t\t\tclient.subscribe(testPacket.topic)\n\t\t\tclient.once(\n\t\t\t\t'message',\n\t\t\t\t(topic, message, packet: mqtt.IPublishPacket) => {\n\t\t\t\t\tassert.strictEqual(topic, testPacket.topic)\n\t\t\t\t\tassert.strictEqual(message.toString(), testPacket.payload)\n\t\t\t\t\tassert.strictEqual(packet.cmd, 'publish')\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t},\n\t\t\t)\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\tserverClient.publish(testPacket)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should emit a packetreceive event', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst testPacket = {\n\t\t\t\ttopic: 'test',\n\t\t\t\tpayload: 'message',\n\t\t\t\tretain: true,\n\t\t\t\tqos: 1,\n\t\t\t\tmessageId: 5,\n\t\t\t}\n\n\t\t\tclient.subscribe(testPacket.topic)\n\t\t\tclient.on('packetreceive', (packet: mqtt.Packet) => {\n\t\t\t\tif (packet.cmd === 'publish') {\n\t\t\t\t\tassert.strictEqual(packet.qos, 1)\n\t\t\t\t\tassert.strictEqual(packet.topic, testPacket.topic)\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\ttestPacket.payload,\n\t\t\t\t\t)\n\t\t\t\t\tassert.strictEqual(packet.retain, true)\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\tserverClient.publish(testPacket)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should support binary data', function _test(t, done) {\n\t\t\tconst client = connect({ encoding: 'binary' })\n\t\t\tconst testPacket = {\n\t\t\t\ttopic: 'test',\n\t\t\t\tpayload: 'message',\n\t\t\t\tretain: true,\n\t\t\t\tqos: 1,\n\t\t\t\tmessageId: 5,\n\t\t\t}\n\n\t\t\tclient.subscribe(testPacket.topic)\n\t\t\tclient.once('message', (topic, message, packet) => {\n\t\t\t\tassert.strictEqual(topic, testPacket.topic)\n\t\t\t\tassert.instanceOf(message, Buffer)\n\t\t\t\tassert.strictEqual(message.toString(), testPacket.payload)\n\t\t\t\tassert.strictEqual(packet.cmd, 'publish')\n\t\t\t\tclient.end(true, done)\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\tserverClient.publish(testPacket)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should emit a message event (qos=2)', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst testPacket = {\n\t\t\t\ttopic: 'test',\n\t\t\t\tpayload: 'message',\n\t\t\t\tretain: true,\n\t\t\t\tqos: 2,\n\t\t\t\tmessageId: 5,\n\t\t\t}\n\n\t\t\tserver.testPublish = testPacket\n\n\t\t\tclient.subscribe(testPacket.topic)\n\t\t\tclient.once('message', (topic, message, packet) => {\n\t\t\t\tassert.strictEqual(topic, testPacket.topic)\n\t\t\t\tassert.strictEqual(message.toString(), testPacket.payload)\n\t\t\t\tassert.strictEqual(packet.messageId, testPacket.messageId)\n\t\t\t\tassert.strictEqual(packet.qos, testPacket.qos)\n\t\t\t\tclient.end(true, done)\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\tserverClient.publish(testPacket)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should emit a message event (qos=2) - repeated publish', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst testPacket = {\n\t\t\t\ttopic: 'test',\n\t\t\t\tpayload: 'message',\n\t\t\t\tretain: true,\n\t\t\t\tqos: 2,\n\t\t\t\tmessageId: 5,\n\t\t\t}\n\n\t\t\tserver.testPublish = testPacket\n\n\t\t\tconst messageHandler = (topic, message, packet) => {\n\t\t\t\tassert.strictEqual(topic, testPacket.topic)\n\t\t\t\tassert.strictEqual(message.toString(), testPacket.payload)\n\t\t\t\tassert.strictEqual(packet.messageId, testPacket.messageId)\n\t\t\t\tassert.strictEqual(packet.qos, testPacket.qos)\n\n\t\t\t\tassert.strictEqual(spiedMessageHandler.callCount, 1)\n\t\t\t\tclient.end(true, done)\n\t\t\t}\n\n\t\t\tconst spiedMessageHandler = sinon.spy(messageHandler)\n\n\t\t\tclient.subscribe(testPacket.topic)\n\t\t\tclient.on('message', spiedMessageHandler)\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\tserverClient.publish(testPacket)\n\t\t\t\t\t// twice, should be ignored\n\t\t\t\t\tserverClient.publish(testPacket)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should support a chinese topic', function _test(t, done) {\n\t\t\tconst client = connect({ encoding: 'binary' })\n\t\t\tconst testPacket = {\n\t\t\t\ttopic: '国',\n\t\t\t\tpayload: 'message',\n\t\t\t\tretain: true,\n\t\t\t\tqos: 1,\n\t\t\t\tmessageId: 5,\n\t\t\t}\n\n\t\t\tclient.subscribe(testPacket.topic)\n\t\t\tclient.once('message', (topic, message, packet) => {\n\t\t\t\tassert.strictEqual(topic, testPacket.topic)\n\t\t\t\tassert.instanceOf(message, Buffer)\n\t\t\t\tassert.strictEqual(message.toString(), testPacket.payload)\n\t\t\t\tassert.strictEqual(packet.messageId, testPacket.messageId)\n\t\t\t\tassert.strictEqual(packet.qos, testPacket.qos)\n\t\t\t\tclient.end(true, done)\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\tserverClient.publish(testPacket)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n\n\tdescribe('qos handling', () => {\n\t\tbeforeEach(beforeEachExec)\n\t\tafter(afterExec)\n\n\t\tit('should follow qos 0 semantics (trivial)', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst testTopic = 'test'\n\t\t\tconst testMessage = 'message'\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(testTopic, { qos: 0 }, () => {\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', () => {\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\ttopic: testTopic,\n\t\t\t\t\t\tpayload: testMessage,\n\t\t\t\t\t\tqos: 0,\n\t\t\t\t\t\tretain: false,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should follow qos 1 semantics', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst testTopic = 'test'\n\t\t\tconst testMessage = 'message'\n\t\t\tconst mid = 50\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(testTopic, { qos: 1 })\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', () => {\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\ttopic: testTopic,\n\t\t\t\t\t\tpayload: testMessage,\n\t\t\t\t\t\tmessageId: mid,\n\t\t\t\t\t\tqos: 1,\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tserverClient.once('puback', (packet) => {\n\t\t\t\t\tassert.strictEqual(packet.messageId, mid)\n\t\t\t\t\tclient.end(done)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should follow qos 2 semantics', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst testTopic = 'test'\n\t\t\tconst testMessage = 'message'\n\t\t\tconst mid = 253\n\t\t\tlet publishReceived = 0\n\t\t\tlet pubrecReceived = 0\n\t\t\tlet pubrelReceived = 0\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(testTopic, { qos: 2 })\n\t\t\t})\n\n\t\t\tclient.on('packetreceive', (packet) => {\n\t\t\t\tswitch (packet.cmd) {\n\t\t\t\t\tcase 'connack':\n\t\t\t\t\tcase 'suback':\n\t\t\t\t\t\t// expected, but not specifically part of QOS 2 semantics\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'publish':\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\tpubrecReceived,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t'server received pubrec before client sent',\n\t\t\t\t\t\t)\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\tpubrelReceived,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t'server received pubrec before client sent',\n\t\t\t\t\t\t)\n\t\t\t\t\t\tpublishReceived += 1\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'pubrel':\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\tpublishReceived,\n\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t'only 1 publish must be received before a pubrel',\n\t\t\t\t\t\t)\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\tpubrecReceived,\n\t\t\t\t\t\t\t1,\n\t\t\t\t\t\t\t'invalid number of PUBREC messages (not only 1)',\n\t\t\t\t\t\t)\n\t\t\t\t\t\tpubrelReceived += 1\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tfail()\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', () => {\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\ttopic: testTopic,\n\t\t\t\t\t\tpayload: testMessage,\n\t\t\t\t\t\tqos: 2,\n\t\t\t\t\t\tmessageId: mid,\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tserverClient.on('pubrec', () => {\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tpublishReceived,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t'invalid number of PUBLISH messages received',\n\t\t\t\t\t)\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tpubrecReceived,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t'invalid number of PUBREC messages recevied',\n\t\t\t\t\t)\n\t\t\t\t\tpubrecReceived += 1\n\t\t\t\t})\n\n\t\t\t\tserverClient.once('pubcomp', () => {\n\t\t\t\t\tclient.removeAllListeners()\n\t\t\t\t\tserverClient.removeAllListeners()\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tpublishReceived,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t'invalid number of PUBLISH messages',\n\t\t\t\t\t)\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tpubrecReceived,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t'invalid number of PUBREC messages',\n\t\t\t\t\t)\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tpubrelReceived,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t'invalid nubmer of PUBREL messages',\n\t\t\t\t\t)\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should should empty the incoming store after a qos 2 handshake is completed', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tconst testTopic = 'test'\n\t\t\tconst testMessage = 'message'\n\t\t\tconst mid = 253\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(testTopic, { qos: 2 })\n\t\t\t})\n\n\t\t\tclient.on('packetreceive', (packet) => {\n\t\t\t\tif (packet.cmd === 'pubrel') {\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tclient.incomingStore['_inflights'].size,\n\t\t\t\t\t\t1,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', () => {\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\ttopic: testTopic,\n\t\t\t\t\t\tpayload: testMessage,\n\t\t\t\t\t\tqos: 2,\n\t\t\t\t\t\tmessageId: mid,\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tserverClient.once('pubcomp', () => {\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tclient.incomingStore['_inflights'].size,\n\t\t\t\t\t\t0,\n\t\t\t\t\t)\n\t\t\t\t\tclient.removeAllListeners()\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tfunction testMultiplePubrel(shouldSendPubcompFail, done) {\n\t\t\tconst client = connect()\n\t\t\tconst testTopic = 'test'\n\t\t\tconst testMessage = 'message'\n\t\t\tconst mid = 253\n\t\t\tlet pubcompCount = 0\n\t\t\tlet pubrelCount = 0\n\t\t\tlet handleMessageCount = 0\n\t\t\tlet emitMessageCount = 0\n\t\t\tconst origSendPacket = client['_sendPacket']\n\t\t\tlet shouldSendFail\n\n\t\t\tclient.handleMessage = (packet, callback) => {\n\t\t\t\thandleMessageCount++\n\t\t\t\tcallback()\n\t\t\t}\n\n\t\t\tclient.on('message', () => {\n\t\t\t\temitMessageCount++\n\t\t\t})\n\n\t\t\tclient['_sendPacket'] = (packet, sendDone) => {\n\t\t\t\tshouldSendFail =\n\t\t\t\t\tpacket.cmd === 'pubcomp' && shouldSendPubcompFail\n\t\t\t\tif (sendDone) {\n\t\t\t\t\tsendDone(\n\t\t\t\t\t\tshouldSendFail\n\t\t\t\t\t\t\t? new Error('testing pubcomp failure')\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\t// send the mocked response\n\t\t\t\tswitch (packet.cmd) {\n\t\t\t\t\tcase 'subscribe': {\n\t\t\t\t\t\tconst suback: ISubackPacket = {\n\t\t\t\t\t\t\tcmd: 'suback',\n\t\t\t\t\t\t\tmessageId: packet.messageId,\n\t\t\t\t\t\t\tgranted: [2],\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandle(client, suback, (err) => {\n\t\t\t\t\t\t\tassert.isNotOk(err)\n\t\t\t\t\t\t})\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tcase 'pubrec':\n\t\t\t\t\tcase 'pubcomp': {\n\t\t\t\t\t\t// for both pubrec and pubcomp, reply with pubrel, simulating the server not receiving the pubcomp\n\t\t\t\t\t\tif (packet.cmd === 'pubcomp') {\n\t\t\t\t\t\t\tpubcompCount++\n\t\t\t\t\t\t\tif (pubcompCount === 2) {\n\t\t\t\t\t\t\t\t// end the test once the client has gone through two rounds of replying to pubrel messages\n\t\t\t\t\t\t\t\tassert.strictEqual(pubrelCount, 2)\n\t\t\t\t\t\t\t\tassert.strictEqual(handleMessageCount, 1)\n\t\t\t\t\t\t\t\tassert.strictEqual(emitMessageCount, 1)\n\t\t\t\t\t\t\t\tclient['_sendPacket'] = origSendPacket\n\t\t\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// simulate the pubrel message, either in response to pubrec or to mock pubcomp failing to be received\n\t\t\t\t\t\tconst pubrel: IPubrelPacket = {\n\t\t\t\t\t\t\tcmd: 'pubrel',\n\t\t\t\t\t\t\tmessageId: mid,\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpubrelCount++\n\t\t\t\t\t\thandle(client, pubrel, (err) => {\n\t\t\t\t\t\t\tif (shouldSendFail) {\n\t\t\t\t\t\t\t\tassert.exists(err)\n\t\t\t\t\t\t\t\tassert.instanceOf(err, Error)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tassert.notExists(err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe(testTopic, { qos: 2 })\n\t\t\t\tconst publish: IPublishPacket = {\n\t\t\t\t\tcmd: 'publish',\n\t\t\t\t\ttopic: testTopic,\n\t\t\t\t\tpayload: testMessage,\n\t\t\t\t\tqos: 2,\n\t\t\t\t\tmessageId: mid,\n\t\t\t\t\tdup: false,\n\t\t\t\t\tretain: false,\n\t\t\t\t}\n\t\t\t\thandle(client, publish, (err) => {\n\t\t\t\t\tassert.notExists(err)\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\n\t\tit('handle qos 2 messages exactly once when multiple pubrel received', function _test(t, done) {\n\t\t\ttestMultiplePubrel(false, done)\n\t\t})\n\n\t\tit('handle qos 2 messages exactly once when multiple pubrel received and sending pubcomp fails on client', function _test(t, done) {\n\t\t\ttestMultiplePubrel(true, done)\n\t\t})\n\t})\n\n\tdescribe('auto reconnect', () => {\n\t\tbeforeEach(beforeEachExec)\n\t\tafter(afterExec)\n\n\t\tit('should mark the client disconnecting if #end called', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.end(true, (err) => {\n\t\t\t\tassert.isTrue(client.disconnecting)\n\t\t\t\tdone(err)\n\t\t\t})\n\t\t})\n\n\t\tit('should reconnect after stream disconnect', function _test(t, done) {\n\t\t\tconst clock = sinon.useFakeTimers(fakeTimersOptions)\n\n\t\t\tt.after(() => {\n\t\t\t\tclock.restore()\n\t\t\t})\n\n\t\t\tconst client = connect({ reconnectPeriod: 1000 })\n\n\t\t\tlet tryReconnect = true\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tif (tryReconnect) {\n\t\t\t\t\tclient.stream.end()\n\t\t\t\t\tclient.once('close', () => {\n\t\t\t\t\t\tclock.tick(client.options.reconnectPeriod)\n\t\t\t\t\t})\n\t\t\t\t\ttryReconnect = false\n\t\t\t\t} else {\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\tclock.tick(100)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tit(\"should emit 'reconnect' when reconnecting\", function _test(t, done) {\n\t\t\tconst clock = sinon.useFakeTimers(fakeTimersOptions)\n\n\t\t\tt.after(() => {\n\t\t\t\tclock.restore()\n\t\t\t})\n\n\t\t\tconst client = connect({\n\t\t\t\treconnectPeriod: 1000,\n\t\t\t})\n\t\t\tlet tryReconnect = true\n\t\t\tlet reconnectEvent = false\n\n\t\t\tclient.on('reconnect', () => {\n\t\t\t\treconnectEvent = true\n\t\t\t})\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tif (tryReconnect) {\n\t\t\t\t\tclient.stream.end()\n\t\t\t\t\tclient.once('close', () => {\n\t\t\t\t\t\tclock.tick(client.options.reconnectPeriod)\n\t\t\t\t\t})\n\t\t\t\t\ttryReconnect = false\n\t\t\t\t} else {\n\t\t\t\t\tassert.isTrue(reconnectEvent)\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\tclock.tick(100)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tit(\"should emit 'offline' after going offline\", function _test(t, done) {\n\t\t\tconst clock = sinon.useFakeTimers(fakeTimersOptions)\n\n\t\t\tt.after(() => {\n\t\t\t\tclock.restore()\n\t\t\t})\n\t\t\tconst client = connect({\n\t\t\t\treconnectPeriod: 1000,\n\t\t\t})\n\n\t\t\tlet tryReconnect = true\n\t\t\tlet offlineEvent = false\n\n\t\t\tclient.on('offline', () => {\n\t\t\t\tofflineEvent = true\n\t\t\t})\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tif (tryReconnect) {\n\t\t\t\t\tclient.stream.end()\n\t\t\t\t\ttryReconnect = false\n\t\t\t\t\tclient.once('close', () => {\n\t\t\t\t\t\tclock.tick(client.options.reconnectPeriod)\n\t\t\t\t\t})\n\t\t\t\t} else {\n\t\t\t\t\tassert.isTrue(offlineEvent)\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\tclock.tick(100)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tit('should not reconnect if it was ended by the user', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.end((err) => done(err))\n\t\t\t})\n\t\t})\n\n\t\tit('should setup a reconnect timer on disconnect', function _test(t, done) {\n\t\t\tconst client = connect()\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\tassert.notExists(client['reconnectTimer'])\n\t\t\t\tclient.stream.end()\n\t\t\t})\n\n\t\t\tclient.once('close', () => {\n\t\t\t\tassert.exists(client['reconnectTimer'])\n\t\t\t\tclient.end(true, done)\n\t\t\t})\n\t\t})\n\n\t\tconst reconnectPeriodTests = [\n\t\t\t{ period: 200 },\n\t\t\t{ period: 2000 },\n\t\t\t{ period: 4000 },\n\t\t]\n\t\treconnectPeriodTests.forEach((test) => {\n\t\t\tit(\n\t\t\t\t`should allow specification of a reconnect period (${test.period}ms)`,\n\t\t\t\t{\n\t\t\t\t\ttimeout: 10000,\n\t\t\t\t},\n\t\t\t\tfunction _test(t, done) {\n\t\t\t\t\tconst clock = sinon.useFakeTimers(fakeTimersOptions)\n\n\t\t\t\t\tt.after(() => {\n\t\t\t\t\t\tclock.restore()\n\t\t\t\t\t})\n\n\t\t\t\t\tlet end\n\t\t\t\t\tconst reconnectSlushTime = 200\n\t\t\t\t\tconst client = connect({ reconnectPeriod: test.period })\n\t\t\t\t\tlet reconnect = false\n\t\t\t\t\tconst start = clock.now\n\n\t\t\t\t\tclient.on('connect', () => {\n\t\t\t\t\t\tif (!reconnect) {\n\t\t\t\t\t\t\tclient.stream.end()\n\t\t\t\t\t\t\tclient.once('close', () => {\n\t\t\t\t\t\t\t\t// ensure the tick is done after the reconnect timer is setup (on close)\n\t\t\t\t\t\t\t\tclock.tick(test.period)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\treconnect = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tend = clock.now\n\t\t\t\t\t\t\tclient.end(() => {\n\t\t\t\t\t\t\t\tconst reconnectPeriodDuringTest = end - start\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\treconnectPeriodDuringTest >=\n\t\t\t\t\t\t\t\t\t\ttest.period - reconnectSlushTime &&\n\t\t\t\t\t\t\t\t\treconnectPeriodDuringTest <=\n\t\t\t\t\t\t\t\t\t\ttest.period + reconnectSlushTime\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\t// give the connection a 200 ms slush window\n\t\t\t\t\t\t\t\t\tdone()\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tdone(\n\t\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\t`Strange reconnect period: ${reconnectPeriodDuringTest}`,\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tclock.tick(100)\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t)\n\t\t})\n\n\t\tit('should always cleanup successfully on reconnection', function _test(t, done) {\n\t\t\tteardownHelper.add({ executeOnce: true, order: 1 }, () => {\n\t\t\t\tif (clock) {\n\t\t\t\t\tclock.restore()\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst clock = sinon.useFakeTimers({\n\t\t\t\t...fakeTimersOptions,\n\t\t\t\ttoFake: ['setTimeout'],\n\t\t\t})\n\n\t\t\tconst client = connect({\n\t\t\t\thost: 'this_hostname_should_not_exist',\n\t\t\t\tconnectTimeout: 0,\n\t\t\t\treconnectPeriod: 1,\n\t\t\t})\n\n\t\t\t// bind client.end so that when it is called it is automatically passed in the done callback\n\t\t\tsetTimeout(() => {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tclient.end(done)\n\t\t\t\t}, 10)\n\n\t\t\t\tclock.tick(10)\n\t\t\t}, 10)\n\n\t\t\tclock.tick(10)\n\t\t})\n\n\t\tit('should emit connack timeout error', function _test(t, done) {\n\t\t\t// Use fake timers to simulate the timeout. The setTimeout inside the client connection\n\t\t\t// will inactive by other tests (maybe) causing this test never ends.\n\t\t\tconst clock = sinon.useFakeTimers({\n\t\t\t\t...fakeTimersOptions,\n\t\t\t\ttoFake: ['setTimeout'],\n\t\t\t})\n\n\t\t\tconst connectTimeout = 10\n\n\t\t\tt.after(() => {\n\t\t\t\tclock.restore()\n\t\t\t})\n\n\t\t\tconst client = connect({\n\t\t\t\tconnectTimeout,\n\t\t\t\treconnectPeriod: 5000,\n\t\t\t})\n\t\t\t\t.on('connect', () => {\n\t\t\t\t\tclock.tick(connectTimeout)\n\t\t\t\t})\n\t\t\t\t.on('error', (err) => {\n\t\t\t\t\tassert.equal(err.message, 'connack timeout')\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t})\n\t\t})\n\n\t\tit('should reconnect on connack error if requested', function _test(t, done) {\n\t\t\tlet connackErrors = 0\n\t\t\tconst rcNotAuthorized = 135\n\t\t\tconst server2 = serverBuilder(config.protocol, (serverClient) => {\n\t\t\t\tserverClient.on('connect', () => {\n\t\t\t\t\tconst rc = connackErrors === 0 ? rcNotAuthorized : 0\n\t\t\t\t\tconst connack =\n\t\t\t\t\t\tversion === 5 ? { reasonCode: rc } : { returnCode: rc }\n\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t})\n\t\t\t})\n\t\t\tteardownHelper.addServer(server2)\n\t\t\tserver2.listen(ports.PORTAND50, () => {\n\t\t\t\tconst client = connect({\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tport: ports.PORTAND50,\n\t\t\t\t\treconnectPeriod: 10,\n\t\t\t\t\treconnectOnConnackError: true,\n\t\t\t\t})\n\t\t\t\tteardownHelper.addClient(client)\n\t\t\t\tclient.on('error', (err) => {\n\t\t\t\t\tassert.instanceOf(err, ErrorWithReasonCode)\n\t\t\t\t\tassert.equal(\n\t\t\t\t\t\t(err as ErrorWithReasonCode).code,\n\t\t\t\t\t\trcNotAuthorized,\n\t\t\t\t\t)\n\t\t\t\t\tassert.equal(connackErrors, 0)\n\t\t\t\t\tconnackErrors++\n\t\t\t\t})\n\t\t\t\tclient.on('connect', () => {\n\t\t\t\t\tassert.equal(connackErrors, 1)\n\t\t\t\t\tdone()\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit(\n\t\t\t'should resend in-flight QoS 1 publish messages from the client',\n\t\t\t{\n\t\t\t\ttimeout: 4000,\n\t\t\t},\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst client = connect({ reconnectPeriod: 200 })\n\t\t\t\tlet serverPublished = false\n\t\t\t\tlet clientCalledBack = false\n\n\t\t\t\t// client is connected the first time\n\t\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\t\t// destroy the stream before the publish is acknowledged\n\t\t\t\t\tserverClient.once('connect', () => {\n\t\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\t\tserverClient.stream.destroy()\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\n\t\t\t\t\t// after 200ms the client should reconnect\n\t\t\t\t\tserver.once('client', (serverClientNew) => {\n\t\t\t\t\t\tserverClientNew.on('publish', () => {\n\t\t\t\t\t\t\tserverPublished = true\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\t// ensure that on first reconnect the publish is still not acknowledged\n\t\t\t\tclient.once('reconnect', () => {\n\t\t\t\t\t// client callback should not be triggered on first connection\n\t\t\t\t\tassert.isFalse(clientCalledBack)\n\t\t\t\t})\n\n\t\t\t\tclient.publish('hello', 'world', { qos: 1 }, () => {\n\t\t\t\t\tclientCalledBack = true\n\t\t\t\t})\n\n\t\t\t\tclient.on('packetreceive', (packet) => {\n\t\t\t\t\tif (packet.cmd === 'puback') {\n\t\t\t\t\t\tassert.isTrue(serverPublished)\n\t\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\t\tassert.isTrue(clientCalledBack)\n\t\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\n\t\tit('should not resend in-flight publish messages if disconnecting', function _test(t, done) {\n\t\t\tconst client = connect({ reconnectPeriod: 200 })\n\t\t\tlet serverPublished = false\n\t\t\tlet clientCalledBack = false\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('connect', () => {\n\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\tserverClient.stream.destroy()\n\t\t\t\t\t\tclient.end(true, (err) => {\n\t\t\t\t\t\t\tassert.isFalse(serverPublished)\n\t\t\t\t\t\t\tassert.isFalse(clientCalledBack)\n\t\t\t\t\t\t\tdone(err)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tserver.once('client', (serverClientNew) => {\n\t\t\t\t\tserverClientNew.on('publish', () => {\n\t\t\t\t\t\tserverPublished = true\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t\tclient.publish('hello', 'world', { qos: 1 }, () => {\n\t\t\t\tclientCalledBack = true\n\t\t\t})\n\t\t})\n\n\t\tit(\n\t\t\t'should resend in-flight QoS 2 publish messages from the client',\n\t\t\t{\n\t\t\t\ttimeout: 4000,\n\t\t\t},\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst client = connect({ reconnectPeriod: 200 })\n\t\t\t\tlet serverPublished = false\n\t\t\t\tlet clientCalledBack = false\n\n\t\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\t\t// ignore errors\n\t\t\t\t\tserverClient.on('error', () => {})\n\t\t\t\t\tserverClient.on('publish', () => {\n\t\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\t\tserverClient.stream.destroy()\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\n\t\t\t\t\tserver.once('client', (serverClientNew) => {\n\t\t\t\t\t\tserverClientNew.on('pubrel', () => {\n\t\t\t\t\t\t\tserverPublished = true\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tclient.publish('hello', 'world', { qos: 2 }, () => {\n\t\t\t\t\tclientCalledBack = true\n\t\t\t\t})\n\n\t\t\t\tclient.on('packetreceive', (packet) => {\n\t\t\t\t\tif (packet.cmd === 'pubcomp') {\n\t\t\t\t\t\tassert.isTrue(serverPublished)\n\t\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\t\tassert.isTrue(clientCalledBack)\n\t\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\n\t\tit('should not resend in-flight QoS 1 removed publish messages from the client', function _test(t, done) {\n\t\t\tconst client = connect({ reconnectPeriod: 100 })\n\t\t\tlet clientCalledBack = false\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.on('connect', () => {\n\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\tserverClient.stream.destroy()\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tserver.once('client', (serverClientNew) => {\n\t\t\t\t\tserverClientNew.on('publish', () => {\n\t\t\t\t\t\tdone(Error('should not have received publish'))\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tclient.publish('hello', 'world', { qos: 1 }, (err) => {\n\t\t\t\tclientCalledBack = true\n\t\t\t\tassert.exists(err, 'error should exist')\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\terr.message,\n\t\t\t\t\t'Message removed',\n\t\t\t\t\t'error message is incorrect',\n\t\t\t\t)\n\t\t\t})\n\t\t\tassert.strictEqual(Object.keys(client.outgoing).length, 1)\n\t\t\tassert.strictEqual(client['outgoingStore']['_inflights'].size, 1)\n\t\t\tclient.removeOutgoingMessage(client.getLastMessageId())\n\t\t\tassert.strictEqual(Object.keys(client.outgoing).length, 0)\n\t\t\tassert.strictEqual(client['outgoingStore']['_inflights'].size, 0)\n\t\t\tassert.isTrue(clientCalledBack)\n\t\t\tclient.end(true, (err) => {\n\t\t\t\tdone(err)\n\t\t\t})\n\t\t})\n\n\t\tit('should not resend in-flight QoS 2 removed publish messages from the client', function _test(t, done) {\n\t\t\tconst client = connect({ reconnectPeriod: 200 })\n\t\t\tlet clientCalledBack = false\n\n\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\tserverClient.on('connect', () => {\n\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\tserverClient.stream.destroy()\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tserver.once('client', (serverClientNew) => {\n\t\t\t\t\tserverClientNew.on('publish', () => {\n\t\t\t\t\t\tdone(Error('should not have received publish'))\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tclient.publish('hello', 'world', { qos: 2 }, (err) => {\n\t\t\t\tclientCalledBack = true\n\t\t\t\tassert.strictEqual(err.message, 'Message removed')\n\t\t\t})\n\t\t\tassert.strictEqual(Object.keys(client.outgoing).length, 1)\n\t\t\tassert.strictEqual(client['outgoingStore']['_inflights'].size, 1)\n\t\t\tclient.removeOutgoingMessage(client.getLastMessageId())\n\t\t\tassert.strictEqual(Object.keys(client.outgoing).length, 0)\n\t\t\tassert.strictEqual(client['outgoingStore']['_inflights'].size, 0)\n\t\t\tassert.isTrue(clientCalledBack)\n\t\t\tclient.end(true, done)\n\t\t})\n\n\t\tit('should resubscribe when reconnecting', function _test(t, done) {\n\t\t\tconst client = connect({ reconnectPeriod: 100 })\n\t\t\tlet tryReconnect = true\n\t\t\tlet reconnectEvent = false\n\n\t\t\tclient.on('reconnect', () => {\n\t\t\t\treconnectEvent = true\n\t\t\t})\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tif (tryReconnect) {\n\t\t\t\t\tclient.subscribe('hello', () => {\n\t\t\t\t\t\tclient.stream.end()\n\n\t\t\t\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\t\t\t\tclient.end(done)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\n\t\t\t\t\ttryReconnect = false\n\t\t\t\t} else {\n\t\t\t\t\tassert.isTrue(reconnectEvent)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tit('should resubscribe when clean=false and sessionPresent=false', function _test(t, done) {\n\t\t\tconst client = connect({\n\t\t\t\tclientId: 'test',\n\t\t\t\treconnectPeriod: 100,\n\t\t\t\tclean: false,\n\t\t\t\tprotocolVersion: 4,\n\t\t\t})\n\t\t\tlet tryReconnect = true\n\t\t\tlet reconnectEvent = false\n\n\t\t\tclient.on('reconnect', () => {\n\t\t\t\treconnectEvent = true\n\t\t\t})\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tif (tryReconnect) {\n\t\t\t\t\tclient.subscribe('hello', () => {\n\t\t\t\t\t\tclient.stream.end()\n\n\t\t\t\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\t\t\t\tclient.end(done)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\n\t\t\t\t\ttryReconnect = false\n\t\t\t\t} else {\n\t\t\t\t\tassert.isTrue(reconnectEvent)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tit('should not resubscribe when reconnecting if resubscribe is disabled', function _test(t, done) {\n\t\t\tconst client = connect({ reconnectPeriod: 100, resubscribe: false })\n\t\t\tlet tryReconnect = true\n\t\t\tlet reconnectEvent = false\n\n\t\t\tclient.on('reconnect', () => {\n\t\t\t\treconnectEvent = true\n\t\t\t})\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tif (tryReconnect) {\n\t\t\t\t\tclient.subscribe('hello', () => {\n\t\t\t\t\t\tclient.stream.end()\n\n\t\t\t\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\t\t\t\tfail()\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\n\t\t\t\t\ttryReconnect = false\n\t\t\t\t} else {\n\t\t\t\t\tassert.isTrue(reconnectEvent)\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tObject.keys(client['_resubscribeTopics']).length,\n\t\t\t\t\t\t0,\n\t\t\t\t\t)\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tit('should not resubscribe when reconnecting if suback is error', function _test(t, done) {\n\t\t\tlet tryReconnect = true\n\t\t\tlet reconnectEvent = false\n\t\t\tlet client: mqtt.MqttClient | null = null\n\t\t\tconst server2 = serverBuilder(config.protocol, (serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tconst connack =\n\t\t\t\t\t\tversion === 5 ? { reasonCode: 0 } : { returnCode: 0 }\n\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t})\n\t\t\t\tserverClient.on('subscribe', (packet) => {\n\t\t\t\t\tserverClient.suback({\n\t\t\t\t\t\tmessageId: packet.messageId,\n\t\t\t\t\t\tgranted: packet.subscriptions.map((e) => e.qos | 0x80),\n\t\t\t\t\t})\n\t\t\t\t\tserverClient.pubrel({\n\t\t\t\t\t\tmessageId: Math.floor(Math.random() * 9000) + 1000,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver2.listen(ports.PORTAND49, () => {\n\t\t\t\tclient = connect({\n\t\t\t\t\tport: ports.PORTAND49,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\treconnectPeriod: 100,\n\t\t\t\t})\n\n\t\t\t\tclient.on('reconnect', () => {\n\t\t\t\t\treconnectEvent = true\n\t\t\t\t})\n\n\t\t\t\tclient.on('connect', () => {\n\t\t\t\t\tif (tryReconnect) {\n\t\t\t\t\t\tclient.subscribe('hello', () => {\n\t\t\t\t\t\t\tclient.stream.end()\n\n\t\t\t\t\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\t\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\t\t\t\t\tfail()\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t\ttryReconnect = false\n\t\t\t\t\t} else {\n\t\t\t\t\t\tassert.isTrue(reconnectEvent)\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\tObject.keys(client['_resubscribeTopics']).length,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tdone()\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should preserved incomingStore after disconnecting if clean is false', function _test(t, done) {\n\t\t\tlet reconnect = false\n\t\t\tlet client: mqtt.MqttClient | null = null\n\t\t\tconst incomingStore = new mqtt.Store({ clean: false })\n\t\t\tconst outgoingStore = new mqtt.Store({ clean: false })\n\t\t\tconst server2 = serverBuilder(config.protocol, (serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tconst connack =\n\t\t\t\t\t\tversion === 5 ? { reasonCode: 0 } : { returnCode: 0 }\n\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t\tif (reconnect) {\n\t\t\t\t\t\tserverClient.pubrel({ messageId: 1 })\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tserverClient.on('subscribe', (packet) => {\n\t\t\t\t\tserverClient.suback({\n\t\t\t\t\t\tmessageId: packet.messageId,\n\t\t\t\t\t\tgranted: packet.subscriptions.map((e) => e.qos),\n\t\t\t\t\t})\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\ttopic: 'topic',\n\t\t\t\t\t\tpayload: 'payload',\n\t\t\t\t\t\tqos: 2,\n\t\t\t\t\t\tmessageId: 1,\n\t\t\t\t\t\tretain: false,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tserverClient.on('pubrec', (packet) => {\n\t\t\t\t\tclient.end(false, () => {\n\t\t\t\t\t\tclient.reconnect({\n\t\t\t\t\t\t\tincomingStore,\n\t\t\t\t\t\t\toutgoingStore,\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tserverClient.on('pubcomp', (packet) => {\n\t\t\t\t\tdone()\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver2.listen(ports.PORTAND50, () => {\n\t\t\t\tclient = connect({\n\t\t\t\t\tport: ports.PORTAND50,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tclean: false,\n\t\t\t\t\tclientId: 'cid1',\n\t\t\t\t\treconnectPeriod: 0,\n\t\t\t\t\tincomingStore,\n\t\t\t\t\toutgoingStore,\n\t\t\t\t})\n\n\t\t\t\tclient.on('connect', () => {\n\t\t\t\t\tif (!reconnect) {\n\t\t\t\t\t\tclient.subscribe('test', { qos: 2 }, () => {})\n\t\t\t\t\t\treconnect = true\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tclient.on('message', (topic, message) => {\n\t\t\t\t\tassert.strictEqual(topic, 'topic')\n\t\t\t\t\tassert.strictEqual(message.toString(), 'payload')\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should clear outgoing if close from server', function _test(t, done) {\n\t\t\tlet reconnect = false\n\t\t\tlet client: mqtt.MqttClient | null = null\n\t\t\tconst server2 = serverBuilder(config.protocol, (serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tconst connack =\n\t\t\t\t\t\tversion === 5 ? { reasonCode: 0 } : { returnCode: 0 }\n\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t})\n\t\t\t\tserverClient.on('subscribe', (packet) => {\n\t\t\t\t\tif (reconnect) {\n\t\t\t\t\t\tserverClient.suback({\n\t\t\t\t\t\t\tmessageId: packet.messageId,\n\t\t\t\t\t\t\tgranted: packet.subscriptions.map((e) => e.qos),\n\t\t\t\t\t\t})\n\t\t\t\t\t} else {\n\t\t\t\t\t\tserverClient.destroy()\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver2.listen(ports.PORTAND50, () => {\n\t\t\t\tclient = connect({\n\t\t\t\t\tport: ports.PORTAND50,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tclean: true,\n\t\t\t\t\tclientId: 'cid1',\n\t\t\t\t\tkeepalive: 1,\n\t\t\t\t\treconnectPeriod: 0,\n\t\t\t\t})\n\n\t\t\t\tclient.on('connect', () => {\n\t\t\t\t\tclient.subscribe('test', { qos: 2 }, (e) => {\n\t\t\t\t\t\tif (!e) {\n\t\t\t\t\t\t\tclient.end()\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tclient.on('close', () => {\n\t\t\t\t\tif (reconnect) {\n\t\t\t\t\t\tdone()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\tObject.keys(client.outgoing).length,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t)\n\t\t\t\t\t\treconnect = true\n\t\t\t\t\t\tclient.reconnect()\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should resend in-flight QoS 1 publish messages from the client if clean is false', function _test(t, done) {\n\t\t\tlet reconnect = false\n\t\t\tlet client: mqtt.MqttClient | null = null\n\t\t\tconst incomingStore = new mqtt.Store({ clean: false })\n\t\t\tconst outgoingStore = new mqtt.Store({ clean: false })\n\t\t\tconst server2 = serverBuilder(config.protocol, (serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tconst connack =\n\t\t\t\t\t\tversion === 5 ? { reasonCode: 0 } : { returnCode: 0 }\n\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t})\n\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\tif (reconnect) {\n\t\t\t\t\t\tdone()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclient.end(true, () => {\n\t\t\t\t\t\t\tclient.reconnect({\n\t\t\t\t\t\t\t\tincomingStore,\n\t\t\t\t\t\t\t\toutgoingStore,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\treconnect = true\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver2.listen(ports.PORTAND50, () => {\n\t\t\t\tclient = connect({\n\t\t\t\t\tport: ports.PORTAND50,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tclean: false,\n\t\t\t\t\tclientId: 'cid1',\n\t\t\t\t\treconnectPeriod: 0,\n\t\t\t\t\tincomingStore,\n\t\t\t\t\toutgoingStore,\n\t\t\t\t})\n\n\t\t\t\tclient.on('connect', () => {\n\t\t\t\t\tif (!reconnect) {\n\t\t\t\t\t\tclient.publish('topic', 'payload', { qos: 1 })\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tclient.on('error', () => {})\n\t\t\t})\n\t\t})\n\n\t\tit('should resend in-flight QoS 2 publish messages from the client if clean is false', function _test(t, done) {\n\t\t\tlet reconnect = false\n\t\t\tlet client: mqtt.MqttClient | null = null\n\t\t\tconst incomingStore = new mqtt.Store({ clean: false })\n\t\t\tconst outgoingStore = new mqtt.Store({ clean: false })\n\t\t\tconst server2 = serverBuilder(config.protocol, (serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tconst connack =\n\t\t\t\t\t\tversion === 5 ? { reasonCode: 0 } : { returnCode: 0 }\n\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t})\n\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\tif (reconnect) {\n\t\t\t\t\t\tdone()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclient.end(true, () => {\n\t\t\t\t\t\t\tclient.reconnect({\n\t\t\t\t\t\t\t\tincomingStore,\n\t\t\t\t\t\t\t\toutgoingStore,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\treconnect = true\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver2.listen(ports.PORTAND50, () => {\n\t\t\t\tclient = connect({\n\t\t\t\t\tport: ports.PORTAND50,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tclean: false,\n\t\t\t\t\tclientId: 'cid1',\n\t\t\t\t\treconnectPeriod: 0,\n\t\t\t\t\tincomingStore,\n\t\t\t\t\toutgoingStore,\n\t\t\t\t})\n\n\t\t\t\tclient.on('connect', () => {\n\t\t\t\t\tif (!reconnect) {\n\t\t\t\t\t\tclient.publish('topic', 'payload', { qos: 2 })\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tclient.on('error', () => {})\n\t\t\t})\n\t\t})\n\n\t\tit('should resend in-flight QoS 2 pubrel messages from the client if clean is false', function _test(t, done) {\n\t\t\tlet reconnect = false\n\t\t\tlet client: mqtt.MqttClient | null = null\n\t\t\tconst incomingStore = new mqtt.Store({ clean: false })\n\t\t\tconst outgoingStore = new mqtt.Store({ clean: false })\n\t\t\tconst server2 = serverBuilder(config.protocol, (serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tconst connack =\n\t\t\t\t\t\tversion === 5 ? { reasonCode: 0 } : { returnCode: 0 }\n\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t})\n\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\tif (!reconnect) {\n\t\t\t\t\t\tserverClient.pubrec({ messageId: packet.messageId })\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tserverClient.on('pubrel', (packet) => {\n\t\t\t\t\tif (reconnect) {\n\t\t\t\t\t\tserverClient.pubcomp({ messageId: packet.messageId })\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclient.end(true, () => {\n\t\t\t\t\t\t\tclient.reconnect({\n\t\t\t\t\t\t\t\tincomingStore,\n\t\t\t\t\t\t\t\toutgoingStore,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\treconnect = true\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver2.listen(ports.PORTAND50, () => {\n\t\t\t\tclient = connect({\n\t\t\t\t\tport: ports.PORTAND50,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tclean: false,\n\t\t\t\t\tclientId: 'cid1',\n\t\t\t\t\treconnectPeriod: 0,\n\t\t\t\t\tincomingStore,\n\t\t\t\t\toutgoingStore,\n\t\t\t\t})\n\n\t\t\t\tclient.on('connect', () => {\n\t\t\t\t\tif (!reconnect) {\n\t\t\t\t\t\tclient.publish(\n\t\t\t\t\t\t\t'topic',\n\t\t\t\t\t\t\t'payload',\n\t\t\t\t\t\t\t{ qos: 2 },\n\t\t\t\t\t\t\t(err) => {\n\t\t\t\t\t\t\t\tassert(reconnect)\n\t\t\t\t\t\t\t\tassert.ifError(err)\n\t\t\t\t\t\t\t\tdone()\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tclient.on('error', () => {})\n\t\t\t})\n\t\t})\n\n\t\tit('should resend in-flight publish messages by published order', function _test(t, done) {\n\t\t\tlet publishCount = 0\n\t\t\tlet reconnect = false\n\t\t\tlet disconnectOnce = true\n\t\t\tlet client: mqtt.MqttClient | null = null\n\t\t\tconst incomingStore = new mqtt.Store({ clean: false })\n\t\t\tconst outgoingStore = new mqtt.Store({ clean: false })\n\t\t\tconst server2 = serverBuilder(config.protocol, (serverClient) => {\n\t\t\t\t// errors are not interesting for this test\n\t\t\t\t// but they might happen on some platforms\n\t\t\t\tserverClient.on('error', () => {})\n\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tconst connack =\n\t\t\t\t\t\tversion === 5 ? { reasonCode: 0 } : { returnCode: 0 }\n\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t})\n\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\tserverClient.puback({ messageId: packet.messageId })\n\t\t\t\t\tif (reconnect) {\n\t\t\t\t\t\tswitch (publishCount++) {\n\t\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\t\t\t\t'payload1',\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\t\t\t\t'payload2',\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\t\tpacket.payload.toString(),\n\t\t\t\t\t\t\t\t\t'payload3',\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tdone()\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (disconnectOnce) {\n\t\t\t\t\t\tclient.end(true, () => {\n\t\t\t\t\t\t\treconnect = true\n\t\t\t\t\t\t\tclient.reconnect({\n\t\t\t\t\t\t\t\tincomingStore,\n\t\t\t\t\t\t\t\toutgoingStore,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t\tdisconnectOnce = false\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver2.listen(ports.PORTAND50, () => {\n\t\t\t\tclient = connect({\n\t\t\t\t\tport: ports.PORTAND50,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tclean: false,\n\t\t\t\t\tclientId: 'cid1',\n\t\t\t\t\treconnectPeriod: 0,\n\t\t\t\t\tincomingStore,\n\t\t\t\t\toutgoingStore,\n\t\t\t\t})\n\n\t\t\t\tclient['nextId'] = 65535\n\n\t\t\t\tclient.on('connect', () => {\n\t\t\t\t\tif (!reconnect) {\n\t\t\t\t\t\tclient.publish('topic', 'payload1', { qos: 1 })\n\t\t\t\t\t\tclient.publish('topic', 'payload2', { qos: 1 })\n\t\t\t\t\t\tclient.publish('topic', 'payload3', { qos: 1 })\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tclient.on('error', () => {})\n\t\t\t})\n\t\t})\n\n\t\tit('should be able to pub/sub if reconnect() is called at close handler', function _test(t, done) {\n\t\t\tconst client = connect({ reconnectPeriod: 0 })\n\t\t\tlet tryReconnect = true\n\t\t\tlet reconnectEvent = false\n\n\t\t\tclient.on('close', () => {\n\t\t\t\tif (tryReconnect) {\n\t\t\t\t\ttryReconnect = false\n\t\t\t\t\tclient.reconnect()\n\t\t\t\t} else {\n\t\t\t\t\tassert.isTrue(reconnectEvent)\n\t\t\t\t\tdone()\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tclient.on('reconnect', () => {\n\t\t\t\treconnectEvent = true\n\t\t\t})\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tif (tryReconnect) {\n\t\t\t\t\tclient.end()\n\t\t\t\t} else {\n\t\t\t\t\tclient.subscribe('hello', () => {\n\t\t\t\t\t\tclient.end()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tit('should be able to pub/sub if reconnect() is called at out of close handler', function _test(t, done) {\n\t\t\tteardownHelper.add({ executeOnce: true, order: 1 }, () => {\n\t\t\t\tif (clock) {\n\t\t\t\t\tclock.restore()\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst clock = sinon.useFakeTimers({\n\t\t\t\t...fakeTimersOptions,\n\t\t\t\ttoFake: ['setTimeout'],\n\t\t\t})\n\n\t\t\tconst client = connect({ reconnectPeriod: 0 })\n\t\t\tlet tryReconnect = true\n\t\t\tlet reconnectEvent = false\n\n\t\t\tclient.on('close', () => {\n\t\t\t\tif (tryReconnect) {\n\t\t\t\t\ttryReconnect = false\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tclient.reconnect()\n\t\t\t\t\t}, 100)\n\n\t\t\t\t\tclock.tick(100)\n\t\t\t\t} else {\n\t\t\t\t\tassert.isTrue(reconnectEvent)\n\t\t\t\t\tdone()\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tclient.on('reconnect', () => {\n\t\t\t\treconnectEvent = true\n\t\t\t})\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\tif (tryReconnect) {\n\t\t\t\t\tclient.end()\n\t\t\t\t} else {\n\t\t\t\t\tclient.subscribe('hello', () => {\n\t\t\t\t\t\tclient.end()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tdescribe('with alternate server client', () => {\n\t\t\tlet cachedClientListeners\n\t\t\tconst connack =\n\t\t\t\tversion === 5 ? { reasonCode: 0 } : { returnCode: 0 }\n\n\t\t\tbeforeEach(async () => {\n\t\t\t\tawait beforeEachExec()\n\t\t\t\tcachedClientListeners = server.listeners('client')\n\t\t\t\tserver.removeAllListeners('client')\n\t\t\t})\n\n\t\t\tafterEach(() => {\n\t\t\t\tserver.removeAllListeners('client')\n\t\t\t\tcachedClientListeners.forEach((listener) => {\n\t\t\t\t\tserver.on('client', listener)\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tafter(afterExec)\n\n\t\t\tit('should resubscribe even if disconnect is before suback', function _test(t, done) {\n\t\t\t\tconst client = connect({ reconnectPeriod: 100, ...config })\n\t\t\t\tlet subscribeCount = 0\n\t\t\t\tlet connectCount = 0\n\n\t\t\t\tserver.on('client', (serverClient) => {\n\t\t\t\t\tserverClient.on('connect', () => {\n\t\t\t\t\t\tconnectCount++\n\t\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t\t})\n\n\t\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\t\tsubscribeCount++\n\n\t\t\t\t\t\t// disconnect before sending the suback on the first subscribe\n\t\t\t\t\t\tif (subscribeCount === 1) {\n\t\t\t\t\t\t\tclient.stream.end()\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// after the second connection, confirm that the only two\n\t\t\t\t\t\t// subscribes have taken place, then cleanup and exit\n\t\t\t\t\t\tif (connectCount >= 2) {\n\t\t\t\t\t\t\tassert.strictEqual(subscribeCount, 2)\n\t\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tclient.subscribe('hello')\n\t\t\t})\n\n\t\t\tit('should resubscribe exactly once', function _test(t, done) {\n\t\t\t\tconst client = connect({ reconnectPeriod: 100, ...config })\n\t\t\t\tlet subscribeCount = 0\n\n\t\t\t\tserver.on('client', (serverClient) => {\n\t\t\t\t\tserverClient.on('connect', () => {\n\t\t\t\t\t\tserverClient.connack(connack)\n\t\t\t\t\t})\n\n\t\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\t\tsubscribeCount++\n\n\t\t\t\t\t\t// disconnect before sending the suback on the first subscribe\n\t\t\t\t\t\tif (subscribeCount === 1) {\n\t\t\t\t\t\t\tclient.stream.end()\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// after the second connection, only two subs\n\t\t\t\t\t\t// subscribes have taken place, then cleanup and exit\n\t\t\t\t\t\tif (subscribeCount === 2) {\n\t\t\t\t\t\t\tclient.end(true, done)\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tclient.subscribe('hello')\n\t\t\t})\n\t\t})\n\t})\n\n\tdescribe('message id to subscription topic mapping', () => {\n\t\tbeforeEach(beforeEachExec)\n\t\tafter(afterExec)\n\n\t\tit('should not create a mapping if resubscribe is disabled', function _test(t, done) {\n\t\t\tconst client = connect({ resubscribe: false })\n\t\t\tclient.subscribe('test1')\n\t\t\tclient.subscribe('test2')\n\t\t\tassert.strictEqual(Object.keys(client.messageIdToTopic).length, 0)\n\t\t\tclient.end(true, done)\n\t\t})\n\n\t\tit('should create a mapping for each subscribe call', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.subscribe('test1')\n\t\t\tassert.strictEqual(Object.keys(client.messageIdToTopic).length, 1)\n\t\t\tclient.subscribe('test2')\n\t\t\tassert.strictEqual(Object.keys(client.messageIdToTopic).length, 2)\n\n\t\t\tclient.subscribe(['test3', 'test4'])\n\t\t\tassert.strictEqual(Object.keys(client.messageIdToTopic).length, 3)\n\t\t\tclient.subscribe(['test5', 'test6'])\n\t\t\tassert.strictEqual(Object.keys(client.messageIdToTopic).length, 4)\n\n\t\t\tclient.end(true, done)\n\t\t})\n\n\t\tit('should remove the mapping after suback', function _test(t, done) {\n\t\t\tconst client = connect()\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe('test1', { qos: 2 }, () => {\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tObject.keys(client.messageIdToTopic).length,\n\t\t\t\t\t\t0,\n\t\t\t\t\t)\n\n\t\t\t\t\tclient.subscribe(['test2', 'test3'], { qos: 2 }, () => {\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\tObject.keys(client.messageIdToTopic).length,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tclient.end(done)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "test/node/abstract_store.ts",
    "content": "import { type IPublishPacket, type IPubrelPacket } from 'mqtt-packet'\nimport 'should'\nimport { it, beforeEach, afterEach } from 'node:test'\nimport { type IStore } from '../../src'\n\nexport default function abstractStoreTest(\n\tbuild: (cb: (err?: Error, store?: IStore) => void) => void,\n) {\n\tlet store: IStore\n\n\tbeforeEach((_ctx, done) => {\n\t\tbuild((err, _store) => {\n\t\t\tstore = _store\n\t\t\tdone(err)\n\t\t})\n\t})\n\n\tafterEach((_ctx, done) => {\n\t\tstore.close(done)\n\t})\n\n\tit('should put and stream in-flight packets', function _test(t, done) {\n\t\tconst packet: IPublishPacket = {\n\t\t\ttopic: 'hello',\n\t\t\tpayload: 'world',\n\t\t\tqos: 1,\n\t\t\tmessageId: 42,\n\t\t\tcmd: 'publish',\n\t\t\tdup: false,\n\t\t\tretain: false,\n\t\t}\n\n\t\tstore.put(packet, () => {\n\t\t\tstore.createStream().on('data', (data) => {\n\t\t\t\tdata.should.eql(packet)\n\t\t\t\tdone()\n\t\t\t})\n\t\t})\n\t})\n\n\tit('should support destroying the stream', function _test(t, done) {\n\t\tconst packet: IPublishPacket = {\n\t\t\ttopic: 'hello',\n\t\t\tpayload: 'world',\n\t\t\tqos: 1,\n\t\t\tmessageId: 42,\n\t\t\tcmd: 'publish',\n\t\t\tdup: false,\n\t\t\tretain: false,\n\t\t}\n\n\t\tstore.put(packet, () => {\n\t\t\tconst stream = store.createStream()\n\t\t\tstream.on('close', done)\n\t\t\tstream.destroy()\n\t\t})\n\t})\n\n\tit('should add and del in-flight packets', function _test(t, done) {\n\t\tconst packet: IPublishPacket = {\n\t\t\ttopic: 'hello',\n\t\t\tpayload: 'world',\n\t\t\tqos: 1,\n\t\t\tmessageId: 42,\n\t\t\tcmd: 'publish',\n\t\t\tdup: false,\n\t\t\tretain: false,\n\t\t}\n\n\t\tstore.put(packet, () => {\n\t\t\tstore.del(packet, () => {\n\t\t\t\tstore\n\t\t\t\t\t.createStream()\n\t\t\t\t\t.on('data', () => {\n\t\t\t\t\t\tdone(new Error('this should never happen'))\n\t\t\t\t\t})\n\t\t\t\t\t.on('end', done)\n\t\t\t})\n\t\t})\n\t})\n\n\tit('should replace a packet when doing put with the same messageId', function _test(t, done) {\n\t\tconst packet1: IPublishPacket = {\n\t\t\tcmd: 'publish', // added\n\t\t\ttopic: 'hello',\n\t\t\tpayload: 'world',\n\t\t\tqos: 2,\n\t\t\tmessageId: 42,\n\t\t\tdup: false,\n\t\t\tretain: false,\n\t\t}\n\t\tconst packet2: IPubrelPacket = {\n\t\t\tcmd: 'pubrel', // added\n\t\t\t// qos: 2,\n\t\t\tmessageId: 42,\n\t\t}\n\n\t\tstore.put(packet1, () => {\n\t\t\tstore.put(packet2, () => {\n\t\t\t\tstore.createStream().on('data', (data) => {\n\t\t\t\t\tdata.should.eql(packet2)\n\t\t\t\t\tdone()\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n\n\tit('should return the original packet on del', function _test(t, done) {\n\t\tconst packet: IPublishPacket = {\n\t\t\ttopic: 'hello',\n\t\t\tpayload: 'world',\n\t\t\tqos: 1,\n\t\t\tmessageId: 42,\n\t\t\tcmd: 'publish',\n\t\t\tdup: false,\n\t\t\tretain: false,\n\t\t}\n\n\t\tstore.put(packet, () => {\n\t\t\tstore.del({ messageId: 42 }, (err, deleted) => {\n\t\t\t\tif (err) {\n\t\t\t\t\tthrow err\n\t\t\t\t}\n\t\t\t\tdeleted.should.eql(packet)\n\t\t\t\tdone()\n\t\t\t})\n\t\t})\n\t})\n\n\tit('should get a packet with the same messageId', function _test(t, done) {\n\t\tconst packet: IPublishPacket = {\n\t\t\ttopic: 'hello',\n\t\t\tpayload: 'world',\n\t\t\tqos: 1,\n\t\t\tmessageId: 42,\n\t\t\tcmd: 'publish',\n\t\t\tdup: false,\n\t\t\tretain: false,\n\t\t}\n\n\t\tstore.put(packet, () => {\n\t\t\tstore.get({ messageId: 42 }, (err, fromDb) => {\n\t\t\t\tif (err) {\n\t\t\t\t\tthrow err\n\t\t\t\t}\n\t\t\t\tfromDb.should.eql(packet)\n\t\t\t\tdone()\n\t\t\t})\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "test/node/client.ts",
    "content": "import { useFakeTimers } from 'sinon'\nimport { assert } from 'chai'\nimport { fork } from 'child_process'\nimport path from 'path'\nimport net from 'net'\nimport eos from 'end-of-stream'\nimport mqttPacket from 'mqtt-packet'\nimport { Duplex } from 'readable-stream'\nimport Connection from 'mqtt-connection'\nimport util from 'util'\nimport _debug from 'debug'\nimport { type IClientOptions } from 'src/lib/client'\nimport { describe, it, after } from 'node:test'\nimport getPorts from './helpers/port_list'\nimport serverBuilder from './server_helpers_for_client_tests'\nimport { MqttServer } from './server'\nimport abstractClientTests from './abstract_client'\nimport mqtt, { MQTTJS_VERSION } from '../../src'\n\nconst debug = _debug('mqttjs:client-test')\n\nconst ports = getPorts(2)\n\ndescribe('MqttClient', () => {\n\tlet client: mqtt.MqttClient\n\tconst server = serverBuilder('mqtt')\n\tconst config: IClientOptions = {\n\t\tprotocol: 'mqtt',\n\t\tport: ports.PORT,\n\t}\n\tserver.listen(ports.PORT)\n\n\tafter(() => {\n\t\t// clean up and make sure the server is no longer listening...\n\t\tif (server.listening) {\n\t\t\tserver.close()\n\t\t}\n\n\t\tprocess.exit(0)\n\t})\n\n\tit('should have static VERSION set', function _test(t) {\n\t\tassert.equal(mqtt.MqttClient.VERSION, MQTTJS_VERSION)\n\t})\n\n\tabstractClientTests(server, config, ports)\n\n\tdescribe('creating', () => {\n\t\tit('should allow instantiation of MqttClient', function _test(t, done) {\n\t\t\ttry {\n\t\t\t\tclient = new mqtt.MqttClient(() => {\n\t\t\t\t\tthrow Error('break')\n\t\t\t\t}, {})\n\t\t\t\tclient.end()\n\t\t\t} catch (err) {\n\t\t\t\tassert.strictEqual(err.message, 'break')\n\t\t\t\tdone()\n\t\t\t}\n\t\t})\n\n\t\tit('should disable number cache if specified in options', function _test(t, done) {\n\t\t\ttry {\n\t\t\t\tassert.isTrue(mqttPacket.writeToStream.cacheNumbers)\n\t\t\t\tclient = new mqtt.MqttClient(\n\t\t\t\t\t() => {\n\t\t\t\t\t\tthrow Error('break')\n\t\t\t\t\t},\n\t\t\t\t\t{ writeCache: false },\n\t\t\t\t)\n\t\t\t\tclient.end()\n\t\t\t} catch {\n\t\t\t\tassert.isFalse(mqttPacket.writeToStream.cacheNumbers)\n\t\t\t\tdone()\n\t\t\t}\n\t\t})\n\t})\n\n\tdescribe('message ids', () => {\n\t\tit('should increment the message id', function _test(t, done) {\n\t\t\tclient = mqtt.connect(config)\n\t\t\tconst currentId = client['_nextId']()\n\n\t\t\tassert.equal(client['_nextId'](), currentId + 1)\n\t\t\tclient.end((err) => done(err))\n\t\t})\n\n\t\tit(\"should not throw an error if packet's messageId is not found when receiving a pubrel packet\", function _test(t, done) {\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tserverClient.connack({ returnCode: 0 })\n\t\t\t\t\tserverClient.pubrel({\n\t\t\t\t\t\tmessageId: Math.floor(Math.random() * 9000) + 1000,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tserver2.listen(ports.PORTAND49, () => {\n\t\t\t\tclient = mqtt.connect({\n\t\t\t\t\tport: ports.PORTAND49,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t})\n\n\t\t\t\tclient.on('packetsend', (packet) => {\n\t\t\t\t\tif (packet.cmd === 'pubcomp') {\n\t\t\t\t\t\tclient.end((err1) => {\n\t\t\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tit('should not go overflow if the TCP frame contains a lot of PUBLISH packets', function _test(t, done) {\n\t\t\tconst parser = mqttPacket.parser()\n\t\t\tconst max = 1000\n\t\t\tlet count = 0\n\t\t\tconst duplex = new Duplex({\n\t\t\t\tread(n) {},\n\t\t\t\twrite(chunk, enc, cb) {\n\t\t\t\t\tparser.parse(chunk)\n\t\t\t\t\tcb() // nothing to do\n\t\t\t\t},\n\t\t\t})\n\t\t\tclient = new mqtt.MqttClient(() => duplex, {})\n\n\t\t\tclient.on('message', (topic, p, packet) => {\n\t\t\t\tif (++count === max) {\n\t\t\t\t\t// BUGBUG: the client.end callback never gets called here\n\t\t\t\t\t// client.end((err) => done(err))\n\t\t\t\t\tclient.end()\n\t\t\t\t\tdone()\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tparser.on('packet', (packet) => {\n\t\t\t\tconst packets = []\n\n\t\t\t\tif (packet.cmd === 'connect') {\n\t\t\t\t\tduplex.push(\n\t\t\t\t\t\tmqttPacket.generate({\n\t\t\t\t\t\t\tcmd: 'connack',\n\t\t\t\t\t\t\tsessionPresent: false,\n\t\t\t\t\t\t\treturnCode: 0,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\n\t\t\t\t\tfor (let i = 0; i < max; i++) {\n\t\t\t\t\t\tpackets.push(\n\t\t\t\t\t\t\tmqttPacket.generate({\n\t\t\t\t\t\t\t\tcmd: 'publish',\n\t\t\t\t\t\t\t\ttopic: 'hello',\n\t\t\t\t\t\t\t\tpayload: Buffer.from('world'),\n\t\t\t\t\t\t\t\tretain: false,\n\t\t\t\t\t\t\t\tdup: false,\n\t\t\t\t\t\t\t\tmessageId: i + 1,\n\t\t\t\t\t\t\t\tqos: 1,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\n\t\t\t\t\tduplex.push(Buffer.concat(packets))\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t})\n\n\tdescribe('flushing', () => {\n\t\tit(\n\t\t\t'should attempt to complete pending unsub and send on ping timeout',\n\t\t\t{\n\t\t\t\ttimeout: 10000,\n\t\t\t},\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\t\tserverClient.connack({ returnCode: 0 })\n\t\t\t\t\t})\n\t\t\t\t}).listen(ports.PORTAND72)\n\n\t\t\t\tlet pubCallbackCalled = false\n\t\t\t\tlet unsubscribeCallbackCalled = false\n\t\t\t\tclient = mqtt.connect({\n\t\t\t\t\tport: ports.PORTAND72,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tkeepalive: 1,\n\t\t\t\t\tconnectTimeout: 350,\n\t\t\t\t\treconnectPeriod: 0, // disable reconnect\n\t\t\t\t})\n\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\tclient.publish(\n\t\t\t\t\t\t'fakeTopic',\n\t\t\t\t\t\t'fakeMessage',\n\t\t\t\t\t\t{ qos: 1 },\n\t\t\t\t\t\t(err) => {\n\t\t\t\t\t\t\t// connection closed\n\t\t\t\t\t\t\tassert.exists(err)\n\t\t\t\t\t\t\tpubCallbackCalled = true\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\t\t\t\t\tclient.unsubscribe('fakeTopic', (err, result) => {\n\t\t\t\t\t\t// connection closed\n\t\t\t\t\t\tassert.exists(err)\n\t\t\t\t\t\tunsubscribeCallbackCalled = true\n\t\t\t\t\t})\n\n\t\t\t\t\tclient.once('error', (err) => {\n\t\t\t\t\t\tassert.equal(err.message, 'Keepalive timeout')\n\t\t\t\t\t\tconst originalFLush = client['_flush']\n\t\t\t\t\t\t// flush will be called on _cleanUp because of keepalive timeout\n\t\t\t\t\t\tclient['_flush'] = function _flush() {\n\t\t\t\t\t\t\toriginalFLush.call(client)\n\t\t\t\t\t\t\tclient.end((err1) => {\n\t\t\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\t\t\tpubCallbackCalled &&\n\t\t\t\t\t\t\t\t\t\tunsubscribeCallbackCalled,\n\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t'callbacks should be invoked with error',\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\t})\n\n\tdescribe('reconnecting', () => {\n\t\tit(\n\t\t\t'should attempt to reconnect once server is down',\n\t\t\t{\n\t\t\t\ttimeout: 5000,\n\t\t\t},\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst args = ['-r', 'ts-node/register']\n\n\t\t\t\tif (process.env.DEBUG_SERVER) {\n\t\t\t\t\targs.unshift('--inspect')\n\t\t\t\t}\n\t\t\t\tconst innerServer = fork(\n\t\t\t\t\tpath.join(__dirname, 'helpers', 'server_process.ts'),\n\t\t\t\t\t{\n\t\t\t\t\t\texecArgv: args,\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tinnerServer.on('close', (code) => {\n\t\t\t\t\tif (code) {\n\t\t\t\t\t\tdone(\n\t\t\t\t\t\t\tutil.format(\n\t\t\t\t\t\t\t\t'child process closed with code %d',\n\t\t\t\t\t\t\t\tcode,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tinnerServer.on('exit', (code) => {\n\t\t\t\t\tif (code) {\n\t\t\t\t\t\tdone(\n\t\t\t\t\t\t\tutil.format(\n\t\t\t\t\t\t\t\t'child process exited with code %d',\n\t\t\t\t\t\t\t\tcode,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tclient = mqtt.connect({\n\t\t\t\t\tport: 3481,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tkeepalive: 1,\n\t\t\t\t})\n\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\tinnerServer.kill('SIGINT') // mocks server shutdown\n\t\t\t\t\tclient.once('close', () => {\n\t\t\t\t\t\tassert.exists(client['reconnectTimer'])\n\t\t\t\t\t\tclient.end(true, (err) => done(err))\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\n\t\tit(\n\t\t\t'should reconnect if a connack is not received in an interval',\n\t\t\t{\n\t\t\t\ttimeout: 2000,\n\t\t\t},\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst server2 = net.createServer().listen(ports.PORTAND43)\n\n\t\t\t\tserver2.on('connection', (c) => {\n\t\t\t\t\teos(c, () => {\n\t\t\t\t\t\tserver2.close()\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tserver2.on('listening', () => {\n\t\t\t\t\tclient = mqtt.connect({\n\t\t\t\t\t\tservers: [\n\t\t\t\t\t\t\t{ port: ports.PORTAND43, host: 'localhost_fake' },\n\t\t\t\t\t\t\t{ port: ports.PORT, host: 'localhost' },\n\t\t\t\t\t\t],\n\t\t\t\t\t\tconnectTimeout: 500,\n\t\t\t\t\t})\n\n\t\t\t\t\tserver.once('client', () => {\n\t\t\t\t\t\tclient.end(false, (err) => {\n\t\t\t\t\t\t\tdone(err)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\n\t\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\t\tclient.stream.destroy()\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\n\t\tit(\n\t\t\t'should not be cleared by the connack timer',\n\t\t\t{\n\t\t\t\ttimeout: 4000,\n\t\t\t},\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst server2 = net.createServer().listen(ports.PORTAND44)\n\n\t\t\t\tserver2.on('connection', (c) => {\n\t\t\t\t\tc.destroy()\n\t\t\t\t})\n\n\t\t\t\tserver2.once('listening', () => {\n\t\t\t\t\tconst connectTimeout = 1000\n\t\t\t\t\tconst reconnectPeriod = 100\n\t\t\t\t\tconst expectedReconnects = Math.floor(\n\t\t\t\t\t\tconnectTimeout / reconnectPeriod,\n\t\t\t\t\t)\n\t\t\t\t\tlet reconnects = 0\n\t\t\t\t\tclient = mqtt.connect({\n\t\t\t\t\t\tport: ports.PORTAND44,\n\t\t\t\t\t\thost: 'localhost',\n\t\t\t\t\t\tconnectTimeout,\n\t\t\t\t\t\treconnectPeriod,\n\t\t\t\t\t})\n\n\t\t\t\t\tclient.on('reconnect', () => {\n\t\t\t\t\t\treconnects++\n\t\t\t\t\t\tif (reconnects >= expectedReconnects) {\n\t\t\t\t\t\t\tclient.end(true, (err) => done(err))\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\n\t\tit(\n\t\t\t'should not keep requeueing the first message when offline',\n\t\t\t{\n\t\t\t\ttimeout: 1000,\n\t\t\t},\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst server2 = serverBuilder('mqtt').listen(ports.PORTAND45)\n\t\t\t\tclient = mqtt.connect({\n\t\t\t\t\tport: ports.PORTAND45,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tconnectTimeout: 350,\n\t\t\t\t\treconnectPeriod: 300,\n\t\t\t\t})\n\n\t\t\t\tserver2.on('client', (serverClient) => {\n\t\t\t\t\tclient.publish('hello', 'world', { qos: 1 }, () => {\n\t\t\t\t\t\tserverClient.destroy()\n\t\t\t\t\t\tserver2.close(() => {\n\t\t\t\t\t\t\tdebug('now publishing message in an offline state')\n\t\t\t\t\t\t\tclient.publish('hello', 'world', { qos: 1 })\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tlet reconnections = 0\n\n\t\t\t\tclient.on('reconnect', () => {\n\t\t\t\t\treconnections++\n\t\t\t\t\tif (reconnections === 2) {\n\t\t\t\t\t\tif (client.queue.length === 0) {\n\t\t\t\t\t\t\tdebug('calling final client.end()')\n\t\t\t\t\t\t\tclient.end(true, (err) => done(err))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdebug('calling client.end()')\n\t\t\t\t\t\t\t// Do not call done. We want to trigger a reconnect here.\n\t\t\t\t\t\t\tclient.end(true)\n\t\t\t\t\t\t\tdone(Error('client queue not empty'))\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\n\t\tit(\n\t\t\t'should not send the same subscribe multiple times on a flaky connection',\n\t\t\t{\n\t\t\t\ttimeout: 3500,\n\t\t\t},\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst KILL_COUNT = 4\n\t\t\t\tconst subIds = {}\n\t\t\t\tlet killedConnections = 0\n\t\t\t\tclient = mqtt.connect({\n\t\t\t\t\tport: ports.PORTAND46,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tconnectTimeout: 350,\n\t\t\t\t\treconnectPeriod: 300,\n\t\t\t\t})\n\n\t\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\t\tdebug('client received on server2.')\n\t\t\t\t\tdebug('subscribing to topic `topic`')\n\t\t\t\t\tclient.subscribe('topic', () => {\n\t\t\t\t\t\tdebug(\n\t\t\t\t\t\t\t'once subscribed to topic, end client, destroy serverClient, and close server.',\n\t\t\t\t\t\t)\n\t\t\t\t\t\tserverClient.destroy()\n\t\t\t\t\t\tserver2.close(() => {\n\t\t\t\t\t\t\tclient.end(true, (err) => done(err))\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\n\t\t\t\t\tserverClient.on('subscribe', (packet) => {\n\t\t\t\t\t\tif (killedConnections < KILL_COUNT) {\n\t\t\t\t\t\t\t// Kill the first few sub attempts to simulate a flaky connection\n\t\t\t\t\t\t\tkilledConnections++\n\t\t\t\t\t\t\tserverClient.destroy()\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Keep track of acks\n\t\t\t\t\t\t\tif (!subIds[packet.messageId]) {\n\t\t\t\t\t\t\t\tsubIds[packet.messageId] = 0\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tsubIds[packet.messageId]++\n\t\t\t\t\t\t\tif (subIds[packet.messageId] > 1) {\n\t\t\t\t\t\t\t\tdone(\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t`Multiple duplicate acked subscriptions received for messageId ${packet.messageId}`,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tclient.end(true)\n\t\t\t\t\t\t\t\tserverClient.end()\n\t\t\t\t\t\t\t\tserver2.close()\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tserverClient.suback({\n\t\t\t\t\t\t\t\tmessageId: packet.messageId,\n\t\t\t\t\t\t\t\tgranted: packet.subscriptions.map((e) => e.qos),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}).listen(ports.PORTAND46)\n\t\t\t},\n\t\t)\n\n\t\tit(\n\t\t\t'should not fill the queue of subscribes if it cannot connect',\n\t\t\t{\n\t\t\t\ttimeout: 2500,\n\t\t\t},\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst server2 = net.createServer((stream) => {\n\t\t\t\t\tconst serverClient = new Connection(stream)\n\n\t\t\t\t\tserverClient.on('error', (e) => {\n\t\t\t\t\t\t/* do nothing */\n\t\t\t\t\t})\n\t\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\t\tserverClient.connack({ returnCode: 0 })\n\t\t\t\t\t\tserverClient.destroy()\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tserver2.listen(ports.PORTAND48, () => {\n\t\t\t\t\tclient = mqtt.connect({\n\t\t\t\t\t\tport: ports.PORTAND48,\n\t\t\t\t\t\thost: 'localhost',\n\t\t\t\t\t\tconnectTimeout: 350,\n\t\t\t\t\t\treconnectPeriod: 300,\n\t\t\t\t\t})\n\n\t\t\t\t\tclient.subscribe('hello')\n\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tassert.equal(client.queue.length, 1)\n\t\t\t\t\t\tclient.end(true, (err) => done(err))\n\t\t\t\t\t}, 1000)\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\n\t\tit(\n\t\t\t'should not send the same publish multiple times on a flaky connection',\n\t\t\t{\n\t\t\t\ttimeout: 3500,\n\t\t\t},\n\t\t\tfunction _test(t, done) {\n\t\t\t\tconst KILL_COUNT = 4\n\t\t\t\tlet killedConnections = 0\n\t\t\t\tconst pubIds = {}\n\t\t\t\tclient = mqtt.connect({\n\t\t\t\t\tport: ports.PORTAND47,\n\t\t\t\t\thost: 'localhost',\n\t\t\t\t\tconnectTimeout: 350,\n\t\t\t\t\treconnectPeriod: 300,\n\t\t\t\t})\n\n\t\t\t\tconst server2 = net\n\t\t\t\t\t.createServer((stream) => {\n\t\t\t\t\t\tconst serverClient = new Connection(stream)\n\t\t\t\t\t\tserverClient.on('error', () => {})\n\t\t\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\t\t\tif (packet.clientId === 'invalid') {\n\t\t\t\t\t\t\t\tserverClient.connack({ returnCode: 2 })\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tserverClient.connack({ returnCode: 0 })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\n\t\t\t\t\t\tserver2.emit('client', serverClient)\n\t\t\t\t\t})\n\t\t\t\t\t.listen(ports.PORTAND47)\n\n\t\t\t\tserver2.on('client', (serverClient) => {\n\t\t\t\t\tclient.publish('topic', 'data', { qos: 1 }, () => {\n\t\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\n\t\t\t\t\tserverClient.on('publish', function onPublish(packet) {\n\t\t\t\t\t\tif (killedConnections < KILL_COUNT) {\n\t\t\t\t\t\t\t// Kill the first few pub attempts to simulate a flaky connection\n\t\t\t\t\t\t\tkilledConnections++\n\t\t\t\t\t\t\tserverClient.destroy()\n\n\t\t\t\t\t\t\t// to avoid receiving inflight messages\n\t\t\t\t\t\t\tserverClient.removeListener('publish', onPublish)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Keep track of acks\n\t\t\t\t\t\t\tif (!pubIds[packet.messageId]) {\n\t\t\t\t\t\t\t\tpubIds[packet.messageId] = 0\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tpubIds[packet.messageId]++\n\n\t\t\t\t\t\t\tif (pubIds[packet.messageId] > 1) {\n\t\t\t\t\t\t\t\tdone(\n\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t`Multiple duplicate acked publishes received for messageId ${packet.messageId}`,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tclient.end(true)\n\t\t\t\t\t\t\t\tserverClient.destroy()\n\t\t\t\t\t\t\t\tserver2.close()\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tserverClient.puback(packet)\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\t})\n\n\tit(\n\t\t'check emit error on checkDisconnection w/o callback',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst server2 = new MqttServer((c) => {\n\t\t\t\tc.on('connect', (packet) => {\n\t\t\t\t\tc.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tc.on('publish', (packet) => {\n\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\tpacket.reasonCode = 0\n\t\t\t\t\t\tc.puback(packet)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND118)\n\n\t\t\tconst opts: IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND118,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t}\n\t\t\tclient = mqtt.connect(opts)\n\n\t\t\t// wait for the client to receive an error...\n\t\t\tclient.on('error', (error) => {\n\t\t\t\tassert.equal(error.message, 'client disconnecting')\n\t\t\t\tserver2.close((err) => done(err))\n\t\t\t})\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.end(() => {\n\t\t\t\t\tclient['_checkDisconnecting']()\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\t)\n\n\tdescribe('connect manually', () => {\n\t\tit(\n\t\t\t'should not throw an error when publish after second connect',\n\t\t\t{\n\t\t\t\ttimeout: 10000,\n\t\t\t},\n\t\t\tasync function _test(t) {\n\t\t\t\tconst clock = useFakeTimers({\n\t\t\t\t\tshouldClearNativeTimers: true,\n\t\t\t\t\ttoFake: ['setTimeout'],\n\t\t\t\t})\n\n\t\t\t\tt.after(async () => {\n\t\t\t\t\tclock.restore()\n\t\t\t\t\tif (client) {\n\t\t\t\t\t\tawait client.endAsync(true)\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tconst fail = await new Promise<boolean>((resolveParent) => {\n\t\t\t\t\tlet countConnects = 0\n\n\t\t\t\t\tconst publishInterval = (\n\t\t\t\t\t\trepetible: number,\n\t\t\t\t\t\ttimeout: number,\n\t\t\t\t\t\tcallback: (threwError: boolean) => void,\n\t\t\t\t\t): void => {\n\t\t\t\t\t\tconst method = () =>\n\t\t\t\t\t\t\tnew Promise<boolean>((resolve) => {\n\t\t\t\t\t\t\t\tclient.publish('test', 'test', (err) => {\n\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\terr?.message.toLocaleLowerCase() ===\n\t\t\t\t\t\t\t\t\t\t'client disconnecting'\n\t\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\t\tresolve(true)\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tresolve(false)\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t})\n\n\t\t\t\t\t\tif (repetible <= 0) {\n\t\t\t\t\t\t\tcallback(false)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tmethod().then((threwError) => {\n\t\t\t\t\t\t\tclock.tick(timeout)\n\n\t\t\t\t\t\t\tif (threwError) {\n\t\t\t\t\t\t\t\tcallback(true)\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tpublishInterval(repetible - 1, timeout, callback)\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\n\t\t\t\t\tclient = mqtt.connect(config)\n\n\t\t\t\t\tclient.on('connect', () => {\n\t\t\t\t\t\t++countConnects\n\n\t\t\t\t\t\tconst intervalRepetible = 4\n\t\t\t\t\t\tconst intervalTimeout = 250\n\t\t\t\t\t\tconst connectTimeout =\n\t\t\t\t\t\t\tintervalRepetible * intervalTimeout\n\n\t\t\t\t\t\tpublishInterval(\n\t\t\t\t\t\t\tintervalRepetible,\n\t\t\t\t\t\t\tintervalTimeout,\n\t\t\t\t\t\t\t(threwError) => {\n\t\t\t\t\t\t\t\tif (countConnects === 2) {\n\t\t\t\t\t\t\t\t\tresolveParent(threwError)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\tif (countConnects === 1) {\n\t\t\t\t\t\t\tclock.setTimeout(() => {\n\t\t\t\t\t\t\t\tclient.end(() => client.connect())\n\t\t\t\t\t\t\t}, connectTimeout)\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tassert.isFalse(fail, 'disconnecting variable was not reset')\n\t\t\t},\n\t\t)\n\n\t\tit(\n\t\t\t'reset disconnecting variable to false after disconnect when option reconnectPeriod=0',\n\t\t\t{\n\t\t\t\ttimeout: 10000,\n\t\t\t},\n\t\t\tasync function _test(t) {\n\t\t\t\tclient = await mqtt.connectAsync({\n\t\t\t\t\t...config,\n\t\t\t\t\treconnectPeriod: 0,\n\t\t\t\t})\n\n\t\t\t\tassert.isFalse(\n\t\t\t\t\tclient.disconnecting,\n\t\t\t\t\t'disconnecting should be false after connect',\n\t\t\t\t)\n\n\t\t\t\tconst endPromise = client.endAsync()\n\n\t\t\t\tassert.isTrue(\n\t\t\t\t\tclient.disconnecting,\n\t\t\t\t\t'disconnecting should be true processing end',\n\t\t\t\t)\n\n\t\t\t\tawait endPromise\n\n\t\t\t\tassert.isFalse(\n\t\t\t\t\tclient.disconnecting,\n\t\t\t\t\t'disconnecting should be false after end',\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\n\t\tit(\n\t\t\t'reset disconnecting variable to false after disconnect when option manualConnect=true',\n\t\t\t{\n\t\t\t\ttimeout: 10000,\n\t\t\t},\n\t\t\tasync function _test(t) {\n\t\t\t\tclient = mqtt.connect({\n\t\t\t\t\t...config,\n\t\t\t\t\tmanualConnect: true,\n\t\t\t\t})\n\n\t\t\t\tawait new Promise((resolve, reject) => {\n\t\t\t\t\tclient\n\t\t\t\t\t\t.connect()\n\t\t\t\t\t\t.on('error', (err) => {\n\t\t\t\t\t\t\treject(err)\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.once('connect', () => {\n\t\t\t\t\t\t\tresolve(undefined)\n\t\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tassert.isFalse(\n\t\t\t\t\tclient.disconnecting,\n\t\t\t\t\t'disconnecting should be false after connect',\n\t\t\t\t)\n\n\t\t\t\tconst endPromise = client.endAsync()\n\n\t\t\t\tassert.isTrue(\n\t\t\t\t\tclient.disconnecting,\n\t\t\t\t\t'disconnecting should be true processing end',\n\t\t\t\t)\n\n\t\t\t\tawait endPromise\n\n\t\t\t\tassert.isFalse(\n\t\t\t\t\tclient.disconnecting,\n\t\t\t\t\t'disconnecting should be false after end',\n\t\t\t\t)\n\t\t\t},\n\t\t)\n\t})\n\n\tdescribe('async methods', () => {\n\t\tit(\n\t\t\t'connect-subscribe-unsubscribe-end',\n\t\t\t{\n\t\t\t\ttimeout: 15000,\n\t\t\t},\n\t\t\tfunction _test(t) {\n\t\t\t\treturn new Promise<void>(async (resolve, reject) => {\n\t\t\t\t\tserver.once('client', (serverClient) => {\n\t\t\t\t\t\tserverClient.on('publish', async (packet) => {\n\t\t\t\t\t\t\tassert.equal(packet.topic, 'hello')\n\t\t\t\t\t\t\tassert.equal(packet.payload.toString(), 'world')\n\t\t\t\t\t\t\tawait client.unsubscribeAsync('hello')\n\t\t\t\t\t\t\tawait client.endAsync()\n\t\t\t\t\t\t\tresolve()\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\n\t\t\t\t\tclient = await mqtt.connectAsync(config)\n\n\t\t\t\t\tconst sub = await client.subscribeAsync('hello')\n\n\t\t\t\t\tassert.equal(sub[0].topic, 'hello')\n\t\t\t\t\tassert.equal(sub[0].qos, 0)\n\n\t\t\t\t\tawait client.publishAsync('hello', 'world')\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\n\t\tit(\n\t\t\t'connect should throw error',\n\t\t\t{\n\t\t\t\ttimeout: 5000,\n\t\t\t},\n\t\t\tasync function _test(t) {\n\t\t\t\tlet error = false\n\n\t\t\t\ttry {\n\t\t\t\t\tawait mqtt.connectAsync({\n\t\t\t\t\t\tport: 1000,\n\t\t\t\t\t\thost: '127.0.0.1',\n\t\t\t\t\t\treconnectPeriod: 0,\n\t\t\t\t\t})\n\t\t\t\t} catch (err) {\n\t\t\t\t\terror = true\n\t\t\t\t\tassert.isTrue(err.message.includes('ECONNREFUSED'))\n\t\t\t\t}\n\n\t\t\t\tassert.isTrue(error)\n\t\t\t},\n\t\t)\n\n\t\tit(\n\t\t\t'publish should throw error',\n\t\t\t{\n\t\t\t\ttimeout: 5000,\n\t\t\t},\n\t\t\tasync function _test(t) {\n\t\t\t\tlet error = false\n\n\t\t\t\ttry {\n\t\t\t\t\tclient = await mqtt.connectAsync(config)\n\t\t\t\t\tclient.disconnecting = true\n\t\t\t\t\tawait client.publishAsync('#/#', 'world')\n\t\t\t\t} catch (err) {\n\t\t\t\t\terror = true\n\t\t\t\t\tclient.disconnecting = false\n\t\t\t\t\tassert.equal(err.message, 'client disconnecting')\n\t\t\t\t}\n\n\t\t\t\tawait client.endAsync()\n\n\t\t\t\tassert.isTrue(error)\n\t\t\t},\n\t\t)\n\t})\n})\n"
  },
  {
    "path": "test/node/client_mqtt5.ts",
    "content": "import { assert } from 'chai'\nimport { after, describe, it } from 'node:test'\nimport abstractClientTests from './abstract_client'\nimport { MqttServer } from './server'\nimport serverBuilder from './server_helpers_for_client_tests'\nimport getPorts from './helpers/port_list'\nimport mqtt, { type ErrorWithReasonCode } from '../../src'\n\nconst ports = getPorts(1)\n\ndescribe('MQTT 5.0', () => {\n\tconst server = serverBuilder('mqtt').listen(ports.PORTAND115)\n\tconst config = {\n\t\tprotocol: 'mqtt',\n\t\tport: ports.PORTAND115,\n\t\tprotocolVersion: 5,\n\t\tproperties: { maximumPacketSize: 200 },\n\t}\n\n\tafter(() => {\n\t\t// clean up and make sure the server is no longer listening...\n\t\tif (server.listening) {\n\t\t\tserver.close()\n\t\t}\n\n\t\tprocess.exit(0)\n\t})\n\n\tabstractClientTests(server, config, ports)\n\n\tit(\n\t\t'topic should be complemented on receive',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND103,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tproperties: {\n\t\t\t\t\ttopicAliasMaximum: 3,\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tlet publishCount = 0\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tassert.strictEqual(packet.properties.topicAliasMaximum, 3)\n\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t})\n\t\t\t\t\t// register topicAlias\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\tmessageId: 0,\n\t\t\t\t\t\ttopic: 'test1',\n\t\t\t\t\t\tpayload: 'Message',\n\t\t\t\t\t\tqos: 0,\n\t\t\t\t\t\tproperties: { topicAlias: 1 },\n\t\t\t\t\t})\n\t\t\t\t\t// use topicAlias\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\tmessageId: 0,\n\t\t\t\t\t\ttopic: '',\n\t\t\t\t\t\tpayload: 'Message',\n\t\t\t\t\t\tqos: 0,\n\t\t\t\t\t\tproperties: { topicAlias: 1 },\n\t\t\t\t\t})\n\t\t\t\t\t// overwrite registered topicAlias\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\tmessageId: 0,\n\t\t\t\t\t\ttopic: 'test2',\n\t\t\t\t\t\tpayload: 'Message',\n\t\t\t\t\t\tqos: 0,\n\t\t\t\t\t\tproperties: { topicAlias: 1 },\n\t\t\t\t\t})\n\t\t\t\t\t// use topicAlias\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\tmessageId: 0,\n\t\t\t\t\t\ttopic: '',\n\t\t\t\t\t\tpayload: 'Message',\n\t\t\t\t\t\tqos: 0,\n\t\t\t\t\t\tproperties: { topicAlias: 1 },\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND103)\n\n\t\t\tclient.on('message', (topic, messagee, packet) => {\n\t\t\t\tswitch (publishCount++) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tassert.strictEqual(topic, 'test1')\n\t\t\t\t\t\tassert.strictEqual(packet.topic, 'test1')\n\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 1)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\tassert.strictEqual(topic, 'test1')\n\t\t\t\t\t\tassert.strictEqual(packet.topic, '')\n\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 1)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\tassert.strictEqual(topic, 'test2')\n\t\t\t\t\t\tassert.strictEqual(packet.topic, 'test2')\n\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 1)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\tassert.strictEqual(topic, 'test2')\n\t\t\t\t\t\tassert.strictEqual(packet.topic, '')\n\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 1)\n\t\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'registered topic alias should automatically used if autoUseTopicAlias is true',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND103,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tautoUseTopicAlias: true,\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\n\t\t\tlet publishCount = 0\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\ttopicAliasMaximum: 3,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\tswitch (publishCount++) {\n\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, 'test1')\n\t\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 1)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, '')\n\t\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 1)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, '')\n\t\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 1)\n\t\t\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND103)\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\t// register topicAlias\n\t\t\t\tclient.publish('test1', 'Message', {\n\t\t\t\t\tproperties: { topicAlias: 1 },\n\t\t\t\t})\n\t\t\t\t// use topicAlias\n\t\t\t\tclient.publish('', 'Message', { properties: { topicAlias: 1 } })\n\t\t\t\t// use topicAlias by autoApplyTopicAlias\n\t\t\t\tclient.publish('test1', 'Message')\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'topicAlias is automatically used if autoAssignTopicAlias is true',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND103,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tautoAssignTopicAlias: true,\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\n\t\t\tlet publishCount = 0\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\ttopicAliasMaximum: 3,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\tswitch (publishCount++) {\n\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, 'test1')\n\t\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 1)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, 'test2')\n\t\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 2)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 2:\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, 'test3')\n\t\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 3)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 3:\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, '')\n\t\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 1)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 4:\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, '')\n\t\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 3)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 5:\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, 'test4')\n\t\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 2)\n\t\t\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND103)\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\t// register topicAlias\n\t\t\t\tclient.publish('test1', 'Message')\n\t\t\t\tclient.publish('test2', 'Message')\n\t\t\t\tclient.publish('test3', 'Message')\n\n\t\t\t\t// use topicAlias\n\t\t\t\tclient.publish('test1', 'Message')\n\t\t\t\tclient.publish('test3', 'Message')\n\n\t\t\t\t// renew LRU topicAlias\n\t\t\t\tclient.publish('test4', 'Message')\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'topicAlias should be removed and topic restored on resend',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst incomingStore = new mqtt.Store({ clean: false })\n\t\t\tconst outgoingStore = new mqtt.Store({ clean: false })\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND103,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tclientId: 'cid1',\n\t\t\t\tincomingStore,\n\t\t\t\toutgoingStore,\n\t\t\t\tclean: false,\n\t\t\t\treconnectPeriod: 100,\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\n\t\t\tlet connectCount = 0\n\t\t\tlet publishCount = 0\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tswitch (connectCount++) {\n\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\t\t\tsessionPresent: false,\n\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\ttopicAliasMaximum: 3,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\t\t\tsessionPresent: true,\n\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\ttopicAliasMaximum: 3,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\tswitch (publishCount++) {\n\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, 'test1')\n\t\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 1)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, '')\n\t\t\t\t\t\t\tassert.strictEqual(packet.properties.topicAlias, 1)\n\t\t\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\t\t\tserverClient.stream.destroy()\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 2: {\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, 'test1')\n\t\t\t\t\t\t\tlet alias1\n\t\t\t\t\t\t\tif (packet.properties) {\n\t\t\t\t\t\t\t\talias1 = packet.properties.topicAlias\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.strictEqual(alias1, undefined)\n\t\t\t\t\t\t\tserverClient.puback({ messageId: packet.messageId })\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase 3: {\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, 'test1')\n\t\t\t\t\t\t\tlet alias2\n\t\t\t\t\t\t\tif (packet.properties) {\n\t\t\t\t\t\t\t\talias2 = packet.properties.topicAlias\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.strictEqual(alias2, undefined)\n\t\t\t\t\t\t\tserverClient.puback({ messageId: packet.messageId })\n\t\t\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND103)\n\n\t\t\tclient.once('connect', () => {\n\t\t\t\t// register topicAlias\n\t\t\t\tclient.publish('test1', 'Message', {\n\t\t\t\t\tqos: 1,\n\t\t\t\t\tproperties: { topicAlias: 1 },\n\t\t\t\t})\n\t\t\t\t// use topicAlias\n\t\t\t\tclient.publish('', 'Message', {\n\t\t\t\t\tqos: 1,\n\t\t\t\t\tproperties: { topicAlias: 1 },\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'topicAlias should be removed and topic restored on offline publish',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst incomingStore = new mqtt.Store({ clean: false })\n\t\t\tconst outgoingStore = new mqtt.Store({ clean: false })\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND103,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tclientId: 'cid1',\n\t\t\t\tincomingStore,\n\t\t\t\toutgoingStore,\n\t\t\t\tclean: false,\n\t\t\t\treconnectPeriod: 100,\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\n\t\t\tlet connectCount = 0\n\t\t\tlet publishCount = 0\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tswitch (connectCount++) {\n\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\t\t\tsessionPresent: false,\n\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\ttopicAliasMaximum: 3,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tsetImmediate(() => {\n\t\t\t\t\t\t\t\tserverClient.stream.destroy()\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\t\t\tsessionPresent: true,\n\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\ttopicAliasMaximum: 3,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tserverClient.on('publish', (packet) => {\n\t\t\t\t\tswitch (publishCount++) {\n\t\t\t\t\t\tcase 0: {\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, 'test1')\n\t\t\t\t\t\t\tlet alias1\n\t\t\t\t\t\t\tif (packet.properties) {\n\t\t\t\t\t\t\t\talias1 = packet.properties.topicAlias\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.strictEqual(alias1, undefined)\n\t\t\t\t\t\t\tassert.strictEqual(packet.qos, 1)\n\t\t\t\t\t\t\tserverClient.puback({ messageId: packet.messageId })\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase 1: {\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, 'test1')\n\t\t\t\t\t\t\tlet alias2\n\t\t\t\t\t\t\tif (packet.properties) {\n\t\t\t\t\t\t\t\talias2 = packet.properties.topicAlias\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.strictEqual(alias2, undefined)\n\t\t\t\t\t\t\tassert.strictEqual(packet.qos, 0)\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase 2: {\n\t\t\t\t\t\t\tassert.strictEqual(packet.topic, 'test1')\n\t\t\t\t\t\t\tlet alias3\n\t\t\t\t\t\t\tif (packet.properties) {\n\t\t\t\t\t\t\t\talias3 = packet.properties.topicAlias\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tassert.strictEqual(alias3, undefined)\n\t\t\t\t\t\t\tassert.strictEqual(packet.qos, 0)\n\t\t\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND103)\n\n\t\t\tclient.once('close', () => {\n\t\t\t\t// register topicAlias\n\t\t\t\tclient.publish('test1', 'Message', {\n\t\t\t\t\tqos: 0,\n\t\t\t\t\tproperties: { topicAlias: 1 },\n\t\t\t\t})\n\t\t\t\t// use topicAlias\n\t\t\t\tclient.publish('', 'Message', {\n\t\t\t\t\tqos: 0,\n\t\t\t\t\tproperties: { topicAlias: 1 },\n\t\t\t\t})\n\t\t\t\tclient.publish('', 'Message', {\n\t\t\t\t\tqos: 1,\n\t\t\t\t\tproperties: { topicAlias: 1 },\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'should error cb call if PUBLISH out of range topicAlias',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND103,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\tsessionPresent: false,\n\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\ttopicAliasMaximum: 3,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND103)\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\t// register topicAlias\n\t\t\t\tclient.publish(\n\t\t\t\t\t'test1',\n\t\t\t\t\t'Message',\n\t\t\t\t\t{ properties: { topicAlias: 4 } },\n\t\t\t\t\t(error) => {\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\terror.message,\n\t\t\t\t\t\t\t'Sending Topic Alias out of range',\n\t\t\t\t\t\t)\n\t\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'should error cb call if PUBLISH out of range topicAlias on topicAlias disabled by broker',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND103,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\tsessionPresent: false,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND103)\n\n\t\t\tclient.on('connect', () => {\n\t\t\t\t// register topicAlias\n\t\t\t\tclient.publish(\n\t\t\t\t\t'test1',\n\t\t\t\t\t'Message',\n\t\t\t\t\t{ properties: { topicAlias: 1 } },\n\t\t\t\t\t(error) => {\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\terror.message,\n\t\t\t\t\t\t\t'Sending Topic Alias out of range',\n\t\t\t\t\t\t)\n\t\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'should throw an error if broker PUBLISH out of range topicAlias',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND103,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tproperties: {\n\t\t\t\t\ttopicAliasMaximum: 3,\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\tsessionPresent: false,\n\t\t\t\t\t})\n\t\t\t\t\t// register out of range topicAlias\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\tmessageId: 0,\n\t\t\t\t\t\ttopic: 'test1',\n\t\t\t\t\t\tpayload: 'Message',\n\t\t\t\t\t\tqos: 0,\n\t\t\t\t\t\tproperties: { topicAlias: 4 },\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND103)\n\n\t\t\tclient.on('error', (error) => {\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\terror.message,\n\t\t\t\t\t'Received Topic Alias is out of range',\n\t\t\t\t)\n\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'should throw an error if broker PUBLISH topicAlias:0',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND103,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tproperties: {\n\t\t\t\t\ttopicAliasMaximum: 3,\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\tsessionPresent: false,\n\t\t\t\t\t})\n\t\t\t\t\t// register out of range topicAlias\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\tmessageId: 0,\n\t\t\t\t\t\ttopic: 'test1',\n\t\t\t\t\t\tpayload: 'Message',\n\t\t\t\t\t\tqos: 0,\n\t\t\t\t\t\tproperties: { topicAlias: 0 },\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND103)\n\n\t\t\tclient.on('error', (error) => {\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\terror.message,\n\t\t\t\t\t'Received Topic Alias is out of range',\n\t\t\t\t)\n\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'should throw an error if broker PUBLISH unregistered topicAlias',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND103,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tproperties: {\n\t\t\t\t\ttopicAliasMaximum: 3,\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\tsessionPresent: false,\n\t\t\t\t\t})\n\t\t\t\t\t// register out of range topicAlias\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\tmessageId: 0,\n\t\t\t\t\t\ttopic: '', // use topic alias\n\t\t\t\t\t\tpayload: 'Message',\n\t\t\t\t\t\tqos: 0,\n\t\t\t\t\t\tproperties: { topicAlias: 1 }, // in range topic alias\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND103)\n\n\t\t\tclient.on('error', (error) => {\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\terror.message,\n\t\t\t\t\t'Received unregistered Topic Alias',\n\t\t\t\t)\n\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'should throw an error if there is Auth Data with no Auth Method',\n\t\t{\n\t\t\ttimeout: 5000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND115,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tproperties: { authenticationData: Buffer.from([1, 2, 3, 4]) },\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tclient.on('error', (error) => {\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\terror.message,\n\t\t\t\t\t'Packet has no Authentication Method',\n\t\t\t\t)\n\t\t\t\t// client will not be connected, so we will call done.\n\t\t\t\tassert.isTrue(\n\t\t\t\t\tclient.disconnected,\n\t\t\t\t\t'validate client is disconnected',\n\t\t\t\t)\n\t\t\t\tclient.end(true, done)\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'auth packet',\n\t\t{\n\t\t\ttimeout: 2500,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND115,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tproperties: { authenticationMethod: 'json' },\n\t\t\t\tauthPacket: {},\n\t\t\t\tmanualConnect: true,\n\t\t\t}\n\t\t\tlet authSent = false\n\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tserver.once('client', (c) => {\n\t\t\t\t// this test is flaky, there is a race condition\n\t\t\t\t// that could make the test fail as the auth packet\n\t\t\t\t// is sent by the client even before connack so it could arrive before\n\t\t\t\t// the clientServer is listening for the auth packet. To avoid this\n\t\t\t\t// if the event is not emitted we simply check if\n\t\t\t\t// the auth packet is sent after 1 second.\n\t\t\t\tlet closeTimeout = setTimeout(() => {\n\t\t\t\t\tassert.isTrue(authSent)\n\t\t\t\t\tcloseTimeout = null\n\t\t\t\t\tclient.end(true, done)\n\t\t\t\t}, 1000)\n\n\t\t\t\tc.on('auth', (packet) => {\n\t\t\t\t\tif (closeTimeout) {\n\t\t\t\t\t\tclearTimeout(closeTimeout)\n\t\t\t\t\t\tclient.end(done)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t})\n\t\t\tclient.on('packetsend', (packet) => {\n\t\t\t\tif (packet.cmd === 'auth') {\n\t\t\t\t\tauthSent = true\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tclient.connect()\n\t\t},\n\t)\n\n\tit(\n\t\t'Maximum Packet Size',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND115,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tproperties: { maximumPacketSize: 1 },\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tclient.on('error', (error) => {\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\terror.message,\n\t\t\t\t\t'exceeding packets size connack',\n\t\t\t\t)\n\t\t\t\tclient.end(true, done)\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'Change values of some properties by server response',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\tserverKeepAlive: 16,\n\t\t\t\t\t\t\tmaximumPacketSize: 95,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND116)\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND116,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tproperties: {\n\t\t\t\t\ttopicAliasMaximum: 10,\n\t\t\t\t\t// serverKeepAlive: 11,\n\t\t\t\t\tmaximumPacketSize: 100,\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tclient.on('connect', () => {\n\t\t\t\tassert.strictEqual(client.options.keepalive, 16)\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\tclient.options.properties.maximumPacketSize,\n\t\t\t\t\t95,\n\t\t\t\t)\n\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'should resubscribe when reconnecting with protocolVersion 5 and Session Present flag is false',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tlet tryReconnect = true\n\t\t\tlet reconnectEvent = false\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\tsessionPresent: false,\n\t\t\t\t\t})\n\t\t\t\t\tserverClient.on('subscribe', () => {\n\t\t\t\t\t\tif (!tryReconnect) {\n\t\t\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND316)\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND316,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\n\t\t\tclient.on('reconnect', () => {\n\t\t\t\treconnectEvent = true\n\t\t\t})\n\n\t\t\tclient.on('connect', (connack) => {\n\t\t\t\tassert.isFalse(connack.sessionPresent)\n\t\t\t\tif (tryReconnect) {\n\t\t\t\t\tclient.subscribe('hello', () => {\n\t\t\t\t\t\tclient.stream.end()\n\t\t\t\t\t})\n\n\t\t\t\t\ttryReconnect = false\n\t\t\t\t} else {\n\t\t\t\t\tassert.isTrue(reconnectEvent)\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'should resubscribe when reconnecting with protocolVersion 5 and properties',\n\t\t{\n\t\t\t// timeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\t// this.timeout(15000)\n\t\t\tlet tryReconnect = true\n\t\t\tlet reconnectEvent = false\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t\tsessionPresent: false,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tserverClient.on('subscribe', (packet) => {\n\t\t\t\t\tif (!reconnectEvent) {\n\t\t\t\t\t\tserverClient.suback({\n\t\t\t\t\t\t\tmessageId: packet.messageId,\n\t\t\t\t\t\t\tgranted: packet.subscriptions.map((e) => e.qos),\n\t\t\t\t\t\t})\n\t\t\t\t\t} else if (!tryReconnect) {\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\tpacket.properties.userProperties.test,\n\t\t\t\t\t\t\t'test',\n\t\t\t\t\t\t)\n\t\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND326)\n\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND326,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\n\t\t\tclient.on('reconnect', () => {\n\t\t\t\treconnectEvent = true\n\t\t\t})\n\n\t\t\tclient.on('connect', (connack) => {\n\t\t\t\tassert.isFalse(connack.sessionPresent)\n\t\t\t\tif (tryReconnect) {\n\t\t\t\t\tclient.subscribe(\n\t\t\t\t\t\t'hello',\n\t\t\t\t\t\t{ properties: { userProperties: { test: 'test' } } },\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\tclient.stream.end()\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\n\t\t\t\t\ttryReconnect = false\n\t\t\t\t} else {\n\t\t\t\t\tassert.isTrue(reconnectEvent)\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t)\n\n\tconst serverThatSendsErrors = new MqttServer((serverClient) => {\n\t\tserverClient.on('connect', (packet) => {\n\t\t\tserverClient.connack({\n\t\t\t\treasonCode: 0,\n\t\t\t})\n\t\t})\n\t\tserverClient.on('publish', (packet) => {\n\t\t\tsetImmediate(() => {\n\t\t\t\tswitch (packet.qos) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\tpacket.reasonCode = 142\n\t\t\t\t\t\tdelete packet.cmd\n\t\t\t\t\t\tserverClient.puback(packet)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\tpacket.reasonCode = 142\n\t\t\t\t\t\tdelete packet.cmd\n\t\t\t\t\t\tserverClient.pubrec(packet)\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tserverClient.on('pubrel', (packet) => {\n\t\t\tpacket.reasonCode = 142\n\t\t\tdelete packet.cmd\n\t\t\tserverClient.pubcomp(packet)\n\t\t})\n\t})\n\n\tit(\n\t\t'Subscribe properties',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND119,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t}\n\t\t\tconst subOptions = { properties: { subscriptionIdentifier: 1234 } }\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tserverClient.on('subscribe', (packet) => {\n\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\tpacket.properties.subscriptionIdentifier,\n\t\t\t\t\t\tsubOptions.properties.subscriptionIdentifier,\n\t\t\t\t\t)\n\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\tserver2.close((err2) => {\n\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t}).listen(ports.PORTAND119)\n\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.subscribe('a/b', subOptions)\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'puback handling errors check',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tserverThatSendsErrors.listen(ports.PORTAND117)\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND117,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.publish(\n\t\t\t\t\t'a/b',\n\t\t\t\t\t'message',\n\t\t\t\t\t{ qos: 1 },\n\t\t\t\t\t(err: ErrorWithReasonCode) => {\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\terr.message,\n\t\t\t\t\t\t\t'Publish error: Session taken over',\n\t\t\t\t\t\t)\n\t\t\t\t\t\tassert.strictEqual(err.code, 142)\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\tserverThatSendsErrors.close((err2) => {\n\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'pubrec handling errors check',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tserverThatSendsErrors.listen(ports.PORTAND118)\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND118,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.publish(\n\t\t\t\t\t'a/b',\n\t\t\t\t\t'message',\n\t\t\t\t\t{ qos: 2 },\n\t\t\t\t\t(err: ErrorWithReasonCode) => {\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\terr.message,\n\t\t\t\t\t\t\t'Publish error: Session taken over',\n\t\t\t\t\t\t)\n\t\t\t\t\t\tassert.strictEqual(err.code, 142)\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\tserverThatSendsErrors.close((err2) => {\n\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'puback handling custom reason code',\n\t\t{\n\t\t\t// timeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\t// this.timeout(15000)\n\t\t\tserverThatSendsErrors.listen(ports.PORTAND117)\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND117,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tcustomHandleAcks(topic, message, packet, cb) {\n\t\t\t\t\tlet code = 0\n\t\t\t\t\tif (topic === 'a/b') {\n\t\t\t\t\t\tcode = 128\n\t\t\t\t\t}\n\t\t\t\t\tcb(code)\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tserverThatSendsErrors.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', () => {\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\ttopic: 'a/b',\n\t\t\t\t\t\tpayload: 'payload',\n\t\t\t\t\t\tqos: 1,\n\t\t\t\t\t\tmessageId: 1,\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tserverClient.on('puback', (packet) => {\n\t\t\t\t\tassert.strictEqual(packet.reasonCode, 128)\n\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\tserverThatSendsErrors.close((err2) => {\n\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe('a/b', { qos: 1 })\n\t\t\t})\n\t\t},\n\t)\n\n\tit('suback handling error codes', function _test(t, done) {\n\t\tserverThatSendsErrors.listen(ports.PORTAND117)\n\n\t\tserverThatSendsErrors.once('client', (serverClient) => {\n\t\t\tserverClient.on('subscribe', (packet) => {\n\t\t\t\tserverClient.suback({\n\t\t\t\t\tmessageId: packet.messageId,\n\t\t\t\t\tgranted: packet.subscriptions.map((e) => 135),\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\n\t\tconst client = mqtt.connect({\n\t\t\tprotocolVersion: 5,\n\t\t\tport: ports.PORTAND117,\n\t\t\thost: 'localhost',\n\t\t})\n\n\t\tclient.subscribe('$SYS/#', (subErr) => {\n\t\t\tclient.end(true, (endErr) => {\n\t\t\t\tserverThatSendsErrors.close((err2) => {\n\t\t\t\t\tif (subErr) {\n\t\t\t\t\t\tassert.strictEqual(\n\t\t\t\t\t\t\tsubErr.message,\n\t\t\t\t\t\t\t'Subscribe error: Not authorized',\n\t\t\t\t\t\t)\n\t\t\t\t\t\treturn done(err2 || endErr)\n\t\t\t\t\t}\n\t\t\t\t\tdone(new Error('Suback errors do NOT work'))\n\t\t\t\t})\n\t\t\t})\n\t\t})\n\t})\n\n\tit(\n\t\t'server side disconnect',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tconst server2 = new MqttServer((serverClient) => {\n\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\tserverClient.connack({\n\t\t\t\t\t\treasonCode: 0,\n\t\t\t\t\t})\n\t\t\t\t\tserverClient.disconnect({ reasonCode: 128 })\n\t\t\t\t\tserver2.close()\n\t\t\t\t})\n\t\t\t})\n\t\t\tserver2.listen(ports.PORTAND327)\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND327,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t}\n\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tclient.once(\n\t\t\t\t'disconnect',\n\t\t\t\t(disconnectPacket: mqtt.IDisconnectPacket) => {\n\t\t\t\t\tassert.strictEqual(disconnectPacket.reasonCode, 128)\n\t\t\t\t\tclient.end(true, (err) => done(err))\n\t\t\t\t},\n\t\t\t)\n\t\t},\n\t)\n\n\tit(\n\t\t'pubrec handling custom reason code',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tserverThatSendsErrors.listen(ports.PORTAND117)\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND117,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tcustomHandleAcks(topic, message, packet, cb) {\n\t\t\t\t\tlet code = 0\n\t\t\t\t\tif (topic === 'a/b') {\n\t\t\t\t\t\tcode = 128\n\t\t\t\t\t}\n\t\t\t\t\tcb(code)\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe('a/b', { qos: 1 })\n\t\t\t})\n\n\t\t\tserverThatSendsErrors.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', () => {\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\ttopic: 'a/b',\n\t\t\t\t\t\tpayload: 'payload',\n\t\t\t\t\t\tqos: 2,\n\t\t\t\t\t\tmessageId: 1,\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tserverClient.on('pubrec', (packet) => {\n\t\t\t\t\tassert.strictEqual(packet.reasonCode, 128)\n\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\tserverThatSendsErrors.close((err2) => {\n\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'puback handling custom reason code with error',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tserverThatSendsErrors.listen(ports.PORTAND117)\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND117,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tcustomHandleAcks(topic, message, packet, cb) {\n\t\t\t\t\tconst code = 0\n\t\t\t\t\tif (topic === 'a/b') {\n\t\t\t\t\t\tcb(new Error('a/b is not valid'))\n\t\t\t\t\t}\n\t\t\t\t\tcb(code)\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tserverThatSendsErrors.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', () => {\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\ttopic: 'a/b',\n\t\t\t\t\t\tpayload: 'payload',\n\t\t\t\t\t\tqos: 1,\n\t\t\t\t\t\tmessageId: 1,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tclient.on('error', (error) => {\n\t\t\t\tassert.strictEqual(error.message, 'a/b is not valid')\n\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\tserverThatSendsErrors.close((err2) => {\n\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe('a/b', { qos: 1 })\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'pubrec handling custom reason code with error',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tserverThatSendsErrors.listen(ports.PORTAND117)\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND117,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tcustomHandleAcks(topic, message, packet, cb) {\n\t\t\t\t\tconst code = 0\n\t\t\t\t\tif (topic === 'a/b') {\n\t\t\t\t\t\tcb(new Error('a/b is not valid'))\n\t\t\t\t\t}\n\t\t\t\t\tcb(code)\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tserverThatSendsErrors.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', () => {\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\ttopic: 'a/b',\n\t\t\t\t\t\tpayload: 'payload',\n\t\t\t\t\t\tqos: 2,\n\t\t\t\t\t\tmessageId: 1,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tclient.on('error', (error) => {\n\t\t\t\tassert.strictEqual(error.message, 'a/b is not valid')\n\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\tserverThatSendsErrors.close((err2) => {\n\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe('a/b', { qos: 1 })\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'puback handling custom invalid reason code',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tserverThatSendsErrors.listen(ports.PORTAND117)\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND117,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tcustomHandleAcks(topic, message, packet, cb) {\n\t\t\t\t\tlet code = 0\n\t\t\t\t\tif (topic === 'a/b') {\n\t\t\t\t\t\tcode = 124124\n\t\t\t\t\t}\n\t\t\t\t\tcb(code)\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tserverThatSendsErrors.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', () => {\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\ttopic: 'a/b',\n\t\t\t\t\t\tpayload: 'payload',\n\t\t\t\t\t\tqos: 1,\n\t\t\t\t\t\tmessageId: 1,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tclient.on('error', (error) => {\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\terror.message,\n\t\t\t\t\t'Wrong reason code for puback',\n\t\t\t\t)\n\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\tserverThatSendsErrors.close((err2) => {\n\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe('a/b', { qos: 1 })\n\t\t\t})\n\t\t},\n\t)\n\n\tit(\n\t\t'pubrec handling custom invalid reason code',\n\t\t{\n\t\t\ttimeout: 15000,\n\t\t},\n\t\tfunction _test(t, done) {\n\t\t\tserverThatSendsErrors.listen(ports.PORTAND117)\n\t\t\tconst opts: mqtt.IClientOptions = {\n\t\t\t\thost: 'localhost',\n\t\t\t\tport: ports.PORTAND117,\n\t\t\t\tprotocolVersion: 5,\n\t\t\t\tcustomHandleAcks(topic, message, packet, cb) {\n\t\t\t\t\tlet code = 0\n\t\t\t\t\tif (topic === 'a/b') {\n\t\t\t\t\t\tcode = 34535\n\t\t\t\t\t}\n\t\t\t\t\tcb(code)\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tserverThatSendsErrors.once('client', (serverClient) => {\n\t\t\t\tserverClient.once('subscribe', () => {\n\t\t\t\t\tserverClient.publish({\n\t\t\t\t\t\ttopic: 'a/b',\n\t\t\t\t\t\tpayload: 'payload',\n\t\t\t\t\t\tqos: 2,\n\t\t\t\t\t\tmessageId: 1,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tconst client = mqtt.connect(opts)\n\t\t\tclient.on('error', (error) => {\n\t\t\t\tassert.strictEqual(\n\t\t\t\t\terror.message,\n\t\t\t\t\t'Wrong reason code for pubrec',\n\t\t\t\t)\n\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\tserverThatSendsErrors.close((err2) => {\n\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t})\n\t\t\tclient.once('connect', () => {\n\t\t\t\tclient.subscribe('a/b', { qos: 1 })\n\t\t\t})\n\t\t},\n\t)\n})\n"
  },
  {
    "path": "test/node/helpers/TeardownHelper.ts",
    "content": "import type { MqttClient } from 'src'\nimport { randomUUID } from 'node:crypto'\nimport { isAsyncFunction } from 'node:util/types'\nimport type serverBuilder from '../server_helpers_for_client_tests'\n\ntype ServerBuilderInstance = ReturnType<typeof serverBuilder>\n\ntype AddOptions = {\n\t/**\n\t * @description\n\t * If `true`, the method will be executed only one time and then removed from the store.\n\t *\n\t * @default true\n\t */\n\texecuteOnce?: boolean\n\t/**\n\t * @description\n\t * The order in which the method will be executed.\n\t * If `order===0` the method will be executed after all methods before it that were added.\n\t *\n\t * @default 0\n\t */\n\torder?: number\n}\n\ntype ResetOptions = {\n\t/**\n\t * @description\n\t * If `true`, only the methods that have the option `executeOnce` set to `true` will be removed.\n\t *\n\t * @default false\n\t */\n\tremoveOnce?: boolean\n}\n\ntype Method =\n\t| Promise<any>\n\t| ((...args: any[]) => Promise<any>)\n\t| ((...args: any[]) => any)\n\n/**\n * @description\n * Class to help clean the environment or close opened connections after tests finish.\n * Also, you can add custom methods to be executed after the tests finish, like\n * deleting temporary files or closing connections.\n *\n * @example\n * ```\n * import { describe, it } from 'node:test'\n * import mqtt from './src'\n * import serverBuilder from './test/server_helpers_for_client_tests'\n * import TeardownHelper from './test/helpers/TeardownHelper'\n *\n *\n * describe('Test', () => {\n *     const teardownHelper = new TeardownHelper()\n *\n *     it('should clean the client and server', (t, done) => {\n *         t.after(async () => {\n *             await teardownHelper.runAll()\n *         })\n *\n *         const server = serverBuilder('8883')\n *         const client = mqtt.connect('mqtt://localhost')\n *\n *         teardownHelper.addServer(server)\n *         teardownHelper.addClient(client)\n *     })\n * })\n * ```\n *\n * @example\n * ```\n * import { describe, it, after } from 'node:test'\n * import mqtt from './src'\n * import serverBuilder from './test/server_helpers_for_client_tests'\n * import TeardownHelper from './test/helpers/TeardownHelper'\n *\n *\n * describe('Test', () => {\n *\n *     const teardownHelper = new TeardownHelper()\n *     let server = serverBuilder('8883')\n *\n *     teardownHelper.add({}, async () => {\n *         if (server?.listening) {\n *             await new Promise<void>((resolve, reject) => {\n *                 server.close((err) => {\n *                     if (err) reject(err)\n *                     else resolve()\n *                 })\n *             })\n *         }\n *     })\n *\n *     after(async () => {\n *         await teardownHelper.runAll()\n *     })\n *\n *     it('should clean the client and server', (t, done) => {\n *         server = serverBuilder('8883')\n *         const client = mqtt.connect('mqtt://localhost')\n *\n *         teardownHelper.addClient(client)\n *         done()\n *     })\n *\n * })\n * ```\n */\nclass TeardownHelper {\n\t#methods: Map<\n\t\tstring,\n\t\t{\n\t\t\toptions: AddOptions\n\t\t\tmethod: Method\n\t\t\targs: any[]\n\t\t}\n\t>\n\n\tconstructor() {\n\t\tthis.#methods = new Map()\n\t}\n\n\t/**\n\t * @description\n\t * Add a client to close.\n\t */\n\taddClient(client: MqttClient) {\n\t\tthis.add({}, this.closeClient, client)\n\t}\n\n\t/**\n\t * @description\n\t * Add a server to close.\n\t */\n\taddServer(server: ServerBuilderInstance) {\n\t\tthis.add({}, this.closeServer, server)\n\t}\n\n\t/**\n\t * @param options Options to be passed to the method.\n\t * @param method It can be a promise or a function that returns a promise.\n\t * @param args Arguments to be passed to the method.\n\t *\n\t * @description\n\t * Add a method to be executed\n\t */\n\tadd<T extends any[] = []>(\n\t\toptions: AddOptions | undefined,\n\t\tmethod: Method,\n\t\t...args: T\n\t): string {\n\t\tconst id = randomUUID()\n\n\t\tthis.#methods.set(id, {\n\t\t\tmethod,\n\t\t\targs,\n\t\t\toptions: { executeOnce: true, order: 0, ...options },\n\t\t})\n\n\t\treturn id\n\t}\n\n\t/**\n\t * @description\n\t * Remove all methods stored.\n\t */\n\treset(options?: ResetOptions) {\n\t\tif (options?.removeOnce) {\n\t\t\tfor (const [id, { options: methodOptions }] of this.#methods) {\n\t\t\t\tif (methodOptions.executeOnce) {\n\t\t\t\t\tthis.#methods.delete(id)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tthis.#methods.clear()\n\t\t}\n\t}\n\n\t/**\n\t * @description\n\t * Close the `client` connection.\n\t *\n\t * @default\n\t * Use the `client` set in the class.\n\t */\n\tasync closeClient(client: MqttClient) {\n\t\tif (client) {\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tclient.end(true, (err) => {\n\t\t\t\t\tif (err) reject(err)\n\t\t\t\t\telse resolve()\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\t}\n\n\t/**\n\t * @description\n\t * Close the `server` connection.\n\t *\n\t * @default\n\t * Use the `server` set in the class.\n\t */\n\tasync closeServer(server: ServerBuilderInstance) {\n\t\tif (server?.listening) {\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tserver.close((err) => {\n\t\t\t\t\tif (err) reject(err)\n\t\t\t\t\telse resolve()\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\t}\n\n\t/**\n\t * @param id Method id to be executed.\n\t *\n\t * @description\n\t * Execute a method stored by its id.\n\t * If the method has the option `executeOnce` set to `true`, it will be removed after execution.\n\t */\n\tasync run(id: string) {\n\t\tconst method = this.#methods.get(id)\n\n\t\tif (!method) {\n\t\t\treturn\n\t\t}\n\n\t\tif (method.options.executeOnce) {\n\t\t\tthis.#methods.delete(id)\n\t\t}\n\n\t\tif (method.method instanceof Promise) {\n\t\t\tawait method.method\n\t\t} else {\n\t\t\tawait method.method(...method.args)\n\t\t}\n\t}\n\n\t/**\n\t * @description\n\t * Execute all methods stored.\n\t * If a method has the option `executeOnce` set to `true`, it will be removed after execution.\n\t */\n\tasync runAll() {\n\t\tif (this.#methods.size === 0) {\n\t\t\treturn\n\t\t}\n\n\t\tconst methodStored: (AddOptions & { key: string })[] = []\n\n\t\tfor (const [key, { options }] of this.#methods) {\n\t\t\tmethodStored.push({ ...options, key })\n\t\t}\n\n\t\tmethodStored.sort((a, b) => b.order - a.order)\n\t\tconst methods: Array<Promise<any>> = []\n\n\t\tfor (const { key, ...options } of methodStored) {\n\t\t\tconst { method, args } = this.#methods.get(key)\n\n\t\t\tif (method instanceof Promise) {\n\t\t\t\tmethods.push(method)\n\t\t\t} else if (isAsyncFunction(method)) {\n\t\t\t\tconst promise = new Promise<any>((resolve, reject) => {\n\t\t\t\t\tmethod(...args)\n\t\t\t\t\t\t.then(resolve)\n\t\t\t\t\t\t.catch(reject)\n\t\t\t\t})\n\n\t\t\t\tmethods.push(promise)\n\t\t\t} else {\n\t\t\t\tconst promise = new Promise<any>((resolve, reject) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = method(...args)\n\t\t\t\t\t\tresolve(result)\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treject(error)\n\t\t\t\t\t}\n\t\t\t\t})\n\n\t\t\t\tmethods.push(promise)\n\t\t\t}\n\n\t\t\tif (options.executeOnce) {\n\t\t\t\tthis.#methods.delete(key)\n\t\t\t}\n\t\t}\n\n\t\tconst results = await Promise.allSettled(methods)\n\n\t\tfor (const result of results) {\n\t\t\tif (result.status === 'rejected') {\n\t\t\t\tif (result.reason instanceof Error) throw result.reason\n\t\t\t\telse throw new Error(result.reason)\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport default TeardownHelper\n"
  },
  {
    "path": "test/node/helpers/leaked.ts",
    "content": "// include this as first module when looking for leaked handles\nimport leaked from 'leaked-handles'\n\nleaked.set({\n\tfullStack: true,\n\ttimeout: 15000,\n\tdebugSockets: true,\n})\n"
  },
  {
    "path": "test/node/helpers/port_list.ts",
    "content": "/**\n * Method used to get ports for testing\n * @param i Index to shift the ports by\n * @returns\n */\nexport default function getPorts(i = 0) {\n\tconst PORT = 10000 + i * 400\n\tconst ports = {\n\t\tPORT,\n\t\tPORTAND40: PORT + 40,\n\t\tPORTAND41: PORT + 41,\n\t\tPORTAND42: PORT + 42,\n\t\tPORTAND43: PORT + 43,\n\t\tPORTAND44: PORT + 44,\n\t\tPORTAND45: PORT + 45,\n\t\tPORTAND46: PORT + 46,\n\t\tPORTAND47: PORT + 47,\n\t\tPORTAND48: PORT + 48,\n\t\tPORTAND49: PORT + 49,\n\t\tPORTAND50: PORT + 50,\n\t\tPORTAND72: PORT + 72,\n\t\tPORTAND103: PORT + 103,\n\t\tPORTAND114: PORT + 114,\n\t\tPORTAND115: PORT + 115,\n\t\tPORTAND116: PORT + 116,\n\t\tPORTAND117: PORT + 117,\n\t\tPORTAND118: PORT + 118,\n\t\tPORTAND119: PORT + 119,\n\t\tPORTAND316: PORT + 316,\n\t\tPORTAND326: PORT + 326,\n\t\tPORTAND327: PORT + 327,\n\t\tPORTAND400: PORT + 400,\n\t}\n\n\treturn ports\n}\n"
  },
  {
    "path": "test/node/helpers/private-csr.pem",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx\nITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBANtzIZmqf7h3axO9mzo2VhiF/BF3Y4E/fDTkFy27\nPgssS/ipFOMe/IxyM/hA/o/bQb0BY+sH5s1020kNH79umtabWMaDmOi8bvmHWtVC\ncYhn3mhbRFWcORdTnfQ8uRYXZGeoupjlhfrKkQCoSAFKh1OzU7aNx4CjMAjSa4py\ntrMAVNJ37RryhsfMuHAeG8+0Eo3qmYyaplpurtr8A3HWV65R2VFCwZ5hKG8I9X2F\n3UrYKHr4xlxOgjD8j2OfYZxpGHI6YexJ28aR0xlsWfzS+TKKFVxy8ntgPGL0ZXL3\nvss80mAcBl9FfsJzufn4IHOYspX1OEM0M7plMmQw/yNT9B8CAwEAAaAAMA0GCSqG\nSIb3DQEBBQUAA4IBAQBsONiE5HTjfR1pDrWPIhbLqMO3AqmuB5AwpQm8kAaM2Oz1\nDI/a8bHYyODMiyWUPTtwLMQWcJpAG2ZhE18gLqFwXZR1XSOxY1yF+uZ7Ls3hwzbq\n9A6O254B5wXBnXkVbzZwFshV5HWiZwVivF5GDyLRsMoS2EtUHoDEP4YIRK0kPL9H\nm3BB334KlWTc8NNXFFG62OL7q2fa8xRHlN8SYfeUjy79eEoBdHv5wL/ZN/YBCDNJ\n2zrYUvbOmfoq1e+6AczZ6xAHHeneUQuaOF225aMwHHZTiP2TlIeFXwBvzV1BWIJv\ndOaHX/f3NamKoGvwYyIR1FrI2FpXTJLRE/eu7TFD\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "test/node/helpers/private-key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA23Mhmap/uHdrE72bOjZWGIX8EXdjgT98NOQXLbs+CyxL+KkU\n4x78jHIz+ED+j9tBvQFj6wfmzXTbSQ0fv26a1ptYxoOY6Lxu+Yda1UJxiGfeaFtE\nVZw5F1Od9Dy5FhdkZ6i6mOWF+sqRAKhIAUqHU7NTto3HgKMwCNJrinK2swBU0nft\nGvKGx8y4cB4bz7QSjeqZjJqmWm6u2vwDcdZXrlHZUULBnmEobwj1fYXdStgoevjG\nXE6CMPyPY59hnGkYcjph7EnbxpHTGWxZ/NL5MooVXHLye2A8YvRlcve+yzzSYBwG\nX0V+wnO5+fggc5iylfU4QzQzumUyZDD/I1P0HwIDAQABAoIBAQDNgNdqS5wnZs1D\nQz/mF5QwiugugxsPoh/yd9as4LeNRwIt7ki9F/twmlHInTTGCpFZKcAkDNY6eMAR\nfNTKNA2UAw3zeLDs4ekai4KoSvx+vKYuG6m2cgGUsp0sZuD8qxM/b2auX+JDpQZ9\nExm6+8wWucwfHE5DTI5i9In4sMweeuiEUYnndTzElkvnP/44h1fGSU1iGUKn/ftc\nP4X+3SU68KMT3kUsEBavtmSdyeG/lSFEjm73FwVIRZ+PfbQX2hDD+mmseAXGFKi1\nHudtQkEzTvYR+QAgvtjNgt/0qxFtPdj7Y+iRkCZQSJToAw8z6vwUn1qNCADauGMI\nX6KIm8XBAoGBAPiwMLYpIqp1rksINbqpbVqjtqsoejQuPYeEF7OXHbH9il7pWrQF\nwLbogo3YXX+a66RreVMhsUeq7+pIf/sK2lT73gDpFfvZnJG1ww94QkHBEPso0bN9\npcGgceIK7KRRAiAl5Mjw6pZZNnIBxlIFaSbBqQau74NfdaalMBF2wi+3AoGBAOHm\n3ttFtVjVlb2fHoiGNZCZDv3gnsQXZlCxS+rQ4XEmEWKHAH4T3+Kzmo8jWoX+DGGD\n6UkxWHv7e+KrYIZDi7Dd2HFV0gHN6d1SNdPix3vN114bNOrbfqxuEVT5PdFHSuel\n5d3ix+3U+tpHamwb88eyeq6Q3t5Lcl3gIRGLzo7ZAoGBAKVuLzk+K/1Qw1zOXU+K\nnWAKP92j04caq3uWd13UTMC2dHGmsdvHZ+dEzHQnVisol1CM3exbIV8XavliuR/6\nnDqkQY5Bf4pFvE2Bp/yGdyzejblF8hmAn98qKBfCRKEZ8lwIWSUCfkr9laZJX+/4\nAXbypMn5XQL7YXw1rsAvTAYJAoGAV4ZL8kkf6jtWuRFdkyfsuQmUdWkCGpe2XK1U\n7LXhoyVMtw/3cOHibMOJrsvT1vaHdYDWcjVcQy084qXj0CF7jhtmMQM/StOtOMMR\nd/b1s1Idj6ia6CQDAGvk6zdmbB9jNj1gwoeLTuqmBsyEvz5VRZoxTlFzCE3TEew0\n48d3UIECgYBMxnLByVQA3pQWWIZZyqt+HgJAphYPdpnPalblQAbuCksKTZ/QKDkW\ndzih1PQROVrYrX7VwJ3/I8gXIuvKVtN1NKOS3a0JtbJQhpH4YbRwyQskXWYP8oYa\nMjBGPymNDhZh0zoGWzst5uR3NpdNV+7yNYPvyxzVNjlPjtAUqIxjBg==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "test/node/helpers/public-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDBjCCAe4CCQDkrq1PMPtmfzANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB\nVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0\ncyBQdHkgTHRkMB4XDTEzMDEyNTEwMzEyOVoXDTEzMDIyNDEwMzEyOVowRTELMAkG\nA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0\nIFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANtzIZmqf7h3axO9mzo2VhiF/BF3Y4E/fDTkFy27PgssS/ipFOMe/IxyM/hA/o/b\nQb0BY+sH5s1020kNH79umtabWMaDmOi8bvmHWtVCcYhn3mhbRFWcORdTnfQ8uRYX\nZGeoupjlhfrKkQCoSAFKh1OzU7aNx4CjMAjSa4pytrMAVNJ37RryhsfMuHAeG8+0\nEo3qmYyaplpurtr8A3HWV65R2VFCwZ5hKG8I9X2F3UrYKHr4xlxOgjD8j2OfYZxp\nGHI6YexJ28aR0xlsWfzS+TKKFVxy8ntgPGL0ZXL3vss80mAcBl9FfsJzufn4IHOY\nspX1OEM0M7plMmQw/yNT9B8CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAeHAwoKYl\n6g9lUEwBDqm6ZxjgoYQi6V3loCjBcTr5OrMkLvvZrA55xsse0NRH40I/pvCaAZAZ\nEEna0fr5GPYi+y+I8EoU2W/+ehSqRAU8Fkdm0eR5MjyLWYOwd3ClUND8EpUNNSKH\nXw9k9EQmyKsDxVsKWoJoO9rfFkUjooz07jGPCud18QCBs5i5ThbnQ9UP+26D8z5k\n1Dii69LIcLXA3Vtm6R5fT57zNusfx8bqA9yy7UThYaXIazNMWNxiJRXfv0J4zFdD\nRQ+SFdJ3p5jurPkc3oRWWPbn/Lpf0E5XlYTJImXT1WmWnQSaNtME4P+3kEL5x+v/\nu8zTLbobG4x0rQ==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/node/helpers/public-key.pem",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA23Mhmap/uHdrE72bOjZW\nGIX8EXdjgT98NOQXLbs+CyxL+KkU4x78jHIz+ED+j9tBvQFj6wfmzXTbSQ0fv26a\n1ptYxoOY6Lxu+Yda1UJxiGfeaFtEVZw5F1Od9Dy5FhdkZ6i6mOWF+sqRAKhIAUqH\nU7NTto3HgKMwCNJrinK2swBU0nftGvKGx8y4cB4bz7QSjeqZjJqmWm6u2vwDcdZX\nrlHZUULBnmEobwj1fYXdStgoevjGXE6CMPyPY59hnGkYcjph7EnbxpHTGWxZ/NL5\nMooVXHLye2A8YvRlcve+yzzSYBwGX0V+wnO5+fggc5iylfU4QzQzumUyZDD/I1P0\nHwIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "test/node/helpers/server.ts",
    "content": "import fs from 'fs'\nimport { MqttServer, MqttSecureServer } from '../server'\n\nexport function init_server(PORT: number) {\n\tconst server = new MqttServer((client) => {\n\t\tclient.on('connect', () => {\n\t\t\tclient.connack(0)\n\t\t})\n\n\t\tclient.on('publish', (packet) => {\n\t\t\tswitch (packet.qos) {\n\t\t\t\tcase 1:\n\t\t\t\t\tclient.puback({ messageId: packet.messageId })\n\t\t\t\t\tbreak\n\t\t\t\tcase 2:\n\t\t\t\t\tclient.pubrec({ messageId: packet.messageId })\n\t\t\t\t\tbreak\n\t\t\t\tdefault:\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t})\n\n\t\tclient.on('pubrel', (packet) => {\n\t\t\tclient.pubcomp({ messageId: packet.messageId })\n\t\t})\n\n\t\tclient.on('pingreq', () => {\n\t\t\tclient.pingresp()\n\t\t})\n\n\t\tclient.on('disconnect', () => {\n\t\t\tclient.stream.end()\n\t\t})\n\t})\n\tserver.listen(PORT)\n\treturn server\n}\n\nexport function init_secure_server(port: number, key: string, cert: string) {\n\tconst server = new MqttSecureServer(\n\t\t{\n\t\t\tkey: fs.readFileSync(key),\n\t\t\tcert: fs.readFileSync(cert),\n\t\t},\n\t\t(client) => {\n\t\t\tclient.on('connect', () => {\n\t\t\t\tclient.connack({ returnCode: 0 })\n\t\t\t})\n\t\t},\n\t)\n\tserver.listen(port)\n\treturn server\n}\n"
  },
  {
    "path": "test/node/helpers/server_process.ts",
    "content": "import { MqttServer } from '../server'\n\nnew MqttServer((client) => {\n\tclient.on('connect', () => {\n\t\tclient.connack({ returnCode: 0 })\n\t})\n}).listen(3481, 'localhost')\n"
  },
  {
    "path": "test/node/helpers/tls-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDkzCCAnugAwIBAgIUKq35JCwofQRXirn9WuUcjNjGt5MwDQYJKoZIhvcNAQEL\nBQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X\nDTIwMDgyMjE4MDcwNloXDTMwMDgyMDE4MDcwNlowWTELMAkGA1UEBhMCQVUxEzAR\nBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5\nIEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEArTgcoNC3gV1yIwMJ3geQCO1iGL7E4GwiGL6h+EyPU011w5bAH9+Q\nftGy8XaNjTJWMu6E+tFf5r+AWE314s0QJc7NsfSpy8LATcUc/Z3XlyTkHN9IMScn\nRmk+J6FVprvi06Ab64LWyIGLd9DC19taw7xF0EO31jA41Vrs3q88jzjH9U6yYMhw\nGAfAPg5L5f0Q1hIz51mgLbqT5zbOE5h3ahZcfmyeR5+UjbS2LuIBem1FNPYwyUAg\njK9AJieb4WVrRgfgIvKEsZQbYtltf9TfWAxVHJVIC0gu+Dhmi6JI6NbZZ1ngYFjJ\nuY91MN/Zu23NW5iTSE90x5iYJgQg0ot5/QIDAQABo1MwUTAdBgNVHQ4EFgQUNI0h\nZ+Q1vtev6jjdkYTNOJ9R7TAwHwYDVR0jBBgwFoAUNI0hZ+Q1vtev6jjdkYTNOJ9R\n7TAwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAclulOFJE7zSo\nYG0TF2PSc3yHdVYgL6MJnSf1rTQygO4XFIPdxlHtYiWeENDzc3drF2p8qRk2nidv\nuxzyDJ9L+K83Jl2QC404uD+bHl/N9M5qF+hZHL6pfuMKv3UZUxPt2bDWtzl96wmg\nXASC+R4AFb54XjRuRwCg8o7U/ILi8A4Q1uyM7dVwmztuy0QQpMJg01c/5Sr3brY0\nqAlsl8EYBRtSVVb/c7CwbKT3b5aitqKm25WK3wWvTOE1VVyYxdNHW4IsX+eYB0Z3\ndQ7ZQeb9TYp6taaaC5avk7e6J5n6emHhpzbnHk0dNpKjmZeBrI9yfqdXqLJWdEbG\nAvPDUVfo/g==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/node/helpers/tls-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCtOByg0LeBXXIj\nAwneB5AI7WIYvsTgbCIYvqH4TI9TTXXDlsAf35B+0bLxdo2NMlYy7oT60V/mv4BY\nTfXizRAlzs2x9KnLwsBNxRz9ndeXJOQc30gxJydGaT4noVWmu+LToBvrgtbIgYt3\n0MLX21rDvEXQQ7fWMDjVWuzerzyPOMf1TrJgyHAYB8A+Dkvl/RDWEjPnWaAtupPn\nNs4TmHdqFlx+bJ5Hn5SNtLYu4gF6bUU09jDJQCCMr0AmJ5vhZWtGB+Ai8oSxlBti\n2W1/1N9YDFUclUgLSC74OGaLokjo1tlnWeBgWMm5j3Uw39m7bc1bmJNIT3THmJgm\nBCDSi3n9AgMBAAECggEAYjCymb52p1BvSMWKLGAhF85okxpgw87ILTqy2eucO15n\naS3lTqwOXrVEOHg5mVZ1Yn2ux/cz47ueZ3AZ+CzCAIyQMVY9ghGtrOgVnPaCpVz2\nKh+v7p0BOHqkDxb3VIKg+9GAwiny0soMYyjlqjLf6qCo+nvIlBPVw6u9JiYzsANT\nEVaC6iEdvwpEG1ZFtzH08Z3/xjhlvDiPDnrfPDFyWZga+J4WJeL0US48vDfufdSJ\nlQM0NveF7cdkbKjLiizlYgq8CSOHjMz6OWHS0SFT7iR7GlC5ADpyeMmJLRInLgmE\nHZV3/FC1IYQzSHk1WNG/MP3RnzCA8NTj1ehE5mfDyQKBgQDYcTnpXRBd2sozAxzN\ndCVf1PtZ5WBmCmk74Ndr/o8wiHkQ7+E3+zee78c45ZQ+P9iEvaygqGTRetItecBA\nWxlOQ9z0CmAg1xI2hNIpImAR8Qohr1bEHuzQhdO8LkOuxtj6yP/FDxkKYQAI9C6v\nLx6zo6o4XD5Et4wbUJVkwamrVwKBgQDM4JaxOPHcIVFuYAqxonQrv9bFB7Eew0Fj\nqdrQ/flsgz8FyZtThxF9b+7280y5XNJ4tNUKtDcat4cH3jeWfa467DLKjTKkWdJR\niR4MGbsONXWoWPHPQ0GJZY/p3iqn9/OvBZh1k7NsXPmfAVRqMWjNws/WcWSb7Mgq\ndBN3A37EywKBgEg6UKcNhV6smnk3eq8dKTO3sUEoiGjE5KU0vO6u/j2l7TC3vCKg\nVMlXHtZf1n6Hc8uoOClMyIgXQngmfv965xD1GJDfvYB4BP3oiPFtJT4Xf9gJ2RyN\nbV2Qqz3K+o8ikFnwJVovVZ3fDNHwGnwfb1FnNnCkZ6sqzTh4RcJf1iz1AoGARwD7\nGNaMc+cUKrWcXy3XJyZoT4a36tpuuhSu4kly/RmLaP0TGOKxvBBj+DAgAgnaY70A\nLKKCin7ajG6GQ2CxVnhvreU7jNwYWOu1fyoXuvfqG/sfat57QxvwwXOewvHbAWhm\nCzGyODcMx/+U+uy+zrjagQ5xeNyaDqSF7nRGpfsCgYAA7b/GlldodAJkZAiqejIc\nSArscos57stZfYyNICJq7Ye4qpzuWSrQKa5GtseSbvnz5yLzLuDe3Lr5HmYLypOc\nwC0JlKeTBMTObsGN0LixrXXRiuyQyCfmuvKu8WfKIlpZMUB5zgHYE8TAvm0BZjq9\n+FUHwoRBoG3Qn04Uj9CCNg==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "test/node/helpers/wrong-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIICATCCAWoCCQDEVSSDKkcTdjANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB\nVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0\ncyBQdHkgTHRkMB4XDTE0MDUxMTE2MzMxMVoXDTE0MDYxMDE2MzMxMVowRTELMAkG\nA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0\nIFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyDMI\nVS2XSizZT8KeFYFQfKt7CcT5/Pkzw2BDJoMVLmrkfHdddjsTgswqHfzhO8Fmfg6B\nMxgsEz2aKi24hJxQFuQ1DGhyfKHnjxM5PqSLiOkQDKllnAOgqOBDXpca0jXypCk1\nIVhMspM2ylrnBXps3nTBLJxFBkZSBov/JDkkL+cCAwEAATANBgkqhkiG9w0BAQUF\nAAOBgQA8k93U0VDIpQ8lpScxrCtEu5jLZgB1fw0fdCUtDHaaM1v+LWr1xfCmFKyT\nkUMcJl4e1pkcSNfXcI7LdNt8EJqMabOi2UpW1+VZJn206D0f3XmNSmZbk8oozGrl\nqg2wSTZYlZClCTpWO2Y+iYzojY8kmLaQ2xbTxBz1XlshC8HvsA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/node/helpers/wrong-csr.pem",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIBhDCB7gIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEh\nMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB\nAQUAA4GNADCBiQKBgQDIMwhVLZdKLNlPwp4VgVB8q3sJxPn8+TPDYEMmgxUuauR8\nd112OxOCzCod/OE7wWZ+DoEzGCwTPZoqLbiEnFAW5DUMaHJ8oeePEzk+pIuI6RAM\nqWWcA6Co4ENelxrSNfKkKTUhWEyykzbKWucFemzedMEsnEUGRlIGi/8kOSQv5wID\nAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAFXqd8jhW+2hRvkRB1CCVBK5e6AQHq1rF\ns3B36O64hRHIr1KC+dWr8vv1t9Rkud+7E3ELHtxWCORIYpqQ2Ddldt4PP+MTNj2C\nqgwOpxM0VDxeeWml8fqx2uzfPhVduyHGm0yff2JS2KRVmnIPLTUuz/+udukIFDVO\nSc4/W3qY7f8=\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "test/node/helpers/wrong-key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQDIMwhVLZdKLNlPwp4VgVB8q3sJxPn8+TPDYEMmgxUuauR8d112\nOxOCzCod/OE7wWZ+DoEzGCwTPZoqLbiEnFAW5DUMaHJ8oeePEzk+pIuI6RAMqWWc\nA6Co4ENelxrSNfKkKTUhWEyykzbKWucFemzedMEsnEUGRlIGi/8kOSQv5wIDAQAB\nAoGBALOzszgaG2I2jb4dmJ7/G4s8tc2YJTlhS4iFgOEx6rJmur/KuXcmIiZXMzsF\nwftMZ76hMHH3saB3vEk+DxHh6bR6cW/I82Vxts9suz2fRnd2mh5JHI+opXE53LVn\nhJcQ4k6LJ9MNVxlHwCTrSuJvtikDOOrCARHRvYzFRL4wXvmpAkEA+DFzXGDg8PxX\nRFp6RLLbqaUT6YXNi+E5ERuumru+rgRj+OF/dxNK3d1lcIJZjqVMDAgOsZ66/bkh\nGfCzJPREUwJBAM5/HeHmTEM5K5B0X8b6XEHTgWFUNTu4K36Ee5ySd8RYI8zjQ9wS\nNM1nXnx12npL7DSkShz9xgnTe0f8YmQnc50CQQCgdE/RXCxwf6LnZNsBCOSsIzXh\nVgiRsxSSs+PI0zGuDNaY8yfV0ponH1fSSeMeLk0gxiDBwg2/tGzq+UrHzEdTAkB1\n/U5O0K+MzbLlxIkhgdaLSlYoDdyo9e/sR7j12v8SMqaqIMWajtCa+VCU3yZqMM2T\nurgaXqr03GEZ3c0+mwhFAkAwWkczV1iwuedmWLKc36iQhoj+FRMUoxWe/fBixQls\ng0lDvwWiZ3M6hjCsBRckmt8eU2mUh79Odrj5fRWIwXaX\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "test/node/keepaliveManager.ts",
    "content": "import { afterEach, beforeEach, describe, it } from 'node:test'\nimport { assert } from 'chai'\nimport { useFakeTimers, spy, stub } from 'sinon'\nimport { type MqttClient } from 'src'\nimport KeepaliveManager from '../../src/lib/KeepaliveManager'\n\nfunction mockedClient(keepalive: number) {\n\treturn {\n\t\toptions: {\n\t\t\tkeepalive,\n\t\t},\n\t\tonKeepaliveTimeout: () => {},\n\t\tsendPing: () => {},\n\t\tlog: () => {},\n\t} as unknown as MqttClient\n}\n\ndescribe('KeepaliveManager', () => {\n\tlet clock: sinon.SinonFakeTimers\n\tbeforeEach(() => {\n\t\tclock = useFakeTimers()\n\t})\n\n\tafterEach(() => {\n\t\tclock.restore()\n\t})\n\n\tit('should schedule and destroy', () => {\n\t\tconst keepalive = 10 // seconds\n\t\tconst client = mockedClient(keepalive)\n\t\tconst manager = new KeepaliveManager(client, 'auto')\n\n\t\tconst spySendPing = spy(client, 'sendPing')\n\t\tconst spyTimeout = spy(client, 'onKeepaliveTimeout')\n\n\t\tconst checksEvery = manager.intervalEvery\n\n\t\tassert.ok(manager['timerId'], 'timer should be created automatically')\n\n\t\tclock.tick(checksEvery)\n\t\tassert.equal(\n\t\t\tspySendPing.callCount,\n\t\t\t0,\n\t\t\t'should not send ping before keepalive seconds',\n\t\t)\n\n\t\tclock.tick(checksEvery)\n\t\tassert.equal(spySendPing.callCount, 1, 'should send ping automatically')\n\t\tassert.equal(spyTimeout.callCount, 0, 'should not trigger timeout')\n\n\t\tclock.tick(checksEvery)\n\t\tassert.equal(\n\t\t\tspyTimeout.callCount,\n\t\t\t1,\n\t\t\t'should trigger keepalive timeout after 1.5*keepalive seconds',\n\t\t)\n\n\t\tmanager.destroy()\n\t\tassert.ok(\n\t\t\t!manager['timerId'],\n\t\t\t'timer should not exists after destroy()',\n\t\t)\n\n\t\tassert.ok(\n\t\t\tmanager['destroyed'],\n\t\t\t'timer should have `destroyed` set to true after destroy()',\n\t\t)\n\t})\n\n\tit('should reschedule', () => {\n\t\tconst keepalive = 10 // seconds\n\t\tconst manager = new KeepaliveManager(mockedClient(keepalive), 'auto')\n\n\t\tconst checksEvery = manager.intervalEvery\n\n\t\tclock.tick(checksEvery)\n\t\tassert.equal(\n\t\t\tmanager['counter'],\n\t\t\t1,\n\t\t\t'should increese counter on every check',\n\t\t)\n\t\tmanager.reschedule()\n\t\tassert.equal(\n\t\t\tmanager['counter'],\n\t\t\t0,\n\t\t\t'should reset counter after reschedule',\n\t\t)\n\t})\n\n\tit('should validate keepalive', () => {\n\t\tconst manager = new KeepaliveManager(mockedClient(1), 'auto')\n\n\t\tassert.throw(\n\t\t\t() => manager.setKeepalive(-1),\n\t\t\t'Keepalive value must be an integer between 0 and 2147483647. Provided value is -1',\n\t\t)\n\n\t\tassert.throw(\n\t\t\t() => manager.setKeepalive(2147483648),\n\t\t\t'Keepalive value must be an integer between 0 and 2147483647. Provided value is 2147483648',\n\t\t)\n\n\t\tmanager.setKeepalive(10)\n\n\t\tassert.equal(manager.keepalive, 10000)\n\t\tassert.equal(manager.intervalEvery, 5000)\n\t})\n\n\tit('should use provided Timer object', () => {\n\t\tconst keepalive = 10 // seconds\n\t\tconst customTimer = {\n\t\t\tset: stub().returns(123),\n\t\t\tclear: stub(),\n\t\t}\n\t\tconst manager = new KeepaliveManager(\n\t\t\tmockedClient(keepalive),\n\t\t\tcustomTimer,\n\t\t)\n\t\tassert.equal(manager['timer'], customTimer)\n\t\tassert.equal(customTimer.set.callCount, 1)\n\t\tassert.equal(manager['timerId'], 123)\n\n\t\tmanager.destroy()\n\t\tassert.equal(customTimer.clear.called, true)\n\t})\n})\n"
  },
  {
    "path": "test/node/message-id-provider.ts",
    "content": "import { assert } from 'chai'\nimport { describe, it } from 'node:test'\nimport { DefaultMessageIdProvider, UniqueMessageIdProvider } from '../../src'\n\ndescribe('message id provider', () => {\n\tdescribe('default', () => {\n\t\tit('should return 1 once the internal counter reached limit', (t) => {\n\t\t\tconst provider = new DefaultMessageIdProvider()\n\t\t\tprovider['nextId'] = 65535\n\n\t\t\tassert.equal(provider.allocate(), 65535)\n\t\t\tassert.equal(provider.allocate(), 1)\n\t\t})\n\n\t\tit('should return 65535 for last message id once the internal counter reached limit', (t) => {\n\t\t\tconst provider = new DefaultMessageIdProvider()\n\t\t\tprovider['nextId'] = 65535\n\n\t\t\tassert.equal(provider.allocate(), 65535)\n\t\t\tassert.equal(provider.getLastAllocated(), 65535)\n\t\t\tassert.equal(provider.allocate(), 1)\n\t\t\tassert.equal(provider.getLastAllocated(), 1)\n\t\t})\n\t\tit('should return true when register with non allocated messageId', (t) => {\n\t\t\tconst provider = new DefaultMessageIdProvider()\n\t\t\tassert.equal(provider.register(10), true)\n\t\t})\n\t})\n\tdescribe('unique', () => {\n\t\tit('should return 1, 2, 3..,  when allocate', (t) => {\n\t\t\tconst provider = new UniqueMessageIdProvider()\n\t\t\tassert.equal(provider.allocate(), 1)\n\t\t\tassert.equal(provider.allocate(), 2)\n\t\t\tassert.equal(provider.allocate(), 3)\n\t\t})\n\t\tit('should skip registerd messageId', (t) => {\n\t\t\tconst provider = new UniqueMessageIdProvider()\n\t\t\tassert.equal(provider.register(2), true)\n\t\t\tassert.equal(provider.allocate(), 1)\n\t\t\tassert.equal(provider.allocate(), 3)\n\t\t})\n\t\tit('should return false register allocated  messageId', (t) => {\n\t\t\tconst provider = new UniqueMessageIdProvider()\n\t\t\tassert.equal(provider.allocate(), 1)\n\t\t\tassert.equal(provider.register(1), false)\n\t\t\tassert.equal(provider.register(5), true)\n\t\t\tassert.equal(provider.register(5), false)\n\t\t})\n\t\tit('should retrun correct last messageId', (t) => {\n\t\t\tconst provider = new UniqueMessageIdProvider()\n\t\t\tassert.equal(provider.allocate(), 1)\n\t\t\tassert.equal(provider.getLastAllocated(), 1)\n\t\t\tassert.equal(provider.register(2), true)\n\t\t\tassert.equal(provider.getLastAllocated(), 1)\n\t\t\tassert.equal(provider.allocate(), 3)\n\t\t\tassert.equal(provider.getLastAllocated(), 3)\n\t\t})\n\t\tit('should be reusable deallocated messageId', (t) => {\n\t\t\tconst provider = new UniqueMessageIdProvider()\n\t\t\tassert.equal(provider.allocate(), 1)\n\t\t\tassert.equal(provider.allocate(), 2)\n\t\t\tassert.equal(provider.allocate(), 3)\n\t\t\tprovider.deallocate(2)\n\t\t\tassert.equal(provider.allocate(), 2)\n\t\t})\n\t\tit('should allocate all messageId and then return null', (t) => {\n\t\t\tconst provider = new UniqueMessageIdProvider()\n\t\t\tfor (let i = 1; i <= 65535; i++) {\n\t\t\t\tassert.equal(provider.allocate(), i)\n\t\t\t}\n\t\t\tassert.equal(provider.allocate(), null)\n\t\t\tprovider.deallocate(10000)\n\t\t\tassert.equal(provider.allocate(), 10000)\n\t\t\tassert.equal(provider.allocate(), null)\n\t\t})\n\t\tit('should all messageId reallocatable after clear', (t) => {\n\t\t\tconst provider = new UniqueMessageIdProvider()\n\t\t\tfor (let i = 1; i <= 65535; i++) {\n\t\t\t\tassert.equal(provider.allocate(), i)\n\t\t\t}\n\t\t\tassert.equal(provider.allocate(), null)\n\t\t\tprovider.clear()\n\t\t\tfor (let i = 1; i <= 65535; i++) {\n\t\t\t\tassert.equal(provider.allocate(), i)\n\t\t\t}\n\t\t\tassert.equal(provider.allocate(), null)\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "test/node/mqtt.ts",
    "content": "import fs from 'fs'\nimport path from 'path'\nimport { describe, it } from 'node:test'\nimport mqtt, { type IClientOptions } from '../../src'\nimport 'should'\n\ndescribe('mqtt', () => {\n\tdescribe('#connect', () => {\n\t\tit('should return an MqttClient when connect is called with mqtt:/ url', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('mqtt://localhost:1883')\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should throw an error when called with no protocol specified', () => {\n\t\t\t;(() => {\n\t\t\t\tmqtt.connect('foo.bar.com')\n\t\t\t}).should.throw('Missing protocol')\n\t\t})\n\n\t\tit('should throw an error when called with no protocol specified - with options', () => {\n\t\t\t;(() => {\n\t\t\t\tmqtt.connect('tcp://foo.bar.com', { protocol: null })\n\t\t\t}).should.throw('Missing protocol')\n\t\t})\n\n\t\tit('should return an MqttClient with username option set', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('mqtt://user:pass@localhost:1883')\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.options.should.have.property('username', 'user')\n\t\t\tc.options.should.have.property('password', 'pass')\n\t\t\tc.options.should.not.have.property('path')\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient with path set when protocol is ws/wss', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('ws://localhost:1883/mqtt')\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.options.should.have.property('path', '/mqtt')\n\t\t\tc.options.should.have.property('unixSocket', false)\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should work with unix sockets', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('mqtt+unix:///tmp/mqtt.sock')\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.options.should.have.property('path', '/tmp/mqtt.sock')\n\t\t\tc.options.should.have.property('unixSocket', true)\n\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should not set `path` when parsing url', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('mqtt://[::1]')\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.options.should.not.have.property('path')\n\t\t\tc.options.should.have.property('host', '::1')\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient with username and password options set', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('mqtt://user@localhost:1883')\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.options.should.not.have.property('path')\n\t\t\tc.options.should.have.property('username', 'user')\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient with the clientid with random value', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('mqtt://user@localhost:1883')\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.options.should.have.property('clientId')\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient with the clientid with empty string', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('mqtt://user@localhost:1883?clientId=')\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.options.should.have.property('clientId', '')\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient with the clientid option set', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('mqtt://user@localhost:1883?clientId=123')\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.options.should.have.property('clientId', '123')\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient when connect is called with tcp:/ url', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('tcp://localhost')\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient with correct host when called with a host and port', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('tcp://user:pass@localhost:1883')\n\n\t\t\tc.options.should.have.property('hostname', 'localhost')\n\t\t\tc.options.should.have.property('port', 1883)\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tconst sslOpts: IClientOptions = {\n\t\t\tkeyPath: path.join(__dirname, 'helpers', 'private-key.pem'),\n\t\t\tcertPath: path.join(__dirname, 'helpers', 'public-cert.pem'),\n\t\t\tcaPaths: [path.join(__dirname, 'helpers', 'public-cert.pem')],\n\t\t}\n\n\t\tit('should return an MqttClient when connect is called with mqtts:/ url', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('mqtts://localhost', sslOpts)\n\n\t\t\tc.options.should.have.property('protocol', 'mqtts')\n\n\t\t\tc.on('error', () => {})\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient when connect is called with ssl:/ url', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('ssl://localhost', sslOpts)\n\n\t\t\tc.options.should.have.property('protocol', 'ssl')\n\n\t\t\tc.on('error', () => {})\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient when connect is called with ws:/ url', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('ws://localhost', sslOpts)\n\n\t\t\tc.options.should.have.property('protocol', 'ws')\n\n\t\t\tc.on('error', () => {})\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient when connect is called with wss:/ url', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('wss://localhost', sslOpts)\n\n\t\t\tc.options.should.have.property('protocol', 'wss')\n\n\t\t\tc.on('error', () => {})\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tconst sslOpts2: IClientOptions = {\n\t\t\tkey: fs.readFileSync(\n\t\t\t\tpath.join(__dirname, 'helpers', 'private-key.pem'),\n\t\t\t),\n\t\t\tcert: fs.readFileSync(\n\t\t\t\tpath.join(__dirname, 'helpers', 'public-cert.pem'),\n\t\t\t),\n\t\t\tca: [\n\t\t\t\tfs.readFileSync(\n\t\t\t\t\tpath.join(__dirname, 'helpers', 'public-cert.pem'),\n\t\t\t\t),\n\t\t\t],\n\t\t}\n\n\t\tit('should throw an error when it is called with cert and key set but no protocol specified', () => {\n\t\t\t// to do rewrite wrap function\n\t\t\t;(() => {\n\t\t\t\tmqtt.connect(sslOpts2)\n\t\t\t}).should.throw('Missing secure protocol key')\n\t\t})\n\n\t\tit('should throw an error when it is called with cert and key set and protocol other than allowed: mqtt,mqtts,ws,wss,wxs', () => {\n\t\t\t;(() => {\n\t\t\t\t;(sslOpts2 as any).protocol = 'UNKNOWNPROTOCOL'\n\t\t\t\tmqtt.connect(sslOpts2)\n\t\t\t}).should.throw()\n\t\t})\n\n\t\tit('should return a MqttClient with mqtts set when connect is called key and cert set and protocol mqtt', function _test(t, done) {\n\t\t\tsslOpts2.protocol = 'mqtt'\n\t\t\tconst c = mqtt.connect(sslOpts2)\n\n\t\t\tc.options.should.have.property('protocol', 'mqtts')\n\n\t\t\tc.on('error', () => {})\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return a MqttClient with mqtts set when connect is called key and cert set and protocol mqtts', function _test(t, done) {\n\t\t\tsslOpts2.protocol = 'mqtts'\n\t\t\tconst c = mqtt.connect(sslOpts2)\n\n\t\t\tc.options.should.have.property('protocol', 'mqtts')\n\n\t\t\tc.on('error', () => {})\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return a MqttClient with wss set when connect is called key and cert set and protocol ws', function _test(t, done) {\n\t\t\tsslOpts2.protocol = 'ws'\n\t\t\tconst c = mqtt.connect(sslOpts2)\n\n\t\t\tc.options.should.have.property('protocol', 'wss')\n\n\t\t\tc.on('error', () => {})\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return a MqttClient with wss set when connect is called key and cert set and protocol wss', function _test(t, done) {\n\t\t\tsslOpts2.protocol = 'wss'\n\t\t\tconst c = mqtt.connect(sslOpts2)\n\n\t\t\tc.options.should.have.property('protocol', 'wss')\n\n\t\t\tc.on('error', () => {})\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient with the clientid with option of clientId as empty string', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('mqtt://localhost:1883', {\n\t\t\t\tclientId: '',\n\t\t\t})\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.options.should.have.property('clientId', '')\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient with the clientid with option of clientId empty', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('mqtt://localhost:1883')\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.options.should.have.property('clientId')\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient with the clientid with option of with specific clientId', function _test(t, done) {\n\t\t\tconst c = mqtt.connect('mqtt://localhost:1883', {\n\t\t\t\tclientId: '123',\n\t\t\t})\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.options.should.have.property('clientId', '123')\n\t\t\tc.end((err) => done(err))\n\t\t})\n\n\t\tit('should return an MqttClient with mqtts protocol when connect is called with mqtts:/ url and protocol (mqtts:) is specified in options', function _test(t, done) {\n\t\t\tconst url = 'mqtts://localhost:1883'\n\t\t\tconst parsedUrl = new URL(url)\n\t\t\tconst protocol = parsedUrl.protocol as 'mqtt' | 'mqtts'\n\n\t\t\tconst c = mqtt.connect(url, {\n\t\t\t\tprotocol,\n\t\t\t})\n\n\t\t\tc.should.be.instanceOf(mqtt.MqttClient)\n\t\t\tc.options.should.have.property('protocol', 'mqtts')\n\t\t\tc.end((err) => done(err))\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "test/node/mqtt_store.ts",
    "content": "import { describe, it } from 'node:test'\nimport { Store } from '../../src'\nimport 'should'\n\ndescribe('store in lib/connect/index.js (webpack entry point)', () => {\n\tit('should create store', function test(t, done) {\n\t\tconst store = new Store()\n\t\tstore.should.be.instanceOf(Store)\n\t\tdone()\n\t})\n})\n"
  },
  {
    "path": "test/node/secure_client.ts",
    "content": "import path from 'path'\nimport fs from 'fs'\nimport { assert } from 'chai'\nimport { describe, it, after } from 'node:test'\nimport mqtt from '../../src'\nimport abstractClientTests from './abstract_client'\nimport { MqttSecureServer, type MqttServerListener } from './server'\nimport 'should'\nimport getPorts from './helpers/port_list'\n\nconst ports = getPorts(5)\n\nconst port = ports.PORT\nconst KEY = path.join(__dirname, 'helpers', 'tls-key.pem')\nconst CERT = path.join(__dirname, 'helpers', 'tls-cert.pem')\nconst WRONG_CERT = path.join(__dirname, 'helpers', 'wrong-cert.pem')\n\nconst serverListener: MqttServerListener = (client) => {\n\t// this is the Server's MQTT Client\n\tclient.on('connect', (packet) => {\n\t\tif (packet.clientId === 'invalid') {\n\t\t\tclient.connack({ returnCode: 2 })\n\t\t} else {\n\t\t\tserver.emit('connect', client)\n\t\t\tclient.connack({ returnCode: 0 })\n\t\t}\n\t})\n\n\tclient.on('publish', (packet) => {\n\t\tsetImmediate(() => {\n\t\t\t/* jshint -W027 */\n\t\t\t/* eslint default-case:0 */\n\t\t\tswitch (packet.qos) {\n\t\t\t\tcase 0:\n\t\t\t\t\tbreak\n\t\t\t\tcase 1:\n\t\t\t\t\tclient.puback(packet)\n\t\t\t\t\tbreak\n\t\t\t\tcase 2:\n\t\t\t\t\tclient.pubrec(packet)\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t\t/* jshint +W027 */\n\t\t})\n\t})\n\n\tclient.on('pubrel', (packet) => {\n\t\tclient.pubcomp(packet)\n\t})\n\n\tclient.on('pubrec', (packet) => {\n\t\tclient.pubrel(packet)\n\t})\n\n\tclient.on('pubcomp', () => {\n\t\t// Nothing to be done\n\t})\n\n\tclient.on('subscribe', (packet) => {\n\t\tclient.suback({\n\t\t\tmessageId: packet.messageId,\n\t\t\tgranted: packet.subscriptions.map((e) => e.qos),\n\t\t})\n\t})\n\n\tclient.on('unsubscribe', (packet) => {\n\t\tclient.unsuback(packet)\n\t})\n\n\tclient.on('pingreq', () => {\n\t\tclient.pingresp()\n\t})\n}\n\nconst server = new MqttSecureServer(\n\t{\n\t\tkey: fs.readFileSync(KEY),\n\t\tcert: fs.readFileSync(CERT),\n\t},\n\tserverListener,\n).listen(port)\n\ndescribe('MqttSecureClient', () => {\n\tconst config = { protocol: 'mqtts', port, rejectUnauthorized: false }\n\n\tafter(() => {\n\t\t// clean up and make sure the server is no longer listening...\n\t\tif (server.listening) {\n\t\t\tserver.close()\n\t\t}\n\n\t\tprocess.exit(0)\n\t})\n\n\tabstractClientTests(server, config, ports)\n\n\tdescribe('with secure parameters', () => {\n\t\tit('should validate successfully the CA', function _test(t, done) {\n\t\t\tconst client = mqtt.connect({\n\t\t\t\tprotocol: 'mqtts',\n\t\t\t\tport,\n\t\t\t\tca: [fs.readFileSync(CERT)],\n\t\t\t\trejectUnauthorized: true,\n\t\t\t})\n\n\t\t\tclient.on('error', (err) => {\n\t\t\t\tdone(err)\n\t\t\t})\n\n\t\t\tserver.once('connect', () => {\n\t\t\t\tdone()\n\t\t\t})\n\t\t})\n\n\t\tit('should validate successfully the CA using URI', function _test(t, done) {\n\t\t\tconst client = mqtt.connect(`mqtts://localhost:${port}`, {\n\t\t\t\tca: [fs.readFileSync(CERT)],\n\t\t\t\trejectUnauthorized: true,\n\t\t\t})\n\n\t\t\tclient.on('error', (err) => {\n\t\t\t\tdone(err)\n\t\t\t})\n\n\t\t\tserver.once('connect', () => {\n\t\t\t\tdone()\n\t\t\t})\n\t\t})\n\n\t\tit('should validate successfully the CA using URI with path', function _test(t, done) {\n\t\t\tconst client = mqtt.connect(`mqtts://localhost:${port}/`, {\n\t\t\t\tca: [fs.readFileSync(CERT)],\n\t\t\t\trejectUnauthorized: true,\n\t\t\t})\n\n\t\t\tclient.on('error', (err) => {\n\t\t\t\tdone(err)\n\t\t\t})\n\n\t\t\tserver.once('connect', () => {\n\t\t\t\tdone()\n\t\t\t})\n\t\t})\n\n\t\tit('should validate unsuccessfully the CA', function _test(t, done) {\n\t\t\tconst client = mqtt.connect({\n\t\t\t\tprotocol: 'mqtts',\n\t\t\t\tport,\n\t\t\t\tca: [fs.readFileSync(WRONG_CERT)],\n\t\t\t\trejectUnauthorized: true,\n\t\t\t})\n\n\t\t\tclient.once('error', (err) => {\n\t\t\t\terr.should.be.instanceOf(Error)\n\t\t\t\tclient.end((err2) => done(err2))\n\t\t\t})\n\t\t})\n\n\t\tit('should emit close on TLS error', function _test(t, done) {\n\t\t\tconst client = mqtt.connect({\n\t\t\t\tprotocol: 'mqtts',\n\t\t\t\tport,\n\t\t\t\tca: [fs.readFileSync(WRONG_CERT)],\n\t\t\t\trejectUnauthorized: true,\n\t\t\t})\n\n\t\t\tclient.on('error', () => {})\n\n\t\t\tclient.once('close', () => {\n\t\t\t\tclient.end((err) => done(err))\n\t\t\t})\n\t\t})\n\n\t\tit('should support SNI on the TLS connection', function _test(t, done) {\n\t\t\tconst hostname = 'localhost'\n\n\t\t\tserver.removeAllListeners('secureConnection') // clear eventHandler\n\t\t\tserver.once('secureConnection', (tlsSocket) => {\n\t\t\t\t// one time eventHandler\n\t\t\t\tassert.equal((tlsSocket as any).servername, hostname) // validate SNI set\n\t\t\t\tserver.setupConnection(tlsSocket)\n\t\t\t})\n\n\t\t\tconst client = mqtt.connect({\n\t\t\t\tprotocol: 'mqtts',\n\t\t\t\tport,\n\t\t\t\tca: [fs.readFileSync(CERT)],\n\t\t\t\trejectUnauthorized: true,\n\t\t\t\thost: hostname,\n\t\t\t})\n\n\t\t\tclient.on('error', (err) => {\n\t\t\t\tdone(err)\n\t\t\t})\n\n\t\t\tserver.once('connect', () => {\n\t\t\t\tserver.on('secureConnection', server.setupConnection) // reset eventHandler\n\t\t\t\tclient.end((err) => done(err))\n\t\t\t})\n\t\t})\n\t})\n})\n"
  },
  {
    "path": "test/node/server.ts",
    "content": "import net from 'net'\nimport tls, { type TlsOptions } from 'tls'\nimport Connection from 'mqtt-connection'\nimport { type Duplex } from 'stream'\n\nexport type MqttServerListener = (client: Connection) => void\n\n/**\n * MqttServer\n *\n * @param {Function} listener - fired on client connection\n */\nexport class MqttServer extends net.Server {\n\tconnectionList: Duplex[]\n\n\tconstructor(listener: MqttServerListener) {\n\t\tsuper()\n\t\tthis.connectionList = []\n\n\t\tthis.on('connection', (duplex) => {\n\t\t\tthis.connectionList.push(duplex)\n\t\t\tconst connection = new Connection(duplex, () => {\n\t\t\t\tthis.emit('client', connection)\n\t\t\t})\n\t\t})\n\n\t\tif (listener) {\n\t\t\tthis.on('client', listener)\n\t\t}\n\t}\n}\n\n/**\n * MqttServerNoWait (w/o waiting for initialization)\n *\n * @param {Function} listener - fired on client connection\n */\nexport class MqttServerNoWait extends net.Server {\n\tconnectionList: Duplex[]\n\n\tconstructor(listener: MqttServerListener) {\n\t\tsuper()\n\t\tthis.connectionList = []\n\n\t\tthis.on('connection', (duplex) => {\n\t\t\tthis.connectionList.push(duplex)\n\t\t\tconst connection = new Connection(duplex)\n\t\t\t// do not wait for connection to return to send it to the client.\n\t\t\tthis.emit('client', connection)\n\t\t})\n\n\t\tif (listener) {\n\t\t\tthis.on('client', listener)\n\t\t}\n\t}\n}\n\n/**\n * MqttSecureServer\n *\n * @param {Object} opts - server options\n * @param {Function} listener\n */\nexport class MqttSecureServer extends tls.Server {\n\tconnectionList: Duplex[]\n\n\tconstructor(opts: TlsOptions, listener: MqttServerListener) {\n\t\tif (typeof opts === 'function') {\n\t\t\tlistener = opts\n\t\t\topts = {}\n\t\t}\n\n\t\t// sets a listener for the 'connection' event\n\t\tsuper(opts)\n\t\tthis.connectionList = []\n\n\t\tthis.on('secureConnection', (socket) => {\n\t\t\tthis.connectionList.push(socket)\n\t\t\tconst connection = new Connection(socket, () => {\n\t\t\t\tthis.emit('client', connection)\n\t\t\t})\n\t\t})\n\n\t\tif (listener) {\n\t\t\tthis.on('client', listener)\n\t\t}\n\t}\n\n\tsetupConnection(duplex: Duplex) {\n\t\tconst connection = new Connection(duplex, () => {\n\t\t\tthis.emit('client', connection)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "test/node/server_helpers_for_client_tests.ts",
    "content": "import _debug from 'debug'\n\nimport path from 'path'\nimport fs from 'fs'\n\nimport http from 'http'\nimport WebSocket from 'ws'\nimport MQTTConnection from 'mqtt-connection'\nimport { type Server } from 'net'\nimport { MqttServer, MqttSecureServer, type MqttServerListener } from './server'\n\nconst KEY = path.join(__dirname, 'helpers', 'tls-key.pem')\nconst CERT = path.join(__dirname, 'helpers', 'tls-cert.pem')\n\nconst debug = _debug('mqttjs:server_helpers_for_client_tests')\n\n/**\n * This will build the client for the server to use during testing, and set up the\n * server side client based on mqtt-connection for handling MQTT messages.\n * @param {String} protocol - 'mqtt', 'mqtts' or 'ws'\n * @param {Function} handler - event handler\n */\nexport default function serverBuilder(\n\tprotocol: string,\n\thandler?: MqttServerListener,\n): Server {\n\tconst sockets = []\n\tconst defaultHandler: MqttServerListener = (serverClient) => {\n\t\tsockets.push(serverClient)\n\t\tserverClient.on('auth', (packet) => {\n\t\t\tif (serverClient.writable) return false\n\t\t\tconst rc = 'reasonCode'\n\t\t\tconst connack = {}\n\t\t\tconnack[rc] = 0\n\t\t\tserverClient.connack(connack)\n\t\t})\n\t\tserverClient.on('connect', (packet) => {\n\t\t\tif (!serverClient.writable) return false\n\t\t\tlet rc = 'returnCode'\n\t\t\tconst connack = {}\n\t\t\tif (serverClient.options.protocolVersion >= 4) {\n\t\t\t\tconnack['sessionPresent'] = false\n\t\t\t}\n\t\t\tif (\n\t\t\t\tserverClient.options &&\n\t\t\t\tserverClient.options.protocolVersion === 5\n\t\t\t) {\n\t\t\t\trc = 'reasonCode'\n\t\t\t\tif (packet.clientId === 'invalid') {\n\t\t\t\t\tconnack[rc] = 128\n\t\t\t\t} else {\n\t\t\t\t\tconnack[rc] = 0\n\t\t\t\t}\n\t\t\t} else if (packet.clientId === 'invalid') {\n\t\t\t\tconnack[rc] = 2\n\t\t\t} else {\n\t\t\t\tconnack[rc] = 0\n\t\t\t}\n\t\t\tif (packet.properties && packet.properties.authenticationMethod) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tserverClient.connack(connack)\n\t\t})\n\n\t\tserverClient.on('publish', (packet) => {\n\t\t\tif (!serverClient.writable) return false\n\t\t\tsetImmediate(() => {\n\t\t\t\tswitch (packet.qos) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\tserverClient.puback(packet)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\tserverClient.pubrec(packet)\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tserverClient.on('pubrel', (packet) => {\n\t\t\tif (!serverClient.writable) return false\n\t\t\tserverClient.pubcomp(packet)\n\t\t})\n\n\t\tserverClient.on('pubrec', (packet) => {\n\t\t\tif (!serverClient.writable) return false\n\t\t\tserverClient.pubrel(packet)\n\t\t})\n\n\t\tserverClient.on('pubcomp', () => {\n\t\t\t// Nothing to be done\n\t\t})\n\n\t\tserverClient.on('subscribe', (packet) => {\n\t\t\tif (!serverClient.writable) return false\n\t\t\tserverClient.suback({\n\t\t\t\tmessageId: packet.messageId,\n\t\t\t\tgranted: packet.subscriptions.map((e) => e.qos),\n\t\t\t})\n\t\t})\n\n\t\tserverClient.on('unsubscribe', (packet) => {\n\t\t\tif (!serverClient.writable) return false\n\t\t\tpacket.granted = packet.unsubscriptions.map(() => 0)\n\t\t\tserverClient.unsuback(packet)\n\t\t})\n\n\t\tserverClient.on('pingreq', () => {\n\t\t\tif (!serverClient.writable) return false\n\t\t\tserverClient.pingresp()\n\t\t})\n\n\t\tserverClient.on('end', () => {\n\t\t\tdebug('disconnected from server')\n\t\t\tconst index = sockets.findIndex((s) => s === serverClient)\n\t\t\tif (index !== -1) {\n\t\t\t\tsockets.splice(index, 1)\n\t\t\t}\n\t\t})\n\t}\n\n\tif (!handler) {\n\t\thandler = defaultHandler\n\t}\n\n\tlet mqttServer = null\n\n\tif (protocol === 'mqtt') {\n\t\tmqttServer = new MqttServer(handler)\n\t}\n\tif (protocol === 'mqtts') {\n\t\tmqttServer = new MqttSecureServer(\n\t\t\t{\n\t\t\t\tkey: fs.readFileSync(KEY),\n\t\t\t\tcert: fs.readFileSync(CERT),\n\t\t\t},\n\t\t\thandler,\n\t\t)\n\t}\n\tif (protocol === 'ws') {\n\t\tconst attachWebsocketServer = (server) => {\n\t\t\tconst webSocketServer = new WebSocket.Server({\n\t\t\t\tserver,\n\t\t\t\tperMessageDeflate: false,\n\t\t\t})\n\n\t\t\twebSocketServer.on('connection', (ws) => {\n\t\t\t\t// server.connectionList.push(ws)\n\t\t\t\tconst stream = WebSocket.createWebSocketStream(ws)\n\t\t\t\tconst connection = new MQTTConnection(stream)\n\t\t\t\tconnection.protocol = ws.protocol\n\t\t\t\tserver.emit('client', connection)\n\t\t\t\tstream.on('error', () => {})\n\t\t\t\tconnection.on('error', () => {})\n\t\t\t\tconnection.on('close', () => {})\n\t\t\t})\n\t\t}\n\n\t\tconst httpServer = http.createServer()\n\t\t// httpServer.connectionList = []\n\t\tattachWebsocketServer(httpServer)\n\t\thttpServer.on('client', handler)\n\t\tmqttServer = httpServer\n\t}\n\n\tconst originalClose = mqttServer.close\n\tmqttServer.close = (cb) => {\n\t\tsockets.forEach((socket) => {\n\t\t\tsocket.destroy()\n\t\t})\n\t\toriginalClose.call(mqttServer, cb)\n\t}\n\n\treturn mqttServer\n}\n"
  },
  {
    "path": "test/node/socks.ts",
    "content": "import assert from 'assert'\nimport { type AddressInfo, createServer, type Server, type Socket } from 'net'\nimport { describe, it, mock, afterEach, beforeEach } from 'node:test'\nimport openSocks from 'src/lib/connect/socks'\n\ntype State5 = 'new' | 'id' | 'connect'\n\nclass MockServer5 {\n\treadonly connect: Promise<Socket>\n\n\tresponseID = Buffer.from([0x05, 0x00])\n\n\tresponseREQUEST = Buffer.from([\n\t\t0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34,\n\t])\n\n\tprivate server = createServer()\n\n\tprivate onConnect: (socket: Socket) => void\n\n\tprivate onError: (err: any) => void\n\n\tprivate socket?: Socket\n\n\tprivate state: State5 = 'new'\n\n\tprivate destination?: [string, number]\n\n\tconstructor() {\n\t\tthis.connect = new Promise((resolve, reject) => {\n\t\t\tthis.onConnect = resolve\n\t\t\tthis.onError = reject\n\t\t})\n\t}\n\n\tstart(): Promise<number> {\n\t\tthis.server.listen(undefined, 'localhost')\n\n\t\tthis.server.on('connection', this.onConnection)\n\n\t\treturn new Promise((r) => {\n\t\t\tthis.server.once('listening', () => r(this.port()))\n\t\t})\n\t}\n\n\tport(): number {\n\t\treturn (this.server.address() as AddressInfo).port\n\t}\n\n\tdestroy() {\n\t\tthis.server.close()\n\t\tthis.socket?.end()\n\t\tthis.socket?.destroy()\n\t}\n\n\tdestinationAddress(): string | undefined {\n\t\treturn this.destination?.[0]\n\t}\n\n\tdestinationPort(): number | undefined {\n\t\treturn this.destination?.[1]\n\t}\n\n\tprivate onConnection = (socket: Socket) => {\n\t\tif (this.socket) {\n\t\t\tsocket.destroy()\n\t\t\treturn this.onError(new Error('double connect to SOCKS5 server'))\n\t\t}\n\n\t\tthis.socket = socket\n\n\t\tsocket.on('data', this.onData)\n\t}\n\n\tprivate onData = (chunk: Buffer) => {\n\t\tswitch (this.state) {\n\t\t\tcase 'new': {\n\t\t\t\tconst [ver, nmethods] = chunk\n\n\t\t\t\tif (\n\t\t\t\t\tver !== 0x05 ||\n\t\t\t\t\tnmethods === 0 ||\n\t\t\t\t\tchunk.length !== nmethods + 2\n\t\t\t\t) {\n\t\t\t\t\treturn this.onError(new Error('bad ID packet'))\n\t\t\t\t}\n\n\t\t\t\tif (chunk.subarray(2, 2 + nmethods).indexOf(0x00) === -1) {\n\t\t\t\t\treturn this.onError(new Error('no supported METHOD'))\n\t\t\t\t}\n\n\t\t\t\tthis.socket?.write?.(this.responseID)\n\t\t\t\tthis.state = 'id'\n\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcase 'id':\n\t\t\t\tthis.destination = this.parseConnect(chunk)\n\n\t\t\t\tif (this.destination === undefined) {\n\t\t\t\t\treturn this.onError(new Error('bad REQUEST packet'))\n\t\t\t\t}\n\n\t\t\t\tthis.socket?.write(this.responseREQUEST)\n\n\t\t\t\tthis.state = 'connect'\n\t\t\t\tthis.socket.off('data', this.onData)\n\t\t\t\tthis.onConnect(this.socket)\n\n\t\t\t\tbreak\n\t\t}\n\t}\n\n\tprivate parseConnect(buf: Buffer): [string, number] | undefined {\n\t\tconst [ver, cmd, rsv, atyp] = buf\n\n\t\tif (ver !== 0x05 || cmd !== 0x01 || rsv !== 0x00) return undefined\n\n\t\tconst port = (buf[buf.length - 2] << 8) | buf[buf.length - 1]\n\n\t\tswitch (atyp) {\n\t\t\tcase 0x01:\n\t\t\t\tif (buf.length !== 10) return undefined\n\n\t\t\t\treturn [buf.subarray(4, 8).join('.'), port]\n\n\t\t\tcase 0x03:\n\t\t\t\tif (buf.length !== 7 + buf[4]) return undefined\n\n\t\t\t\treturn [buf.subarray(5, 5 + buf[4]).toString('ascii'), port]\n\n\t\t\tdefault:\n\t\t\t\treturn undefined\n\t\t}\n\t}\n}\n\ndescribe('SOCKS layer', { timeout: 1000 }, () => {\n\tlet server5!: MockServer5\n\tlet server4: Server | undefined\n\n\tbeforeEach(() => {\n\t\tserver5 = new MockServer5()\n\t})\n\n\tafterEach(() => {\n\t\tserver5.destroy()\n\t\tserver4?.close()\n\t})\n\n\tit('should resolve hostnames locally for socks5', async () => {\n\t\tconst port = await server5.start()\n\n\t\tconst lookup = mock.fn((_: string) =>\n\t\t\tPromise.resolve({ address: '1.2.3.4' }),\n\t\t)\n\n\t\tconst stream = openSocks(\n\t\t\t'foo.bar',\n\t\t\t1883,\n\t\t\t`socks5://localhost:${port}`,\n\t\t\t{\n\t\t\t\tlookup,\n\t\t\t},\n\t\t)\n\n\t\tawait server5.connect\n\n\t\tstream.destroy()\n\n\t\tawait new Promise((r) => {\n\t\t\tstream.once('close', r)\n\t\t})\n\n\t\tassert.strictEqual(lookup.mock.callCount(), 1)\n\t\tassert.strictEqual(lookup.mock.calls[0].arguments[0], 'foo.bar')\n\t\tassert.strictEqual(server5.destinationAddress(), '1.2.3.4')\n\t\tassert.strictEqual(server5.destinationPort(), 1883)\n\t})\n\n\tit('should resolve hostnames remotely for socks5h', async () => {\n\t\tconst port = await server5.start()\n\n\t\tconst lookup = mock.fn((_: string) =>\n\t\t\tPromise.resolve({ address: '1.2.3.4' }),\n\t\t)\n\n\t\tconst stream = openSocks(\n\t\t\t'foo.bar',\n\t\t\t1883,\n\t\t\t`socks5h://localhost:${port}`,\n\t\t\t{\n\t\t\t\tlookup,\n\t\t\t},\n\t\t)\n\n\t\tawait server5.connect\n\n\t\tstream.destroy()\n\n\t\tawait new Promise((r) => {\n\t\t\tstream.once('close', r)\n\t\t})\n\n\t\tassert.strictEqual(lookup.mock.callCount(), 0)\n\t\tassert.strictEqual(server5.destinationAddress(), 'foo.bar')\n\t\tassert.strictEqual(server5.destinationPort(), 1883)\n\t})\n\n\tit('errors during name resolution should be emitted on stream', async () => {\n\t\tconst ERROR = new Error()\n\n\t\tconst lookup = mock.fn((address) => Promise.reject(ERROR))\n\n\t\tconst stream = openSocks('foo.bar', 1883, 'socks5://localhost:6666', {\n\t\t\tlookup,\n\t\t})\n\n\t\tconst error = await new Promise((r) => {\n\t\t\tstream.once('error', r)\n\t\t})\n\n\t\tassert.strictEqual(error, ERROR)\n\t})\n\n\tit('errors during SOCKS connect should be emitted on stream', async () => {\n\t\tconst port = await server5.start()\n\t\tserver5.responseID = Buffer.from([0x00, 0x00])\n\n\t\tconst lookup = mock.fn((_: string) =>\n\t\t\tPromise.resolve({ address: '1.2.3.4' }),\n\t\t)\n\n\t\tconst stream = openSocks(\n\t\t\t'foo.bar',\n\t\t\t1883,\n\t\t\t`socks5://localhost:${port}`,\n\t\t\t{\n\t\t\t\tlookup,\n\t\t\t},\n\t\t)\n\n\t\tconst err = await new Promise((r) => {\n\t\t\tstream.once('error', r)\n\t\t})\n\n\t\tstream.destroy()\n\n\t\tassert(err instanceof Error)\n\t})\n\n\tit('data flows through the stream after SOCKS has connected', async () => {\n\t\tconst port = await server5.start()\n\n\t\tconst lookup = mock.fn((_: string) =>\n\t\t\tPromise.resolve({ address: '1.2.3.4' }),\n\t\t)\n\n\t\tconst stream = openSocks(\n\t\t\t'foo.bar',\n\t\t\t1883,\n\t\t\t`socks5://localhost:${port}`,\n\t\t\t{\n\t\t\t\tlookup,\n\t\t\t},\n\t\t)\n\n\t\tconst socket = await server5.connect\n\n\t\tsocket.once('data', (chunk) => socket.write(`${chunk.toString()} pong`))\n\n\t\tconst response = await new Promise((resolve, reject) => {\n\t\t\tstream.once('error', (err) => {\n\t\t\t\treject(err)\n\t\t\t})\n\n\t\t\tstream.once('data', (chunk) => {\n\t\t\t\tresolve(chunk.toString())\n\t\t\t})\n\n\t\t\tstream.write('ping')\n\t\t})\n\n\t\tserver5.destroy()\n\t\tstream.destroy()\n\n\t\tassert.strictEqual(response, 'ping pong')\n\t})\n\n\tit('data written to the stream is buffered until SOCKS has connected', async () => {\n\t\tconst port = await server5.start()\n\n\t\tlet startNameResolution!: () => undefined\n\t\tconst resolutionPromise = new Promise<void>((r) => {\n\t\t\tstartNameResolution = r as () => undefined\n\t\t})\n\n\t\tconst lookup = mock.fn((_: string) =>\n\t\t\tresolutionPromise.then(() => ({\n\t\t\t\taddress: '1.2.3.4',\n\t\t\t})),\n\t\t)\n\n\t\tconst stream = openSocks(\n\t\t\t'foo.bar',\n\t\t\t1883,\n\t\t\t`socks5://localhost:${port}`,\n\t\t\t{\n\t\t\t\tlookup,\n\t\t\t},\n\t\t)\n\n\t\tstream.write('ping')\n\t\tstartNameResolution()\n\n\t\tconst socket = await server5.connect\n\n\t\tsocket.once('data', (chunk) => socket.write(`${chunk.toString()} pong`))\n\n\t\tconst response = await new Promise((resolve, reject) => {\n\t\t\tstream.once('error', (err) => {\n\t\t\t\treject(err)\n\t\t\t})\n\n\t\t\tstream.once('data', (chunk) => {\n\t\t\t\tresolve(chunk.toString())\n\t\t\t})\n\t\t})\n\n\t\tserver5.destroy()\n\t\tstream.destroy()\n\n\t\tassert.strictEqual(response, 'ping pong')\n\t})\n\n\tit('closing the stream closes the connection', async () => {\n\t\tconst port = await server5.start()\n\n\t\tconst lookup = mock.fn((_: string) =>\n\t\t\tPromise.resolve({ address: '1.2.3.4' }),\n\t\t)\n\n\t\tconst stream = openSocks(\n\t\t\t'foo.bar',\n\t\t\t1883,\n\t\t\t`socks5://localhost:${port}`,\n\t\t\t{\n\t\t\t\tlookup,\n\t\t\t},\n\t\t)\n\n\t\tconst socket = await server5.connect\n\n\t\tstream.destroy()\n\n\t\tawait new Promise((r) => {\n\t\t\tsocket.once('close', r)\n\t\t})\n\t})\n\n\tit('closing the connection closes the stream', async () => {\n\t\tconst port = await server5.start()\n\n\t\tconst lookup = mock.fn((_: string) =>\n\t\t\tPromise.resolve({ address: '1.2.3.4' }),\n\t\t)\n\n\t\tconst stream = openSocks(\n\t\t\t'foo.bar',\n\t\t\t1883,\n\t\t\t`socks5://localhost:${port}`,\n\t\t\t{\n\t\t\t\tlookup,\n\t\t\t},\n\t\t)\n\n\t\tconst socket = await server5.connect\n\t\tsocket.destroy()\n\n\t\tawait new Promise((r) => {\n\t\t\tstream.once('close', r)\n\t\t})\n\t})\n\n\tit('an invalid protocol errors the stream', async () => {\n\t\tconst port = await server5.start()\n\n\t\tconst lookup = mock.fn((_: string) =>\n\t\t\tPromise.resolve({ address: '1.2.3.4' }),\n\t\t)\n\n\t\tconst stream = openSocks('foo.bar', 1883, `socks://localhost:${port}`, {\n\t\t\tlookup,\n\t\t})\n\n\t\tconst error = await new Promise((r) => {\n\t\t\tstream.once('error', r)\n\t\t})\n\n\t\tassert(error instanceof Error)\n\t})\n\n\tit('an invalid URL errors the stream', async () => {\n\t\tconst port = await server5.start()\n\n\t\tconst lookup = mock.fn((_: string) =>\n\t\t\tPromise.resolve({ address: '1.2.3.4' }),\n\t\t)\n\n\t\tconst stream = openSocks('foo.bar', 1883, `socks:localhost:${port}`, {\n\t\t\tlookup,\n\t\t})\n\n\t\tconst error = await new Promise((r) => {\n\t\t\tstream.once('error', r)\n\t\t})\n\n\t\tassert(error instanceof Error)\n\t})\n\n\tit('should resolve hostnames locally for socks4', async () => {\n\t\tlet onConnect!: (socket: Socket) => void\n\t\tconst connect = new Promise<Socket>((r) => {\n\t\t\tonConnect = mock.fn((socket: Socket) => {\n\t\t\t\tsocket.destroy()\n\t\t\t\tr(socket)\n\t\t\t})\n\t\t})\n\n\t\tserver4 = await new Promise<Server>((resolve, reject) => {\n\t\t\tconst server = createServer(onConnect)\n\n\t\t\tserver.on('listening', () => resolve(server))\n\t\t\tserver.on('error', reject)\n\n\t\t\tserver.listen()\n\t\t})\n\n\t\tconst lookup = mock.fn((_: string) =>\n\t\t\tPromise.resolve({ address: '1.2.3.4' }),\n\t\t)\n\n\t\tconst stream = openSocks(\n\t\t\t'foo.bar',\n\t\t\t1883,\n\t\t\t`socks4://localhost:${(server4.address() as AddressInfo).port}`,\n\t\t\t{\n\t\t\t\tlookup,\n\t\t\t},\n\t\t)\n\n\t\tconst socket = await connect\n\n\t\tsocket.destroy()\n\t\tstream.destroy()\n\n\t\tassert.strictEqual(lookup.mock.callCount(), 1)\n\t\tassert.strictEqual(lookup.mock.calls[0].arguments[0], 'foo.bar')\n\t})\n\n\tit('should resolve hostnames remotely for socks4a', async () => {\n\t\tlet onConnect!: (socket: Socket) => void\n\t\tconst connect = new Promise<Socket>((r) => {\n\t\t\tonConnect = mock.fn((socket: Socket) => {\n\t\t\t\tsocket.destroy()\n\t\t\t\tr(socket)\n\t\t\t})\n\t\t})\n\n\t\tserver4 = await new Promise<Server>((resolve, reject) => {\n\t\t\tconst server = createServer(onConnect)\n\n\t\t\tserver.on('listening', () => resolve(server))\n\t\t\tserver.on('error', reject)\n\n\t\t\tserver.listen()\n\t\t})\n\n\t\tconst lookup = mock.fn((_: string) =>\n\t\t\tPromise.resolve({ address: '1.2.3.4' }),\n\t\t)\n\n\t\tconst stream = openSocks(\n\t\t\t'foo.bar',\n\t\t\t1883,\n\t\t\t`socks4a://localhost:${(server4.address() as AddressInfo).port}`,\n\t\t\t{\n\t\t\t\tlookup,\n\t\t\t},\n\t\t)\n\n\t\tconst socket = await connect\n\n\t\tsocket.destroy()\n\t\tstream.destroy()\n\n\t\tassert.strictEqual(lookup.mock.callCount(), 0)\n\t})\n})\n"
  },
  {
    "path": "test/node/store.ts",
    "content": "import { describe } from 'node:test'\nimport Store from '../../src/lib/store'\nimport abstractTest from './abstract_store'\n\ndescribe('in-memory store', () => {\n\tabstractTest(function test(done) {\n\t\tdone(null, new Store())\n\t})\n})\n"
  },
  {
    "path": "test/node/unique_message_id_provider_client.ts",
    "content": "import { describe, after } from 'node:test'\nimport abstractClientTests from './abstract_client'\nimport serverBuilder from './server_helpers_for_client_tests'\nimport { UniqueMessageIdProvider, type IClientOptions } from '../../src'\nimport getPorts from './helpers/port_list'\n\nconst ports = getPorts(3)\n\ndescribe('UniqueMessageIdProviderMqttClient', () => {\n\tconst server = serverBuilder('mqtt')\n\tconst config: IClientOptions = {\n\t\tprotocol: 'mqtt',\n\t\tport: ports.PORTAND400,\n\t\tmessageIdProvider: new UniqueMessageIdProvider(),\n\t}\n\tserver.listen(ports.PORTAND400)\n\n\tafter(() => {\n\t\t// clean up and make sure the server is no longer listening...\n\t\tif (server.listening) {\n\t\t\tserver.close()\n\t\t}\n\n\t\tprocess.exit(0)\n\t})\n\n\tabstractClientTests(server, config, ports)\n})\n"
  },
  {
    "path": "test/node/util.ts",
    "content": "import { Transform } from 'readable-stream'\n\nconst testStream = () => {\n\treturn new Transform({\n\t\ttransform(buf, enc, cb) {\n\t\t\tsetImmediate(() => {\n\t\t\t\tthis.push(buf)\n\t\t\t\tcb()\n\t\t\t})\n\t\t},\n\t})\n}\n\nexport default testStream\n"
  },
  {
    "path": "test/node/websocket_client.ts",
    "content": "import http from 'http'\nimport WebSocket from 'ws'\nimport MQTTConnection from 'mqtt-connection'\nimport assert from 'assert'\nimport { after, describe, it } from 'node:test'\nimport abstractClientTests from './abstract_client'\nimport getPorts from './helpers/port_list'\nimport { MqttServerNoWait } from './server'\nimport mqtt, { type IClientOptions } from '../../src'\n\nconst ports = getPorts(4)\n\nconst port = 9999\nconst httpServer = http.createServer()\nlet lastProcotols = new Set<string>()\nfunction attachWebsocketServer(httpServer2) {\n\tconst webSocketServer = new WebSocket.Server({\n\t\tserver: httpServer2,\n\t\thandleProtocols: (protocols: Set<string>, request: any) => {\n\t\t\tlastProcotols = protocols\n\t\t\treturn [...protocols][0]\n\t\t},\n\t\tperMessageDeflate: false,\n\t})\n\n\twebSocketServer.on('connection', (ws) => {\n\t\tconst stream = WebSocket.createWebSocketStream(ws)\n\t\tconst connection = new MQTTConnection(stream)\n\t\tconnection.protocol = ws.protocol\n\t\thttpServer2.emit('client', connection)\n\t\tstream.on('error', () => {})\n\t\tconnection.on('error', () => {})\n\t})\n\n\treturn httpServer2\n}\n\nfunction attachClientEventHandlers(client) {\n\tclient.on('connect', (packet) => {\n\t\tif (packet.clientId === 'invalid') {\n\t\t\tclient.connack({ returnCode: 2 })\n\t\t} else {\n\t\t\thttpServer.emit('connect', client)\n\t\t\tclient.connack({ returnCode: 0 })\n\t\t}\n\t})\n\n\tclient.on('publish', (packet) => {\n\t\tsetImmediate(() => {\n\t\t\tswitch (packet.qos) {\n\t\t\t\tcase 0:\n\t\t\t\t\tbreak\n\t\t\t\tcase 1:\n\t\t\t\t\tclient.puback(packet)\n\t\t\t\t\tbreak\n\t\t\t\tcase 2:\n\t\t\t\t\tclient.pubrec(packet)\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t})\n\t})\n\n\tclient.on('pubrel', (packet) => {\n\t\tclient.pubcomp(packet)\n\t})\n\n\tclient.on('pubrec', (packet) => {\n\t\tclient.pubrel(packet)\n\t})\n\n\tclient.on('pubcomp', () => {\n\t\t// Nothing to be done\n\t})\n\n\tclient.on('subscribe', (packet) => {\n\t\tclient.suback({\n\t\t\tmessageId: packet.messageId,\n\t\t\tgranted: packet.subscriptions.map((e) => e.qos),\n\t\t})\n\t})\n\n\tclient.on('unsubscribe', (packet) => {\n\t\tclient.unsuback(packet)\n\t})\n\n\tclient.on('pingreq', () => {\n\t\tclient.pingresp()\n\t})\n}\n\nattachWebsocketServer(httpServer)\n\nhttpServer.on('client', attachClientEventHandlers).listen(port)\n\ndescribe('Websocket Client', () => {\n\tconst baseConfig: IClientOptions = { protocol: 'ws', port }\n\n\tfunction makeOptions(custom?: IClientOptions): IClientOptions {\n\t\treturn { ...baseConfig, ...(custom || {}) }\n\t}\n\n\tafter(() => {\n\t\t// clean up and make sure the server is no longer listening...\n\t\tif (httpServer.listening) {\n\t\t\thttpServer.close()\n\t\t}\n\n\t\tprocess.exit(0)\n\t})\n\n\tit('should use mqtt as the protocol by default', function _test(t, done) {\n\t\thttpServer.once('client', (client) => {\n\t\t\tassert.strictEqual(client.protocol, 'mqtt')\n\t\t})\n\t\tconst client = mqtt.connect(makeOptions())\n\n\t\tclient.on('connect', () => {\n\t\t\tclient.end(true, (err) => done(err))\n\t\t})\n\t})\n\n\tit('should be able to transform the url (for e.g. to sign it)', function _test(t, done) {\n\t\tconst baseUrl = 'ws://localhost:9999/mqtt'\n\t\tconst sig = '?AUTH=token'\n\t\tconst expected = baseUrl + sig\n\t\tlet actual: string\n\t\tconst opts = makeOptions({\n\t\t\tpath: '/mqtt',\n\t\t\ttransformWsUrl(url, opt, client) {\n\t\t\t\tassert.equal(url, baseUrl)\n\t\t\t\tassert.strictEqual(opt, opts)\n\t\t\t\tassert.strictEqual(client.options, opts)\n\t\t\t\tassert.strictEqual(typeof opt.transformWsUrl, 'function')\n\t\t\t\tassert(client instanceof mqtt.MqttClient)\n\t\t\t\turl += sig\n\t\t\t\tactual = url\n\t\t\t\treturn url\n\t\t\t},\n\t\t})\n\t\tconst client = mqtt.connect(opts)\n\n\t\tclient.on('connect', () => {\n\t\t\t// `url` is set in `connect/ws.ts` `streamBuilder`\n\t\t\tassert.equal((client.stream as any).url, expected)\n\t\t\tassert.equal(actual, expected)\n\t\t\tclient.end(true, (err) => done(err))\n\t\t})\n\t})\n\n\tit('should be able to create custom Websocket instance', function _test(t, done) {\n\t\tconst baseUrl = 'ws://localhost:9999/mqtt'\n\t\tlet urlInCallback: string\n\t\tconst opts = makeOptions({\n\t\t\tpath: '/mqtt',\n\t\t\tcreateWebsocket(\n\t\t\t\turl: string,\n\t\t\t\twebsocketSubProtocols: string[],\n\t\t\t\toptions: IClientOptions,\n\t\t\t) {\n\t\t\t\turlInCallback = url\n\t\t\t\tassert.equal(url, baseUrl)\n\t\t\t\tconst subProtocols = [\n\t\t\t\t\twebsocketSubProtocols[0],\n\t\t\t\t\t'myCustomSubprotocol',\n\t\t\t\t]\n\t\t\t\treturn new WebSocket(url, subProtocols)\n\t\t\t},\n\t\t})\n\t\tconst client = mqtt.connect(opts)\n\t\tclient.on('connect', () => {\n\t\t\tassert.equal((client.stream as any).url, urlInCallback)\n\t\t\tassert.equal(baseUrl, urlInCallback)\n\t\t\tassert.equal('myCustomSubprotocol', [...lastProcotols][1])\n\t\t\tclient.end(true, (err) => done(err))\n\t\t})\n\t})\n\n\tit('should use mqttv3.1 as the protocol if using v3.1', function _test(t, done) {\n\t\thttpServer.once('client', (client) => {\n\t\t\tassert.strictEqual(client.protocol, 'mqttv3.1')\n\t\t})\n\n\t\tconst opts = makeOptions({\n\t\t\tprotocolId: 'MQIsdp',\n\t\t\tprotocolVersion: 3,\n\t\t})\n\n\t\tconst client = mqtt.connect(opts)\n\n\t\tclient.on('connect', () => {\n\t\t\tclient.end(true, (err) => done(err))\n\t\t})\n\t})\n\n\tdescribe('reconnecting', () => {\n\t\tit(\n\t\t\t'should reconnect to multiple host-ports-protocol combinations if servers is passed',\n\t\t\t{\n\t\t\t\ttimeout: 15000,\n\t\t\t},\n\t\t\tfunction _test(t, done) {\n\t\t\t\tlet serverPort42Connected = false\n\t\t\t\tconst handler = (serverClient) => {\n\t\t\t\t\tserverClient.on('connect', (packet) => {\n\t\t\t\t\t\tserverClient.connack({ returnCode: 0 })\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tconst actualURL41 = `wss://localhost:${ports.PORTAND41}/`\n\t\t\t\tconst actualURL42 = `ws://localhost:${ports.PORTAND42}/`\n\t\t\t\tconst serverPort41 = new MqttServerNoWait(handler).listen(\n\t\t\t\t\tports.PORTAND41,\n\t\t\t\t)\n\t\t\t\tconst serverPort42 = new MqttServerNoWait(handler).listen(\n\t\t\t\t\tports.PORTAND42,\n\t\t\t\t)\n\n\t\t\t\tserverPort42.on('listening', () => {\n\t\t\t\t\tconst client = mqtt.connect({\n\t\t\t\t\t\tprotocol: 'wss',\n\t\t\t\t\t\tservers: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tport: ports.PORTAND42,\n\t\t\t\t\t\t\t\thost: 'localhost',\n\t\t\t\t\t\t\t\tprotocol: 'ws',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{ port: ports.PORTAND41, host: 'localhost' },\n\t\t\t\t\t\t],\n\t\t\t\t\t\tkeepalive: 50,\n\t\t\t\t\t})\n\t\t\t\t\tserverPort41.once('client', (c) => {\n\t\t\t\t\t\tassert.equal(\n\t\t\t\t\t\t\t(client.stream as any).url,\n\t\t\t\t\t\t\tactualURL41,\n\t\t\t\t\t\t\t'Protocol for second client should use the default protocol: wss, on port: port + 41.',\n\t\t\t\t\t\t)\n\t\t\t\t\t\tassert(serverPort42Connected)\n\t\t\t\t\t\tc.stream.destroy()\n\t\t\t\t\t\tclient.end(true, (err1) => {\n\t\t\t\t\t\t\tserverPort41.close((err2) => {\n\t\t\t\t\t\t\t\tdone(err1 || err2)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t\tserverPort42.once('client', (c) => {\n\t\t\t\t\t\tserverPort42Connected = true\n\t\t\t\t\t\tassert.equal(\n\t\t\t\t\t\t\t(client.stream as any).url,\n\t\t\t\t\t\t\tactualURL42,\n\t\t\t\t\t\t\t'Protocol for connection should use ws, on port: port + 42.',\n\t\t\t\t\t\t)\n\t\t\t\t\t\tc.stream.destroy()\n\t\t\t\t\t\tserverPort42.close()\n\t\t\t\t\t})\n\n\t\t\t\t\tclient.once('connect', () => {\n\t\t\t\t\t\tclient.stream.destroy()\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t},\n\t\t)\n\t})\n\n\tabstractClientTests(httpServer, makeOptions(), ports)\n})\n"
  },
  {
    "path": "tsconfig.build.json",
    "content": "{\n    \"extends\": \"./tsconfig.json\",\n    \"exclude\": [\"node_modules\", \"test\", \"dist\", \"build\", \"example.ts\"],\n  }\n  "
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"$schema\": \"https://json.schemastore.org/tsconfig\",\n\t\"extends\": \"@tsconfig/node20/tsconfig.json\",\n\t\"compilerOptions\": {\n\t\t\"lib\": [\"es2023\", \"DOM\"],\n\t\t\"pretty\": true,\n\t\t\"declaration\": true,\n\t\t\"newLine\": \"LF\",\n\t\t\"preserveConstEnums\": false,\n\t\t\"isolatedModules\": false,\n\t\t\"sourceMap\": true,\n\t\t\"allowJs\": false,\n\t\t\"checkJs\": false,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"experimentalDecorators\": false,\n\t\t\"emitDecoratorMetadata\": false,\n\t\t\"noErrorTruncation\": true,\n\t\t\"allowUnreachableCode\": false,\n\t\t\"allowUnusedLabels\": false,\n\t\t\"alwaysStrict\": true,\n\n\t\t\"removeComments\": true,\n\t\t\"allowSyntheticDefaultImports\": true,\n\t\t\"outDir\": \"./build\",\n\t\t\"baseUrl\": \"./\",\n\t\t\"incremental\": true,\n\t\t\"preserveSymlinks\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"strict\": false,\n\t\t\"strictBindCallApply\": false,\n\t\t\"strictBuiltinIteratorReturn\": false,\n\t\t\"strictFunctionTypes\": false,\n\t\t\"strictNullChecks\": false,\n\t\t\"strictPropertyInitialization\": false,\n\t\t\"noImplicitAny\": false,\n\t\t\"noImplicitThis\": false,\n\t\t\"useUnknownInCatchVariables\": false\n\t},\n\t\"include\": [\n\t\t\"src\",\n\t\t\"test\",\n\t\t\"example.ts\"\n\t],\n\t\"exclude\": [\n\t\t\"dist\",\n\t\t\"test/browser\"\n\t]\n}"
  },
  {
    "path": "web-test-runner.config.mjs",
    "content": "// Docs: https://modern-web.dev/docs/test-runner/cli-and-configuration/\nimport { playwrightLauncher } from '@web/test-runner-playwright';\nimport { start } from 'aedes-cli'\n\nconst wsPort = 4000\nconst wssPort = 4443\n\nawait start({\n    protos: ['tcp', 'tls', 'ws', 'wss'],\n    wsPort,\n    wssPort,\n    key: './test/browser/certs/server-key.pem',\n    cert: './test/browser/certs/server-cert.pem',\n    verbose: true,\n    stats: false\n})\n\nconsole.log('Broker setup done')\n\n/** @type { import('@web/test-runner-playwright').PlaywrightLauncher[] } */\nconst browsers = ['chromium', 'firefox', 'webkit'].map(product => playwrightLauncher({\n    product,\n    createBrowserContext: ({ browser, config }) => {\n\n        // ignore HTTPS errors\n        const context = browser.newContext({\n            ignoreHTTPSErrors: true\n        })\n        return context\n    },\n    launchOptions: { headless: true, devtools: false }\n}))\n\n/**\n * @type { import('@web/test-runner').TestRunnerConfig }\n */\nexport default {\n    // https://modern-web.dev/docs/test-runner/browser-launchers/playwright/#testing-multiple-browsers\n    // Requires: @web/test-runner-playwright\n    browsers,\n    playwright: true,\n    concurrency: 1,\n    files: ['./test/browser/test.js'],\n    nodeResolve: true,\n    testFramework: {\n        config: {\n            timeout: '10000',\n        },\n    },\n    // manual: true,\n    // open: true,\n    // rootDir: path.resolve(__dirname)\n    // http2: true,\n    // protocol: 'https:',\n    // sslCert: './test/certs/server-cert.pem',\n    // sslKey: './test/certs/server-key.pem',\n    testRunnerHtml: (testFrameworkImport) =>\n        `<html>\n        <body>\n            <script src=\"dist/mqtt.js\"></script>\n            <!-- web worker code -->\n            <link rel=\"modulepreload\" href=\"test/browser/worker.js\">\n            <script>\n                window.wsPort = ${wsPort};\n                window.wssPort = ${wssPort};\n            </script>\n            <script type=\"module\" src=\"${testFrameworkImport}\"></script>\n        </body>\n    </html>`\n};"
  }
]