├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── build.yml │ ├── deploy.yml │ └── snyk.yml ├── .gitignore ├── .prettierrc ├── CHANGELOG ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── emitter.ts ├── got_emitter.ts ├── index.ts ├── tracker.ts └── version.ts ├── test ├── emitter.ts ├── got_emitter.ts └── tracker.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | # don't ever lint node_modules 2 | node_modules 3 | # don't lint build output (make sure it's set to your correct build folder name) 4 | dist 5 | # don't lint nyc coverage output 6 | coverage 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | module.exports = { 3 | root: true, 4 | parser: '@typescript-eslint/parser', 5 | plugins: [ 6 | '@typescript-eslint', 7 | 'ava' 8 | ], 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:@typescript-eslint/recommended', 12 | 'plugin:ava/recommended' 13 | ] 14 | }; 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior or code snippets that produce the issue. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Environment (please complete the following information):** 23 | - OS: [e.g. Ubuntu 20.04] 24 | - Version [e.g. 12.18.2] 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [10.x, 12.x, 14.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - uses: actions/cache@v2 20 | with: 21 | path: ~/.npm 22 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 23 | restore-keys: | 24 | ${{ runner.os }}-node- 25 | 26 | - run: npm ci 27 | - run: npm run build 28 | - run: npm run test+coverage 29 | 30 | - name: Coveralls 31 | continue-on-error: true 32 | uses: coverallsapp/github-action@master 33 | with: 34 | github-token: ${{ secrets.GITHUB_TOKEN }} 35 | flag-name: run-${{ matrix.node-version }} 36 | parallel: true 37 | 38 | coverage: 39 | needs: build 40 | runs-on: ubuntu-latest 41 | 42 | steps: 43 | - name: Coveralls Finish 44 | uses: coverallsapp/github-action@master 45 | with: 46 | github-token: ${{ secrets.GITHUB_TOKEN }} 47 | parallel-finished: true -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Deploy 5 | 6 | on: 7 | push: 8 | tags: 9 | - '*.*.*' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v1 17 | with: 18 | node-version: 12 19 | - run: npm ci 20 | - run: npm run build 21 | - run: npm test 22 | - name: Get tag and tracker version information 23 | id: version 24 | run: | 25 | echo ::set-output name=TAG_VERSION::${GITHUB_REF#refs/*/} 26 | echo "##[set-output name=TRACKER_VERSION;]$(node -p "require('./package.json').version")" 27 | - name: Fail if version mismatch 28 | if: ${{ steps.version.outputs.TAG_VERSION != steps.version.outputs.TRACKER_VERSION }} 29 | run: | 30 | echo "Tag version (${{ steps.version.outputs.TAG_VERSION }}) doesn't match version in project (${{ steps.version.outputs.TRACKER_VERSION }})" 31 | exit 1 32 | 33 | publish-npm: 34 | needs: build 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v2 38 | - uses: actions/setup-node@v1 39 | with: 40 | node-version: 12 41 | registry-url: https://registry.npmjs.org/ 42 | - run: npm ci 43 | - run: npm run build 44 | - run: npm publish 45 | env: 46 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 47 | -------------------------------------------------------------------------------- /.github/workflows/snyk.yml: -------------------------------------------------------------------------------- 1 | name: Snyk 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | security: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Run Snyk to check for vulnerabilities 13 | uses: snyk/actions/node@master 14 | env: 15 | SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} 16 | with: 17 | command: monitor 18 | args: --project-name=snowplow-nodejs-tracker 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # node.js 2 | lib-cov 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | npm-debug.log 11 | node_modules 12 | dist 13 | 14 | # Coverage 15 | coverage 16 | .nyc_output 17 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Version 0.4.4 (2020-10-20) 2 | -------------------------- 3 | Bump snowplow-tracker-core to 0.9.4 (#74) 4 | Update coveralls github action with improved parallel build support (#76) 5 | Bump object-path from 0.11.4 to 0.11.5 (#73) 6 | 7 | Version 0.4.3 (2020-10-10) 8 | -------------------------- 9 | Bump snowplow-tracker-core to 0.9.3 (#70) 10 | Update Snyk to monitor project `snowplow-nodejs-tracker` (#69) 11 | 12 | Version 0.4.2 (2020-09-28) 13 | -------------------------- 14 | Bump got to 11.7.0 (#67) 15 | Bump tsconfig target to match got (#66) 16 | 17 | Version 0.4.1 (2020-09-18) 18 | -------------------------- 19 | Bump snowplow-tracker-core to 0.9.2 20 | 21 | Version 0.4.0 (2020-09-04) 22 | -------------------------- 23 | Switch from 'request' to 'got' (#61) 24 | Remove Vagrant image (#56) 25 | Add Snyk support (#60) 26 | Switch to GitHub Actions (#54) 27 | Add setNetworkUserId method (#23) 28 | Add setDomainUserId method (#24) 29 | Add support for dvce_sent_tstamp (#27) 30 | Bump snowplow-tracker-core to 0.9.1 (#58) 31 | Switch out Mocha for Ava (#59) 32 | Add Typescript support (#13) 33 | Bump NodeJS to support active LTS releases (#55) 34 | Switch to RollupJS for building ES Module and CJS versions (#57) 35 | Update project dependencies (#49) 36 | Update flush to not send a request if the buffer is empty (#53) 37 | Add Snowplow Maintenance Badge (#47) 38 | Add CONTRIBUTING.md (#46) 39 | 40 | Version 0.3.0 (2017-04-28) 41 | -------------------------- 42 | Add npm credentials to .travis.yml (#36) 43 | Bump request to 2.81.0 (#34) 44 | Bump Core to 0.5.0 (#33) 45 | Add agentOptions argument to emitter (#30) 46 | Add latest Node.js versions to travis.yml (#32) 47 | Update README markdown in according with CommonMark (#31) 48 | 49 | Version 0.2.0 (2015-10-09) 50 | -------------------------- 51 | Removed callback argument from tracker constructor (#19) 52 | Added Coveralls code coverage button to README (#8) 53 | Fixed links to wiki (#7) 54 | Added Vagrant quickstart (#15) 55 | Bumped Core version to 0.4.0 (#9) 56 | Added POST support to tracker.js (#4) 57 | Added integration tests for POST requests (#21) 58 | Rewrote tests to use mocking object callbacks rather than nock.recorder (#20) 59 | Renamed "tests" directory to "test", in line with mocha's expectations (#14) 60 | Added emitter function (#5) 61 | Fixed npm badge link (#3) 62 | Added ability to specify collector port (#18) 63 | 64 | Version 0.1.1 (2014-12-18) 65 | -------------------------- 66 | Republished to npm as snowplow-tracker 0.1.1 (#10) 67 | 68 | Version 0.1.0 (2014-08-08) 69 | -------------------------- 70 | Initial release 71 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | The Snowplow Nodejs Tracker is maintained by the Engineering team at Snowplow Analytics. We welcome suggestions for improvements and bug fixes to all Snowplow Trackers. 4 | 5 | We are extremely grateful for all contributions we receive, whether that is reporting an issue or a change to the code which can be made in the form of a pull request. 6 | 7 | For support requests, please use our community support Discourse forum: https://discourse.snowplowanalytics.com/. 8 | 9 | ## Setting up an Environment 10 | 11 | Instructions on how to build and run tests are available in the [README.md](README.md). The README will also list any requirements that you will need to install first before being able to build and run the tests. 12 | 13 | You should ensure you are comfortable building and testing the existing release before adding new functionality or fixing issues. 14 | 15 | ## Issues 16 | 17 | ### Creating an issue 18 | 19 | The project contains an issue template which should help guiding you through the process. However, please keep in mind that support requests should go to our Discourse forum: https://discourse.snowplowanalytics.com/ and not GitHub issues. 20 | 21 | It's also a good idea to log an issue before starting to work on a pull request to discuss it with the maintainers. A pull request is just one solution to a problem and it is often a good idea to talk about the problem with the maintainers first. 22 | 23 | ### Working on an issue 24 | 25 | If you see an issue you would like to work on, please let us know in the issue! That will help us in terms of scheduling and 26 | not doubling the amount of work. 27 | 28 | If you don't know where to start contributing, you can look at 29 | [the issues labeled `good first issue`](https://github.com/snowplow/snowplow-nodejs-tracker/labels/good%20first%20issue). 30 | 31 | ## Pull requests 32 | 33 | These are a few guidelines to keep in mind when opening pull requests. 34 | 35 | ### Guidelines 36 | 37 | Please supply a good PR description. These are very helpful and help the maintainers to understand _why_ the change has been made, not just _what_ changes have been made. 38 | 39 | Please try and keep your PR to a single feature of fix. This might mean breaking up a feature into multiple PRs but this makes it easier for the maintainers to review and also reduces the risk in each change. 40 | 41 | Please review your own PR as you would do it you were a reviewer first. This is a great way to spot any mistakes you made when writing the change. Additionally, ensure your code compiles and all tests pass. 42 | 43 | ### Commit hygiene 44 | 45 | We keep a strict 1-to-1 correspondance between commits and issues, as such our commit messages are formatted in the following 46 | fashion: 47 | 48 | `Issue Description (closes #1234)` 49 | 50 | for example: 51 | 52 | `Fix Issue with Tracker (closes #1234)` 53 | 54 | ### Writing tests 55 | 56 | Whenever necessary, it's good practice to add the corresponding tests to whichever feature you are working on. 57 | Any non-trivial PR must have tests and will not be accepted without them. 58 | 59 | ### Feedback cycle 60 | 61 | Reviews should happen fairly quickly during weekdays. 62 | If you feel your pull request has been forgotten, please ping one or more maintainers in the pull request. 63 | 64 | ### Getting your pull request merged 65 | 66 | If your pull request is fairly chunky, there might be a non-trivial delay between the moment the pull request is approved and the moment it gets merged. This is because your pull request will have been scheduled for a specific milestone which might or might not be actively worked on by a maintainer at the moment. 67 | 68 | ### Contributor license agreement 69 | 70 | We require outside contributors to sign a Contributor license agreement (or CLA) before we can merge their pull requests. 71 | You can find more information on the topic in [the dedicated wiki page](https://github.com/snowplow/snowplow/wiki/CLA). 72 | The @snowplowcla bot will guide you through the process. 73 | 74 | ## Getting in touch 75 | 76 | ### Community support requests 77 | 78 | Please do not log an issue if you are asking for support, all of our community support requests go through our Discourse forum: https://discourse.snowplowanalytics.com/. 79 | 80 | Posting your problem there ensures more people will see it and you should get support faster than creating a new issue on GitHub. Please do create a new issue on GitHub if you think you've found a bug though! -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node.js Analytics for Snowplow 2 | 3 | [![early-release]][tracker-classificiation] 4 | [![Build Status][gh-actions-image]][gh-actions] 5 | [![npm version][npm-image]][npm-url] 6 | [![Coveralls][coveralls-image]][coveralls] 7 | 8 | ## Deprecation notice 9 | 10 | This version of the Node.js Tracker is now deprecated. 11 | 12 | The latest version can be found in the [snowplow-javascript-tracker](https://github.com/snowplow/snowplow-javascript-tracker) repo. 13 | 14 | You should switch your applications to use `@snowplow/node-tracker` for the latest updates. 15 | 16 | ## Overview 17 | 18 | Add analytics to your JavaScript and TypeScript Node.js applications and servers with the Snowplow Node.js Tracker. 19 | 20 | This tracker lets you collect event data from Node.js applications. 21 | 22 | ## Find out more 23 | 24 | | Technical Docs | Setup Guide | Contributing | 25 | |--------------------------------------|-------------------------------|---------------------------------| 26 | | [![i1][techdocs-image]][tech-docs] | [![i2][setup-image]][setup] | ![i3][contributing-image] | 27 | | [Technical Docs][tech-docs] | [Setup Guide][setup] | [Contributing](Contributing.md) | 28 | 29 | ## Developers 30 | 31 | ### Getting started 32 | 33 | Make sure you have `node` and `npm` installed and in your `$PATH`. 34 | 35 | Install npm dependencies using `npm install`: 36 | 37 | ```bash 38 | git clone git@github.com:snowplow/snowplow-nodejs-tracker.git 39 | cd snowplow-nodejs-tracker 40 | npm install 41 | npm run build 42 | npm test 43 | ``` 44 | 45 | ## Copyright and license 46 | 47 | The Snowplow Node.js Tracker is copyright 2014-2020 Snowplow Analytics Ltd. 48 | 49 | Licensed under the **[Apache License, Version 2.0][license]** (the "License"); 50 | you may not use this software except in compliance with the License. 51 | 52 | Unless required by applicable law or agreed to in writing, software 53 | distributed under the License is distributed on an "AS IS" BASIS, 54 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 55 | See the License for the specific language governing permissions and 56 | limitations under the License. 57 | 58 | [snowplow]: http://snowplowanalytics.com 59 | 60 | [license]: http://www.apache.org/licenses/LICENSE-2.0 61 | 62 | [gh-actions]: https://github.com/snowplow/snowplow-nodejs-tracker/actions 63 | [gh-actions-image]: https://github.com/snowplow/snowplow-nodejs-tracker/workflows/Build/badge.svg 64 | [npm-url]: https://badge.fury.io/js/snowplow-tracker 65 | [npm-image]: https://badge.fury.io/js/snowplow-tracker.svg 66 | [coveralls-image]: https://coveralls.io/repos/github/snowplow/snowplow-nodejs-tracker/badge.svg?branch=master 67 | [coveralls]: https://coveralls.io/github/snowplow/snowplow-nodejs-tracker?branch=master 68 | 69 | [tech-docs]: https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/node-js-tracker/configuration/ 70 | [techdocs-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/techdocs.png 71 | [setup]: https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/node-js-tracker/setup/ 72 | [setup-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/setup.png 73 | [contributing-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/contributing.png 74 | 75 | [tracker-classificiation]: https://github.com/snowplow/snowplow/wiki/Tracker-Maintenance-Classification 76 | [early-release]: https://img.shields.io/static/v1?style=flat&label=Snowplow&message=Early%20Release&color=014477&labelColor=9ba0aa&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAeFBMVEVMaXGXANeYANeXANZbAJmXANeUANSQAM+XANeMAMpaAJhZAJeZANiXANaXANaOAM2WANVnAKWXANZ9ALtmAKVaAJmXANZaAJlXAJZdAJxaAJlZAJdbAJlbAJmQAM+UANKZANhhAJ+EAL+BAL9oAKZnAKVjAKF1ALNBd8J1AAAAKHRSTlMAa1hWXyteBTQJIEwRgUh2JjJon21wcBgNfmc+JlOBQjwezWF2l5dXzkW3/wAAAHpJREFUeNokhQOCA1EAxTL85hi7dXv/E5YPCYBq5DeN4pcqV1XbtW/xTVMIMAZE0cBHEaZhBmIQwCFofeprPUHqjmD/+7peztd62dWQRkvrQayXkn01f/gWp2CrxfjY7rcZ5V7DEMDQgmEozFpZqLUYDsNwOqbnMLwPAJEwCopZxKttAAAAAElFTkSuQmCC 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snowplow-tracker", 3 | "version": "0.4.4", 4 | "main": "dist/cjs/index.js", 5 | "module": "dist/esm/index.js", 6 | "types": "dist/esm/index.d.ts", 7 | "scripts": { 8 | "build": "rollup -c", 9 | "dev": "rollup -c -w", 10 | "test": "ava", 11 | "test+coverage": "nyc --reporter=lcov npm test" 12 | }, 13 | "files": [ 14 | "dist" 15 | ], 16 | "contributors": [ 17 | "Fred Blundun", 18 | "Anton Parkhomenko", 19 | "Paul Boocock" 20 | ], 21 | "description": "Node.js tracker for Snowplow", 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/snowplow/snowplow-nodejs-tracker.git" 25 | }, 26 | "bugs": "https://github.com/snowplow/snowplow-nodejs-tracker/issues", 27 | "keywords": [ 28 | "snowplow", 29 | "analytics", 30 | "tracking", 31 | "events", 32 | "open source" 33 | ], 34 | "license": "Apache-2.0", 35 | "dependencies": { 36 | "got": "^11.7.0", 37 | "snowplow-tracker-core": "^0.9.4", 38 | "tslib": "^2.0.1" 39 | }, 40 | "devDependencies": { 41 | "@types/node": "^14.6.0", 42 | "@types/sinon": "^9.0.5", 43 | "@typescript-eslint/eslint-plugin": "^3.10.1", 44 | "@typescript-eslint/parser": "^3.10.1", 45 | "@wessberg/rollup-plugin-ts": "^1.3.3", 46 | "ava": "^3.12.1", 47 | "coveralls": "^3.1.0", 48 | "eslint": "^7.7.0", 49 | "eslint-plugin-ava": "^11.0.0", 50 | "nock": "^13.0.4", 51 | "nyc": "^15.1.0", 52 | "rollup": "^2.26.4", 53 | "sinon": "^9.0.3", 54 | "ts-node": "^9.0.0", 55 | "typescript": "^3.9.7" 56 | }, 57 | "ava": { 58 | "extensions": [ 59 | "ts" 60 | ], 61 | "require": [ 62 | "ts-node/register" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import ts from "@wessberg/rollup-plugin-ts"; // Prefered over @rollup/plugin-typescript as it bundles .d.ts files 2 | import pkg from './package.json'; 3 | 4 | import { builtinModules } from "module"; 5 | 6 | export default [ 7 | // CommonJS (for Node) and ES module (for bundlers) build. 8 | { 9 | input: 'src/index.ts', 10 | external: [...builtinModules, ...Object.keys(pkg.dependencies), ...Object.keys(pkg.devDependencies)], 11 | plugins: [ 12 | ts() // so Rollup can convert TypeScript to JavaScript 13 | ], 14 | output: [ 15 | { file: pkg.main, format: 'cjs', sourcemap: true }, 16 | { file: pkg.module, format: 'es', sourcemap: true } 17 | ] 18 | } 19 | ]; -------------------------------------------------------------------------------- /src/emitter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Node.js tracker for Snowplow: emitter.ts 3 | * 4 | * Copyright (c) 2014-2020 Snowplow Analytics Ltd. All rights reserved. 5 | * 6 | * This program is licensed to you under the Apache License Version 2.0, 7 | * and you may not use this file except in compliance with the Apache License Version 2.0. 8 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the Apache License Version 2.0 is distributed on an 12 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 14 | */ 15 | 16 | import { PayloadDictionary } from 'snowplow-tracker-core'; 17 | 18 | export interface Emitter { 19 | flush: () => void; 20 | input: (payload: PayloadDictionary) => void; 21 | } 22 | 23 | export enum HttpProtocol { 24 | HTTP = 'http', 25 | HTTPS = 'https', 26 | } 27 | 28 | export enum HttpMethod { 29 | GET = 'get', 30 | POST = 'post', 31 | } 32 | 33 | /** 34 | * Convert all fields in a payload dictionary to strings 35 | * 36 | * @param payload Payload on which the new dictionary is based 37 | */ 38 | export const preparePayload = (payload: PayloadDictionary): Record => { 39 | const stringifiedPayload: Record = {}; 40 | 41 | const finalPayload = addDeviceSentTimestamp(payload); 42 | 43 | for (const key in finalPayload) { 44 | if (Object.prototype.hasOwnProperty.call(finalPayload, key)) { 45 | stringifiedPayload[key] = String(finalPayload[key]); 46 | } 47 | } 48 | return stringifiedPayload; 49 | }; 50 | 51 | /** 52 | * Adds the 'stm' paramater with the current time to the payload 53 | * @param payload The payload which will be mutated 54 | */ 55 | const addDeviceSentTimestamp = (payload: PayloadDictionary): PayloadDictionary => { 56 | payload['stm'] = new Date().getTime().toString(); 57 | return payload; 58 | }; 59 | -------------------------------------------------------------------------------- /src/got_emitter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Node.js tracker for Snowplow: got_emitter.ts 3 | * 4 | * Copyright (c) 2014-2020 Snowplow Analytics Ltd. All rights reserved. 5 | * 6 | * This program is licensed to you under the Apache License Version 2.0, 7 | * and you may not use this file except in compliance with the Apache License Version 2.0. 8 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the Apache License Version 2.0 is distributed on an 12 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 14 | */ 15 | 16 | import got, { Response, RequestError, Agents, RequiredRetryOptions, ToughCookieJar, PromiseCookieJar } from 'got'; 17 | import { PayloadDictionary } from 'snowplow-tracker-core'; 18 | 19 | import { Emitter, HttpProtocol, HttpMethod, preparePayload } from './emitter'; 20 | import { version } from './version'; 21 | 22 | /** 23 | * Create an emitter object, which uses the `got` library, that will send events to a collector 24 | * 25 | * @param endpoint The collector to which events will be sent 26 | * @param protocol http or https 27 | * @param port The port for requests to use 28 | * @param method get or post 29 | * @param bufferSize Number of events which can be queued before flush is called 30 | * @param retry Configure the retry policy for `got` - https://github.com/sindresorhus/got/blob/v11.5.2/readme.md#retry 31 | * @param cookieJar Add a cookieJar to `got` - https://github.com/sindresorhus/got/blob/v11.5.2/readme.md#cookiejar 32 | * @param callback Callback called after a `got` request following retries - called with ErrorRequest (https://github.com/sindresorhus/got/blob/v11.5.2/readme.md#errors) and Response (https://github.com/sindresorhus/got/blob/v11.5.2/readme.md#response) 33 | * @param agents Set new http.Agent and https.Agent objects on `got` requests - https://github.com/sindresorhus/got/blob/v11.5.2/readme.md#agent 34 | */ 35 | export function gotEmitter( 36 | endpoint: string, 37 | protocol: HttpProtocol, 38 | port?: number, 39 | method?: HttpMethod, 40 | bufferSize?: number, 41 | retry?: number | Partial, 42 | cookieJar?: PromiseCookieJar | ToughCookieJar, 43 | callback?: (error?: RequestError, response?: Response) => void, 44 | agents?: Agents 45 | ): Emitter { 46 | const maxBufferLength = bufferSize ?? (method === HttpMethod.GET ? 0 : 10); 47 | const path = method === HttpMethod.GET ? '/i' : '/com.snowplowanalytics.snowplow/tp2'; 48 | const targetUrl = protocol + '://' + endpoint + (port ? ':' + port : '') + path; 49 | 50 | let buffer: Array = []; 51 | 52 | /** 53 | * Handles the callback on a successful response if the callback is present 54 | * @param response The got response object 55 | */ 56 | const handleSuccess = (response: Response) => { 57 | if (callback) { 58 | try { 59 | callback(undefined, response); 60 | } catch (e) { 61 | console.error('Error in callback after success', e); 62 | } 63 | } 64 | }; 65 | 66 | /** 67 | * Handles the callback on a failed request if the callback is present 68 | * @param error The got error object 69 | */ 70 | const handleFailure = (error: RequestError) => { 71 | if (callback) { 72 | try { 73 | callback(error); 74 | } catch (e) { 75 | console.error('Error in callback after failure', e); 76 | } 77 | } 78 | }; 79 | 80 | /** 81 | * Flushes all events currently stored in buffer 82 | */ 83 | const flush = (): void => { 84 | const bufferCopy = buffer; 85 | buffer = []; 86 | if (bufferCopy.length === 0) { 87 | return; 88 | } 89 | 90 | if (method === HttpMethod.POST) { 91 | const postJson = { 92 | schema: 'iglu:com.snowplowanalytics.snowplow/payload_data/jsonschema/1-0-4', 93 | data: bufferCopy.map(preparePayload), 94 | }; 95 | got 96 | .post(targetUrl, { 97 | json: postJson, 98 | headers: { 99 | 'content-type': 'application/json; charset=utf-8', 100 | 'user-agent': `snowplow-nodejs-tracker/${version}`, 101 | }, 102 | agent: agents, 103 | retry: retry, 104 | cookieJar: cookieJar, 105 | }) 106 | .then(handleSuccess, handleFailure); 107 | } else { 108 | for (let i = 0; i < bufferCopy.length; i++) { 109 | got 110 | .get(targetUrl, { 111 | searchParams: preparePayload(bufferCopy[i]), 112 | headers: { 113 | 'user-agent': `snowplow-nodejs-tracker/${version}`, 114 | }, 115 | agent: agents, 116 | retry: retry, 117 | cookieJar: cookieJar, 118 | }) 119 | .then(handleSuccess, handleFailure); 120 | } 121 | } 122 | }; 123 | 124 | /** 125 | * Adds a payload to the internal buffer and sends if buffer >= bufferSize 126 | * @param payload Payload to add to buffer 127 | */ 128 | const input = (payload: PayloadDictionary): void => { 129 | buffer.push(payload); 130 | if (buffer.length >= maxBufferLength) { 131 | flush(); 132 | } 133 | }; 134 | 135 | return { 136 | /** 137 | * Send all events queued in the buffer to the collector 138 | */ 139 | flush, 140 | input, 141 | }; 142 | } 143 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Node.js tracker for Snowplow: index.ts 3 | * 4 | * Copyright (c) 2014-2020 Snowplow Analytics Ltd. All rights reserved. 5 | * 6 | * This program is licensed to you under the Apache License Version 2.0, 7 | * and you may not use this file except in compliance with the Apache License Version 2.0. 8 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the Apache License Version 2.0 is distributed on an 12 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 14 | */ 15 | 16 | export { Emitter, HttpMethod, HttpProtocol } from './emitter'; 17 | export { tracker, Tracker, EcommerceTransactionItem } from './tracker'; 18 | export { gotEmitter } from './got_emitter'; 19 | export { version } from './version'; 20 | -------------------------------------------------------------------------------- /src/tracker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Node.js tracker for Snowplow: tracker.ts 3 | * 4 | * Copyright (c) 2014-2020 Snowplow Analytics Ltd. All rights reserved. 5 | * 6 | * This program is licensed to you under the Apache License Version 2.0, 7 | * and you may not use this file except in compliance with the Apache License Version 2.0. 8 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the Apache License Version 2.0 is distributed on an 12 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 14 | */ 15 | 16 | import { trackerCore, PayloadData, SelfDescribingJson, Timestamp, Core } from 'snowplow-tracker-core'; 17 | 18 | import { version } from './version'; 19 | import { Emitter } from './emitter'; 20 | 21 | export interface EcommerceTransactionItem { 22 | sku: string; 23 | name: string; 24 | category: string; 25 | price: string; 26 | quantity: string; 27 | context?: Array; 28 | } 29 | 30 | export interface Tracker extends Core { 31 | /** 32 | * Track an ecommerce transaction and all items in that transaction 33 | * Each item is represented by an EcommerceTransactionItem interface. 34 | * 35 | * @param orderId Internal unique order id number for this transaction. 36 | * @param affiliation Partner or store affiliation. 37 | * @param total Total amount of the transaction. 38 | * @param tax Tax amount of the transaction. 39 | * @param shipping Shipping charge for the transaction. 40 | * @param city City to associate with transaction. 41 | * @param state State to associate with transaction. 42 | * @param country Country to associate with transaction. 43 | * @param currency Currency to associate with this transaction. 44 | * @param items Items which make up the transaction. 45 | * @param context Context relating to the event. 46 | * @param tstamp Timestamp for the event. 47 | */ 48 | trackEcommerceTransactionWithItems: ( 49 | orderId: string, 50 | affiliation: string, 51 | total: string, 52 | tax?: string, 53 | shipping?: string, 54 | city?: string, 55 | state?: string, 56 | country?: string, 57 | currency?: string, 58 | items?: Array, 59 | context?: Array, 60 | tstamp?: Timestamp 61 | ) => PayloadData; 62 | 63 | /** 64 | * Set the domain user ID 65 | * 66 | * @param userId The domain user id 67 | */ 68 | setDomainUserId: (userId: string) => void; 69 | 70 | /** 71 | * Set the network user ID 72 | * 73 | * @param userId The network user id 74 | */ 75 | setNetworkUserId: (userId: string) => void; 76 | } 77 | 78 | /** 79 | * Snowplow Node.js Tracker 80 | * 81 | * @param string or array emitters The emitter or emitters to which events will be sent 82 | * @param string namespace The namespace of the tracker 83 | * @param string appId The application ID 84 | * @param boolean encodeBase64 Whether unstructured events and custom contexts should be base 64 encoded 85 | */ 86 | export function tracker( 87 | emitters: Emitter | Array, 88 | namespace: string, 89 | appId: string, 90 | encodeBase64: boolean 91 | ): Tracker { 92 | let domainUserId: string; 93 | let networkUserId: string; 94 | let allEmitters: Array; 95 | 96 | if (emitters instanceof Array) { 97 | allEmitters = emitters; 98 | } else { 99 | allEmitters = [emitters]; 100 | } 101 | 102 | encodeBase64 = encodeBase64 !== false; 103 | 104 | const addUserInformation = (payload: PayloadData): void => { 105 | payload.add('duid', domainUserId); 106 | payload.add('nuid', networkUserId); 107 | }; 108 | 109 | /** 110 | * Send the payload for an event to the endpoint 111 | * 112 | * @param payload Dictionary of name-value pairs for the querystring 113 | */ 114 | const sendPayload = (payload: PayloadData): void => { 115 | addUserInformation(payload); 116 | const builtPayload = payload.build(); 117 | for (let i = 0; i < allEmitters.length; i++) { 118 | allEmitters[i].input(builtPayload); 119 | } 120 | }; 121 | 122 | const core = trackerCore(encodeBase64, sendPayload); 123 | 124 | core.setPlatform('srv'); // default platform 125 | core.setTrackerVersion('node-' + version); 126 | core.setTrackerNamespace(namespace); 127 | core.setAppId(appId); 128 | 129 | const trackEcommerceTransactionWithItems = function ( 130 | orderId: string, 131 | affiliation: string, 132 | total: string, 133 | tax?: string, 134 | shipping?: string, 135 | city?: string, 136 | state?: string, 137 | country?: string, 138 | currency?: string, 139 | items?: Array, 140 | context?: Array, 141 | tstamp?: Timestamp 142 | ): PayloadData { 143 | const payloadData = core.trackEcommerceTransaction( 144 | orderId, 145 | affiliation, 146 | total, 147 | tax, 148 | shipping, 149 | city, 150 | state, 151 | country, 152 | currency, 153 | context, 154 | tstamp 155 | ); 156 | 157 | if (items) { 158 | for (let i = 0; i < items.length; i++) { 159 | const item = items[i]; 160 | core.trackEcommerceTransactionItem( 161 | orderId, 162 | item.sku, 163 | item.name, 164 | item.category, 165 | item.price, 166 | item.quantity, 167 | currency, 168 | item.context, 169 | tstamp 170 | ); 171 | } 172 | } 173 | 174 | return payloadData; 175 | }; 176 | 177 | const setDomainUserId = function (userId: string) { 178 | domainUserId = userId; 179 | }; 180 | 181 | const setNetworkUserId = function (userId: string) { 182 | networkUserId = userId; 183 | }; 184 | 185 | return { 186 | trackEcommerceTransactionWithItems, 187 | setDomainUserId, 188 | setNetworkUserId, 189 | ...core, 190 | }; 191 | } 192 | -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Node.js tracker for Snowplow: version.ts 3 | * 4 | * Copyright (c) 2014-2020 Snowplow Analytics Ltd. All rights reserved. 5 | * 6 | * This program is licensed to you under the Apache License Version 2.0, 7 | * and you may not use this file except in compliance with the Apache License Version 2.0. 8 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 9 | * 10 | * Unless required by applicable law or agreed to in writing, 11 | * software distributed under the Apache License Version 2.0 is distributed on an 12 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 14 | */ 15 | 16 | export const version = '0.4.0'; 17 | -------------------------------------------------------------------------------- /test/emitter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2020 Snowplow Analytics Ltd. All rights reserved. 3 | * 4 | * This program is licensed to you under the Apache License Version 2.0, 5 | * and you may not use this file except in compliance with the Apache License Version 2.0. 6 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 7 | * 8 | * Unless required by applicable law or agreed to in writing, 9 | * software distributed under the Apache License Version 2.0 is distributed on an 10 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 12 | */ 13 | 14 | import test from 'ava'; 15 | import sinon from 'sinon'; 16 | 17 | import { preparePayload } from '../src/emitter'; 18 | import { PayloadDictionary } from 'snowplow-tracker-core'; 19 | 20 | test('preparePayload should convert payload values to strings', (t) => { 21 | const payload: PayloadDictionary = { a: 1234, b: '1'} 22 | 23 | const result = preparePayload(payload); 24 | 25 | t.like(result, { a: '1234', b: '1' }); 26 | }); 27 | 28 | test('preparePayload should add "stm" property', (t) => { 29 | const testTime = new Date('2020-06-15T09:12:30.000Z').getTime(); 30 | const clock = sinon.useFakeTimers(testTime); 31 | 32 | const payload: PayloadDictionary = { a: '1'} 33 | 34 | const result = preparePayload(payload); 35 | 36 | t.deepEqual(result, { a: '1', stm: testTime.toString()}); 37 | 38 | clock.restore(); 39 | }); -------------------------------------------------------------------------------- /test/got_emitter.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2020 Snowplow Analytics Ltd. All rights reserved. 3 | * 4 | * This program is licensed to you under the Apache License Version 2.0, 5 | * and you may not use this file except in compliance with the Apache License Version 2.0. 6 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 7 | * 8 | * Unless required by applicable law or agreed to in writing, 9 | * software distributed under the Apache License Version 2.0 is distributed on an 10 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 12 | */ 13 | 14 | import test from 'ava'; 15 | import sinon from 'sinon'; 16 | import nock from 'nock'; 17 | import { HttpMethod, HttpProtocol, gotEmitter } from '../src/index'; 18 | 19 | const endpoint = 'd3rkrsqld9gmqf.cloudfront.net'; 20 | 21 | nock(new RegExp('https*://' + endpoint)) 22 | .persist() 23 | .filteringPath(() => '/') 24 | .get('/') 25 | .reply(200, (uri) => uri); 26 | 27 | nock(new RegExp('https*://' + endpoint)) 28 | .matchHeader('content-type', 'application/json; charset=utf-8') 29 | .persist() 30 | .filteringRequestBody(() => '*') 31 | .post('/com.snowplowanalytics.snowplow/tp2', '*') 32 | .reply(200, (_uri, body: Record) => (body['data'] as Array)[0]); 33 | 34 | test.before(() => { 35 | nock.disableNetConnect(); 36 | }); 37 | 38 | test.after(() => { 39 | nock.cleanAll(); 40 | }); 41 | 42 | test.cb('gotEmitter should send an HTTP GET request', (t) => { 43 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, 80, HttpMethod.GET, undefined, undefined, undefined, function ( 44 | error, 45 | response 46 | ) { 47 | t.regex(response?.body as string, /\/i\?.*a=b.*/); 48 | t.end(error); 49 | }); 50 | e.input({ a: 'b' }); 51 | }); 52 | 53 | test.cb('gotEmitter should send an HTTP POST request', (t) => { 54 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, HttpMethod.POST, 1, undefined, undefined, function ( 55 | error, 56 | response 57 | ) { 58 | t.like(JSON.parse(response?.body as string), { a: 'b' }); 59 | t.end(error); 60 | }); 61 | e.input({ a: 'b' }); 62 | }); 63 | 64 | test.cb('gotEmitter should send an HTTPS GET request', (t) => { 65 | const e = gotEmitter(endpoint, HttpProtocol.HTTPS, 443, HttpMethod.GET, undefined, undefined, undefined, function ( 66 | error, 67 | response 68 | ) { 69 | t.regex(response?.body as string, /\/i\?.*a=b.*/); 70 | t.end(error); 71 | }); 72 | e.input({ a: 'b' }); 73 | }); 74 | 75 | test.cb('gotEmitter should send an HTTPS POST request', (t) => { 76 | const e = gotEmitter(endpoint, HttpProtocol.HTTPS, undefined, HttpMethod.POST, 1, undefined, undefined, function ( 77 | error, 78 | response 79 | ) { 80 | t.like(JSON.parse(response?.body as string), { a: 'b' }); 81 | t.end(error); 82 | }); 83 | e.input({ a: 'b' }); 84 | }); 85 | 86 | test.cb('gotEmitter should not send requests if the buffer is not full', (t) => { 87 | const e = gotEmitter(endpoint, HttpProtocol.HTTPS, undefined, HttpMethod.POST, undefined, undefined, undefined, () => 88 | t.fail('Event unexpectedly emitted') 89 | ); 90 | e.input({}); 91 | e.input({}); 92 | e.input({}); 93 | setTimeout(t.end, 250); //Give chance for emitter callback to fire 94 | }); 95 | 96 | test.cb('gotEmitter should not send requests if the buffer is empty', (t) => { 97 | const e = gotEmitter(endpoint, HttpProtocol.HTTPS, undefined, HttpMethod.POST, undefined, undefined, undefined, () => 98 | t.fail('Event unexpectedly emitted') 99 | ); 100 | e.flush(); 101 | setTimeout(t.end, 250); //Give chance for emitter callback to fire 102 | }); 103 | 104 | test.cb('gotEmitter should add STM querystring parameter when sending POST requests', (t) => { 105 | const testTime = new Date('1988-12-12T12:30:00.000Z').getTime(); 106 | const clock = sinon.useFakeTimers(testTime); 107 | 108 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, HttpMethod.POST, 1, undefined, undefined, function ( 109 | error, 110 | response 111 | ) { 112 | t.like(JSON.parse(response?.body as string), { stm: testTime.toString() }); 113 | t.end(error); 114 | }); 115 | e.input({ a: 'b' }); 116 | 117 | clock.restore(); 118 | }); 119 | 120 | test.cb('gotEmitter should add STM querystring parameter when sending GET requests', (t) => { 121 | const testTime = new Date('2020-06-15T09:12:30.000Z').getTime(); 122 | const clock = sinon.useFakeTimers(testTime); 123 | 124 | const e = gotEmitter(endpoint, HttpProtocol.HTTPS, 443, HttpMethod.GET, undefined, undefined, undefined, function ( 125 | error, 126 | response 127 | ) { 128 | t.regex(response?.body as string, new RegExp(`/i?.*stm=${testTime}.*`)); 129 | t.end(error); 130 | }); 131 | e.input({ a: 'b' }); 132 | 133 | clock.restore(); 134 | }); 135 | 136 | test.cb('gotEmitter should handle undefined callbacks on success situation', (t) => { 137 | t.notThrows(() => { 138 | const e = gotEmitter(endpoint, HttpProtocol.HTTPS, 443, HttpMethod.GET, undefined, undefined, undefined, undefined); 139 | e.input({ a: 'b' }); 140 | }); 141 | t.end(); 142 | }); 143 | 144 | test.cb('gotEmitter should handle undefined callbacks on failure situation', (t) => { 145 | t.notThrows(() => { 146 | const e = gotEmitter('invalid-url', HttpProtocol.HTTPS, 443, HttpMethod.POST, 1, undefined, undefined, undefined); 147 | e.input({ a: 'b' }); 148 | }); 149 | t.end(); 150 | }); 151 | 152 | test.cb('gotEmitter should catch error in success situation', (t) => { 153 | t.notThrows(() => { 154 | const e = gotEmitter( 155 | endpoint, 156 | HttpProtocol.HTTPS, 157 | 443, 158 | HttpMethod.GET, 159 | undefined, 160 | undefined, 161 | undefined, 162 | function () { 163 | throw new Error('test error'); 164 | } 165 | ); 166 | e.input({ a: 'b' }); 167 | }); 168 | t.end(); 169 | }); 170 | 171 | test.cb('gotEmitter should catch error in error situation', (t) => { 172 | t.notThrows(() => { 173 | const e = gotEmitter('invalid-url', HttpProtocol.HTTPS, 443, HttpMethod.POST, 1, undefined, undefined, function () { 174 | throw new Error('test error'); 175 | }); 176 | e.input({ a: 'b' }); 177 | }); 178 | t.end(); 179 | }); 180 | 181 | test.cb('gotEmitter should pass response in success situation', (t) => { 182 | const e = gotEmitter(endpoint, HttpProtocol.HTTPS, 443, HttpMethod.GET, undefined, undefined, undefined, function ( 183 | error, 184 | response 185 | ) { 186 | t.falsy(error); 187 | t.truthy(response); 188 | t.end(); 189 | }); 190 | e.input({ a: 'b' }); 191 | }); 192 | 193 | test.cb('gotEmitter should pass error in error situation', (t) => { 194 | const e = gotEmitter('invalid-url', HttpProtocol.HTTPS, 443, HttpMethod.POST, 1, undefined, undefined, function ( 195 | error, 196 | response 197 | ) { 198 | t.truthy(error); 199 | t.falsy(response); 200 | t.end(); 201 | }); 202 | e.input({ a: 'b' }); 203 | }); 204 | -------------------------------------------------------------------------------- /test/tracker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2020 Snowplow Analytics Ltd. All rights reserved. 3 | * 4 | * This program is licensed to you under the Apache License Version 2.0, 5 | * and you may not use this file except in compliance with the Apache License Version 2.0. 6 | * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. 7 | * 8 | * Unless required by applicable law or agreed to in writing, 9 | * software distributed under the Apache License Version 2.0 is distributed on an 10 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. 12 | */ 13 | 14 | import test, { ExecutionContext } from 'ava'; 15 | import nock from 'nock'; 16 | import querystring from 'querystring'; 17 | import { PayloadDictionary } from 'snowplow-tracker-core'; 18 | 19 | import { tracker, gotEmitter, version, HttpMethod, HttpProtocol } from '../src/index'; 20 | 21 | const testMethods = [HttpMethod.GET, HttpMethod.POST]; 22 | 23 | const endpoint = 'd3rkrsqld9gmqf.cloudfront.net'; 24 | 25 | const context = [ 26 | { 27 | schema: 'iglu:com.acme/user/jsonschema/1-0-0', 28 | data: { 29 | type: 'tester', 30 | }, 31 | }, 32 | ]; 33 | 34 | const completedContext = JSON.stringify({ 35 | schema: 'iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0', 36 | data: context, 37 | }); 38 | 39 | nock(new RegExp('https*://' + endpoint)) 40 | .persist() 41 | .filteringPath(() => '/') 42 | .get('/') 43 | .reply(200, (uri) => querystring.parse(uri.slice(3))); 44 | 45 | nock(new RegExp('https*://' + endpoint)) 46 | .matchHeader('content-type', 'application/json; charset=utf-8') 47 | .persist() 48 | .filteringRequestBody(() => '*') 49 | .post('/com.snowplowanalytics.snowplow/tp2', '*') 50 | .reply(200, (_uri, body) => body); 51 | 52 | function extractPayload(response?: string, method?: string): PayloadDictionary { 53 | if (!response) return {}; 54 | 55 | const parsed = JSON.parse(response); 56 | if (method === 'get') { 57 | return parsed; 58 | } else { 59 | return (parsed['data'] as Array)[0] as PayloadDictionary; 60 | } 61 | } 62 | 63 | function checkPayload(payloadDict: PayloadDictionary, expected: PayloadDictionary, t: ExecutionContext): void { 64 | t.like(payloadDict, expected); 65 | t.deepEqual(payloadDict['co'], completedContext, 'a custom context should be attached'); 66 | t.truthy(payloadDict['dtm'], 'a timestamp should be attached'); 67 | t.truthy(payloadDict['eid'], 'a UUID should be attached'); 68 | } 69 | 70 | test.before(() => { 71 | nock.disableNetConnect(); 72 | }); 73 | 74 | test.after(() => { 75 | nock.cleanAll(); 76 | }); 77 | 78 | for (const method of testMethods) { 79 | test.cb(method + ' method: trackPageView should send a page view event', (t) => { 80 | const expected = { 81 | tv: 'node-' + version, 82 | tna: 'cf', 83 | aid: 'cfe35', 84 | p: 'srv', 85 | e: 'pv', 86 | url: 'http://www.example.com', 87 | page: 'example page', 88 | refr: 'google', 89 | }; 90 | 91 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, method, 0, undefined, undefined, function ( 92 | error, 93 | response 94 | ) { 95 | checkPayload(extractPayload(response?.body, method), expected, t); 96 | t.end(error); 97 | }); 98 | 99 | const track = tracker(e, 'cf', 'cfe35', false); 100 | track.trackPageView('http://www.example.com', 'example page', 'google', context); 101 | }); 102 | 103 | test.cb(method + ' method: trackStructEvent should send a structured event', (t) => { 104 | const expected = { 105 | tv: 'node-' + version, 106 | tna: 'cf', 107 | aid: 'cfe35', 108 | e: 'se', 109 | se_ca: 'clothes', 110 | se_ac: 'add_to_basket', 111 | se_la: 'jumper', 112 | se_pr: 'red', 113 | se_va: '15', 114 | }; 115 | 116 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, method, 0, undefined, undefined, function ( 117 | error, 118 | response 119 | ) { 120 | checkPayload(extractPayload(response?.body, method), expected, t); 121 | t.end(error); 122 | }); 123 | 124 | const track = tracker(e, 'cf', 'cfe35', false); 125 | track.trackStructEvent('clothes', 'add_to_basket', 'jumper', 'red', 15, context); 126 | }); 127 | 128 | test.cb(method + ' method: trackEcommerceTransactionWithItems should track an ecommerce transaction', (t) => { 129 | const expectedTransaction = { 130 | e: 'tr', 131 | tr_id: 'order-7', 132 | tr_af: 'affiliate', 133 | tr_tt: '15', 134 | tr_tx: '5', 135 | tr_sh: '0', 136 | tr_ci: 'Dover', 137 | tr_st: 'Delaware', 138 | tr_co: 'US', 139 | tr_cu: 'GBP', 140 | }; 141 | 142 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, method, 0, undefined, undefined, function ( 143 | error, 144 | response 145 | ) { 146 | const payloadDict = extractPayload(response?.body, method); 147 | checkPayload(payloadDict, expectedTransaction, t); 148 | t.end(error); 149 | }); 150 | 151 | const track = tracker(e, 'cf', 'cfe35', false); 152 | track.trackEcommerceTransaction('order-7', 'affiliate', '15', '5', '0', 'Dover', 'Delaware', 'US', 'GBP', context); 153 | }); 154 | 155 | test.cb( 156 | method + ' method: trackEcommerceTransactionWithItems should track an ecommerce transaction and items', 157 | (t) => { 158 | const items = [ 159 | { 160 | sku: 'item-729', 161 | name: 'red hat', 162 | category: 'headgear', 163 | price: '10', 164 | quantity: '1', 165 | context: context, 166 | }, 167 | ]; 168 | const expectedTransaction = { 169 | e: 'tr', 170 | tr_id: 'order-7', 171 | tr_af: 'affiliate', 172 | tr_tt: '15', 173 | tr_tx: '5', 174 | tr_sh: '0', 175 | tr_ci: 'Dover', 176 | tr_st: 'Delaware', 177 | tr_co: 'US', 178 | tr_cu: 'GBP', 179 | }; 180 | const expectedItem = { 181 | e: 'ti', 182 | ti_sk: 'item-729', 183 | ti_nm: 'red hat', 184 | ti_ca: 'headgear', 185 | ti_qu: '1', 186 | ti_id: 'order-7', 187 | ti_cu: 'GBP', 188 | }; 189 | 190 | let requestCount = items.length + 1; 191 | 192 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, method, 0, undefined, undefined, function ( 193 | error, 194 | response 195 | ) { 196 | const payloadDict = extractPayload(response?.body, method); 197 | const expected = payloadDict['e'] === 'tr' ? expectedTransaction : expectedItem; 198 | 199 | checkPayload(payloadDict, expected, t); 200 | 201 | requestCount--; 202 | if (!requestCount) { 203 | t.end(error); 204 | } 205 | }); 206 | 207 | const track = tracker(e, 'cf', 'cfe35', false); 208 | track.trackEcommerceTransactionWithItems( 209 | 'order-7', 210 | 'affiliate', 211 | '15', 212 | '5', 213 | '0', 214 | 'Dover', 215 | 'Delaware', 216 | 'US', 217 | 'GBP', 218 | items, 219 | context 220 | ); 221 | } 222 | ); 223 | 224 | test.cb( 225 | method + 226 | ' method: trackEcommerceTransactionWithItems with no items should track an ecommerce transaction and no items events', 227 | (t) => { 228 | t.plan(1); 229 | 230 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, method, 0, undefined, undefined, function (error) { 231 | t.pass(); 232 | t.end(error); 233 | }); 234 | 235 | const track = tracker(e, 'cf', 'cfe35', false); 236 | track.trackEcommerceTransactionWithItems( 237 | 'order-7', 238 | 'affiliate', 239 | '15', 240 | '5', 241 | '0', 242 | 'Dover', 243 | 'Delaware', 244 | 'US', 245 | 'GBP', 246 | undefined, 247 | context 248 | ); 249 | } 250 | ); 251 | 252 | test.cb(method + ' method: trackUnstructEvent should send a structured event', (t) => { 253 | const inputJson = { 254 | schema: 'iglu:com.acme/viewed_product/jsonschema/1-0-0', 255 | data: { 256 | price: 20, 257 | }, 258 | }; 259 | const expected = { 260 | tv: 'node-' + version, 261 | tna: 'cf', 262 | aid: 'cfe35', 263 | e: 'ue', 264 | ue_pr: JSON.stringify({ 265 | schema: 'iglu:com.snowplowanalytics.snowplow/unstruct_event/jsonschema/1-0-0', 266 | data: inputJson, 267 | }), 268 | }; 269 | 270 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, method, 0, undefined, undefined, function ( 271 | error, 272 | response 273 | ) { 274 | checkPayload(extractPayload(response?.body, method), expected, t); 275 | t.end(error); 276 | }); 277 | 278 | const track = tracker(e, 'cf', 'cfe35', false); 279 | track.trackUnstructEvent(inputJson, context); 280 | }); 281 | 282 | test.cb(method + ' method: trackScreenView should send a screen view event', (t) => { 283 | const expected = { 284 | tv: 'node-' + version, 285 | tna: 'cf', 286 | aid: 'cfe35', 287 | e: 'ue', 288 | ue_pr: JSON.stringify({ 289 | schema: 'iglu:com.snowplowanalytics.snowplow/unstruct_event/jsonschema/1-0-0', 290 | data: { 291 | schema: 'iglu:com.snowplowanalytics.snowplow/screen_view/jsonschema/1-0-0', 292 | data: { 293 | name: 'title screen', 294 | id: '12345', 295 | }, 296 | }, 297 | }), 298 | }; 299 | 300 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, method, 0, undefined, undefined, function ( 301 | error, 302 | response 303 | ) { 304 | checkPayload(extractPayload(response?.body, method), expected, t); 305 | t.end(error); 306 | }); 307 | 308 | const track = tracker(e, 'cf', 'cfe35', false); 309 | track.trackScreenView('title screen', '12345', context); 310 | }); 311 | 312 | test.cb(method + ' method: setter methods should set user attributes', (t) => { 313 | const expected = { 314 | tv: 'node-' + version, 315 | tna: 'cf', 316 | aid: 'cfe35', 317 | e: 'pv', 318 | url: 'http://www.example.com', 319 | page: 'example page', 320 | refr: 'google', 321 | p: 'web', 322 | uid: 'jacob', 323 | res: '400x200', 324 | vp: '500x800', 325 | cd: '24', 326 | tz: 'Europe London', 327 | dtm: '1000000000000', 328 | }; 329 | 330 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, method, 0, undefined, undefined, function ( 331 | error, 332 | response 333 | ) { 334 | checkPayload(extractPayload(response?.body, method), expected, t); 335 | t.end(error); 336 | }); 337 | 338 | const track = tracker(e, 'cf', 'cfe35', false); 339 | 340 | track.setPlatform('web'); 341 | track.setUserId('jacob'); 342 | track.setScreenResolution('400', '200'); 343 | track.setViewport('500', '800'); 344 | track.setColorDepth('24'); 345 | track.setTimezone('Europe London'); 346 | 347 | track.trackPageView('http://www.example.com', 'example page', 'google', context, 1000000000000); 348 | }); 349 | 350 | test.cb(method + ' method: base 64 encoding should base 64 encode unstructured events and custom contexts', (t) => { 351 | const inputJson = { 352 | schema: 'iglu:com.acme/viewed_product/jsonschema/1-0-0', 353 | data: { 354 | price: 20, 355 | }, 356 | }; 357 | 358 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, method, 0, undefined, undefined, function ( 359 | error, 360 | response 361 | ) { 362 | const pd = extractPayload(response?.body, method); 363 | t.is( 364 | pd['ue_px'], 365 | 'eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvdy91bnN0cnVjdF9ldmVudC9qc29uc2NoZW1hLzEtMC0wIiwiZGF0YSI6eyJzY2hlbWEiOiJpZ2x1OmNvbS5hY21lL3ZpZXdlZF9wcm9kdWN0L2pzb25zY2hlbWEvMS0wLTAiLCJkYXRhIjp7InByaWNlIjoyMH19fQ' 366 | ); 367 | t.is( 368 | pd['cx'], 369 | 'eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvdy9jb250ZXh0cy9qc29uc2NoZW1hLzEtMC0wIiwiZGF0YSI6W3sic2NoZW1hIjoiaWdsdTpjb20uYWNtZS91c2VyL2pzb25zY2hlbWEvMS0wLTAiLCJkYXRhIjp7InR5cGUiOiJ0ZXN0ZXIifX1dfQ' 370 | ); 371 | t.end(error); 372 | }); 373 | 374 | const track = tracker(e, 'cf', 'cfe35', true); 375 | track.trackUnstructEvent(inputJson, context); 376 | }); 377 | 378 | test.cb(method + ' method: multiple emitters should send an event to multiple collectors', (t) => { 379 | const expected = { 380 | tv: 'node-' + version, 381 | tna: 'cf', 382 | aid: 'cfe35', 383 | p: 'srv', 384 | e: 'pv', 385 | url: 'http://www.example.com', 386 | page: 'example page', 387 | refr: 'google', 388 | }; 389 | let count = 2; 390 | 391 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, method, 0, undefined, undefined, function ( 392 | error, 393 | response 394 | ) { 395 | checkPayload(extractPayload(response?.body, method), expected, t); 396 | count--; 397 | if (count === 0) { 398 | t.end(error); 399 | } 400 | }); 401 | 402 | const track = tracker([e, e], 'cf', 'cfe35', false); 403 | track.trackPageView('http://www.example.com', 'example page', 'google', context); 404 | }); 405 | 406 | test.cb(method + ' method: setDomainUserId should attach a duid property to event', (t) => { 407 | const expected = { 408 | duid: 'duid-test-1234', 409 | }; 410 | 411 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, method, 0, undefined, undefined, function ( 412 | error, 413 | response 414 | ) { 415 | checkPayload(extractPayload(response?.body, method), expected, t); 416 | t.end(error); 417 | }); 418 | 419 | const track = tracker(e, 'cf', 'cfe35', false); 420 | track.setDomainUserId('duid-test-1234'); 421 | track.trackPageView('http://www.example.com', 'example page', 'google', context); 422 | }); 423 | 424 | test.cb(method + ' method: setNetworkUserID should attach a nuid property to event', (t) => { 425 | const expected = { 426 | nuid: 'nuid-test-1234', 427 | }; 428 | 429 | const e = gotEmitter(endpoint, HttpProtocol.HTTP, undefined, method, 0, undefined, undefined, function ( 430 | error, 431 | response 432 | ) { 433 | checkPayload(extractPayload(response?.body, method), expected, t); 434 | t.end(error); 435 | }); 436 | 437 | const track = tracker(e, 'cf', 'cfe35', false); 438 | track.setNetworkUserId('nuid-test-1234'); 439 | track.trackPageView('http://www.example.com', 'example page', 'google', context); 440 | }); 441 | } 442 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "lib": ["es2018"], 5 | "target": "es2018", /* Good for Node 10 - https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping */ 6 | "module": "commonjs", /* Ignored by rollup but used by ts-node for tests */ 7 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 8 | 9 | /* Strict Type-Checking Options */ 10 | "strict": true, /* Enable all strict type-checking options. */ 11 | 12 | /* Additional Checks */ 13 | "noUnusedLocals": true, /* Report errors on unused locals. */ 14 | "noUnusedParameters": true, /* Report errors on unused parameters. */ 15 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 16 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 17 | 18 | /* Module Resolution Options */ 19 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 20 | } 21 | } 22 | --------------------------------------------------------------------------------