From 35487b27405e785e10a2ef84f9c82c4d444f4300 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Sat, 7 Dec 2024 18:21:51 +0100 Subject: [PATCH] Require Node.js 18 and move to ESM Fixes #86 Fixes #85 Fixes #83 Fixes #79 Fixes #52 --- .github/security.md | 3 + .github/workflows/main.yml | 7 +- contributing.md | 9 +- lib/index.js | 103 ++++++++++++----------- lib/providers.js | 73 ++++++++-------- lib/push.js | 55 +++++++------ package.json | 31 ++++--- readme.md | 20 +++-- test/_sub-process.js | 11 ++- test/ask-permission.js | 23 ++++-- test/insight.js | 128 +++++++++++++++++------------ test/providers-google-analytics.js | 44 +++++----- test/providers-yandex-metrica.js | 30 +++---- test/providers.js | 8 +- 14 files changed, 291 insertions(+), 254 deletions(-) create mode 100644 .github/security.md diff --git a/.github/security.md b/.github/security.md new file mode 100644 index 0000000..5358dc5 --- /dev/null +++ b/.github/security.md @@ -0,0 +1,3 @@ +# Security Policy + +To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 441975c..346585c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,10 +10,11 @@ jobs: fail-fast: false matrix: node-version: - - 16 + - 20 + - 18 steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/contributing.md b/contributing.md index b49819c..0fc9f5b 100644 --- a/contributing.md +++ b/contributing.md @@ -7,10 +7,10 @@ In addition to the regular tests via `npm test`, contributors should also ensure Please sign up for a free GA web-tracking account, then run below script using your tracking code: ```js -const Insight = require('lib/insight.js'); +import Insight from 'lib/insight.js'; const insight = new Insight({ - trackingCode: 'UA-00000000-0', // replace with your test GA tracking code + trackingCode: 'UA-00000000-0', // Replace with your test GA tracking code. packageName: 'test app', packageVersion: '0.0.1' }); @@ -21,8 +21,3 @@ insight.track('hello', 'sindre'); Then visit GA's Real Time dashboard and ensure data is showing up: ![analytics screenshot](screenshot-real-time.png) - - -## Other Guidelines - -Please see Yeoman's [contributing docs](https://github.com/yeoman/yeoman/blob/master/contributing.md). diff --git a/lib/index.js b/lib/index.js index 807c581..f5f02b2 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,51 +1,57 @@ -'use strict'; -const path = require('path'); -const childProcess = require('child_process'); -const osName = require('os-name'); -const Conf = require('conf'); -const chalk = require('chalk'); -const debounce = require('lodash.debounce'); -const inquirer = require('inquirer'); -const uuid = require('uuid'); -const providers = require('./providers.js'); +import process from 'node:process'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import childProcess from 'node:child_process'; +import {randomUUID} from 'node:crypto'; +import osName from 'os-name'; +import Conf from 'conf'; +import chalk from 'chalk'; +import debounce from 'lodash.debounce'; +import inquirer from 'inquirer'; +import providers from './providers.js'; const DEBOUNCE_MS = 100; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); class Insight { - constructor(options) { - options = options || {}; - options.pkg = options.pkg || {}; + #queue = {}; + #permissionTimeout = 30; + #debouncedSend; + + constructor(options = {}) { + const {pkg: package_ = {}} = options; // Deprecated options // TODO: Remove these at some point in the future if (options.packageName) { - options.pkg.name = options.packageName; + package_.name = options.packageName; } if (options.packageVersion) { - options.pkg.version = options.packageVersion; + package_.version = options.packageVersion; } - if (!options.trackingCode || !options.pkg.name) { + if (!options.trackingCode || !package_.name) { throw new Error('trackingCode and pkg.name required'); } this.trackingCode = options.trackingCode; - this.trackingProvider = options.trackingProvider || 'google'; - this.packageName = options.pkg.name; - this.packageVersion = options.pkg.version || 'undefined'; + this.trackingProvider = options.trackingProvider ?? 'google'; + this.packageName = package_.name; + this.packageVersion = package_.version ?? 'undefined'; this.os = osName(); this.nodeVersion = process.version; this.appVersion = this.packageVersion; - this.config = options.config || new Conf({ + this.config = options.config ?? new Conf({ + projectName: package_.name, configName: `insight-${this.packageName}`, defaults: { - clientId: options.clientId || Math.floor(Date.now() * Math.random()), + clientId: options.clientId ?? Math.floor(Date.now() * Math.random()), }, }); - this._queue = {}; - this._permissionTimeout = 30; - this._debouncedSend = debounce(this._send, DEBOUNCE_MS, {leading: true}); + + this.#debouncedSend = debounce(this.#send.bind(this), DEBOUNCE_MS, {leading: true}); } get optOut() { @@ -64,22 +70,23 @@ class Insight { this.config.set('clientId', value); } - _save() { + #save() { setImmediate(() => { - this._debouncedSend(); + this.#debouncedSend(); }); } - _send() { - const pending = Object.keys(this._queue).length; + #send() { + const pending = Object.keys(this.#queue).length; if (pending === 0) { return; } - this._fork(this._getPayload()); - this._queue = {}; + this._fork(this.#getPayload()); + this.#queue = {}; } + // For testing. _fork(payload) { // Extracted to a method so it can be easily mocked const cp = childProcess.fork(path.join(__dirname, 'push.js'), {silent: true}); @@ -88,9 +95,9 @@ class Insight { cp.disconnect(); } - _getPayload() { + #getPayload() { return { - queue: {...this._queue}, + queue: {...this.#queue}, packageName: this.packageName, packageVersion: this.packageVersion, trackingCode: this.trackingCode, @@ -98,23 +105,26 @@ class Insight { }; } - _getRequestObj(...args) { - return providers[this.trackingProvider].apply(this, args); + // For testing. + _getRequestObj(...arguments_) { + return providers[this.trackingProvider].apply(this, arguments_); } - track(...args) { + track(...arguments_) { if (this.optOut) { return; } - const path = '/' + args.map(element => String(element).trim().replace(/ /, '-')).join('/'); + const path = '/' + arguments_.map(element => + String(element).trim().replace(/ /, '-'), + ).join('/'); // Timestamp isn't unique enough since it can end up with duplicate entries - this._queue[`${Date.now()} ${uuid.v4()}`] = { + this.#queue[`${Date.now()} ${randomUUID()}`] = { path, type: 'pageview', }; - this._save(); + this.#save(); } trackEvent(options) { @@ -126,32 +136,33 @@ class Insight { throw new Error('Event tracking is supported only for Google Analytics'); } - if (!options || !options.category || !options.action) { + if (!options?.category || !options?.action) { throw new Error('`category` and `action` required'); } // Timestamp isn't unique enough since it can end up with duplicate entries - this._queue[`${Date.now()} ${uuid.v4()}`] = { + this.#queue[`${Date.now()} ${randomUUID()}`] = { category: options.category, action: options.action, label: options.label, value: options.value, type: 'event', }; - this._save(); + + this.#save(); } - askPermission(message) { + async askPermission(message) { const defaultMessage = `May ${chalk.cyan(this.packageName)} anonymously report usage statistics to improve the tool over time?`; if (!process.stdout.isTTY || process.argv.includes('--no-insight') || process.env.CI) { - return Promise.resolve(); + return; } const prompt = inquirer.prompt({ type: 'confirm', name: 'optIn', - message: message || defaultMessage, + message: message ?? defaultMessage, default: true, }); @@ -165,7 +176,7 @@ class Insight { // Automatically opt out this.optOut = true; resolve(false); - }, this._permissionTimeout * 1000); + }, this.#permissionTimeout * 1000); }); const promise = (async () => { @@ -183,4 +194,4 @@ class Insight { } } -module.exports = Insight; +export default Insight; diff --git a/lib/providers.js b/lib/providers.js index 02a7d45..7386334 100644 --- a/lib/providers.js +++ b/lib/providers.js @@ -1,28 +1,23 @@ -'use strict'; -const qs = require('querystring'); - /** - * Tracking providers - * - * Each provider is a function(id, path) that should return - * options object for request() call. It will be called bound - * to Insight instance object. - */ - -module.exports = { +Tracking providers. + +Each provider is a function(id, path) that should return options object for ky() call. It will be called bound to Insight instance object. +*/ + +const payload = { // Google Analytics — https://www.google.com/analytics/ google(id, payload) { const now = Date.now(); - const _qs = { + const queryParameters = new URLSearchParams({ // GA Measurement Protocol API version - v: 1, + v: '1', // Hit type t: payload.type, // Anonymize IP - aip: 1, + aip: '1', tid: this.trackingCode, @@ -42,63 +37,65 @@ module.exports = { // Cache busting, need to be last param sent z: now, - }; + }); // Set payload data based on the tracking type if (payload.type === 'event') { - _qs.ec = payload.category; - _qs.ea = payload.action; + queryParameters.set('ec', payload.category); // Event category + queryParameters.set('ea', payload.action); // Event action if (payload.label) { - _qs.el = payload.label; + queryParameters.set('el', payload.label); // Event label } if (payload.value) { - _qs.ev = payload.value; + queryParameters.set('ev', payload.value); // Event value } } else { - _qs.dp = payload.path; + queryParameters.set('dp', payload.path); // Document path } return { url: 'https://ssl.google-analytics.com/collect', method: 'POST', - // GA docs recommends body payload via POST instead of querystring via GET - body: qs.stringify(_qs), + // GA docs recommend body payload via POST instead of querystring via GET + body: queryParameters.toString(), + headers: { + 'content-type': 'application/x-www-form-urlencoded', + }, }; }, + // Yandex.Metrica - https://metrica.yandex.com yandex(id, payload) { - const request = require('request'); - const ts = new Date(Number.parseInt(id, 10)) .toISOString() - .replace(/[-:T]/g, '') - .replace(/\..*$/, ''); + .replaceAll(/[-:T]/g, '') // Remove `-`, `:`, and `T` + .replace(/\..*$/, ''); // Remove milliseconds const {path} = payload; - const qs = { - wmode: 3, - ut: 'noindex', + + // Query parameters for Yandex.Metrica + const queryParameters = new URLSearchParams({ + wmode: '3', // Web mode + ut: 'noindex', // User type 'page-url': `http://${this.packageName}.insight${path}?version=${this.packageVersion}`, 'browser-info': `i:${ts}:z:0:t:${path}`, // Cache busting rn: Date.now(), - }; + }); const url = `https://mc.yandex.ru/watch/${this.trackingCode}`; - // Set custom cookie using tough-cookie - const _jar = request.jar(); - const cookieString = `name=yandexuid; value=${this.clientId}; path=/;`; - const cookie = request.cookie(cookieString); - _jar.setCookie(cookie, url); - return { url, method: 'GET', - qs, - jar: _jar, + searchParams: queryParameters, + headers: { + Cookie: `yandexuid=${this.clientId}`, + }, }; }, }; + +export default payload; diff --git a/lib/push.js b/lib/push.js index 966a723..ac96535 100644 --- a/lib/push.js +++ b/lib/push.js @@ -1,39 +1,44 @@ -'use strict'; -const request = require('request'); -const async = require('async'); -const Insight = require('.'); +import process from 'node:process'; +import ky from 'ky'; +import Insight from './index.js'; // Messaged on each debounced `track()` // Gets the queue, merges is with the previous and tries to upload everything // If it fails, it will save everything again -process.on('message', message => { +process.on('message', async message => { const insight = new Insight(message); const {config} = insight; - const q = config.get('queue') || {}; + const queue = config.get('queue') ?? {}; - Object.assign(q, message.queue); + Object.assign(queue, message.queue); config.delete('queue'); - async.forEachSeries(Object.keys(q), (element, cb) => { - const parts = element.split(' '); - const id = parts[0]; - const payload = q[element]; + try { + // Process queue items sequentially + for (const element of Object.keys(queue)) { + const [id] = element.split(' '); + const payload = queue[element]; - request(insight._getRequestObj(id, payload), error => { - if (error) { - cb(error); - return; - } + const requestObject = insight._getRequestObj(id, payload); - cb(); - }); - }, error => { - if (error) { - const q2 = config.get('queue') || {}; - Object.assign(q2, q); - config.set('queue', q2); - } + // Convert request options to ky format + const kyOptions = { + method: requestObject.method ?? 'GET', + headers: requestObject.headers, + body: requestObject.body, + searchParams: requestObject.searchParams, + retry: 0, // Disable retries as we handle failures by saving to queue + }; + // Wait for each request to complete before moving to the next + // eslint-disable-next-line no-await-in-loop + await ky(requestObject.url, kyOptions); + } + } catch { + const existingQueue = config.get('queue') ?? {}; + Object.assign(existingQueue, queue); + config.set('queue', existingQueue); + } finally { process.exit(); - }); + } }); diff --git a/package.json b/package.json index 7254e86..7a5666e 100644 --- a/package.json +++ b/package.json @@ -3,15 +3,17 @@ "version": "0.11.1", "description": "Understand how your tool is being used by anonymously reporting usage metrics to Google Analytics or Yandex.Metrica", "license": "BSD-2-Clause", - "repository": "yeoman/insight", + "repository": "sindresorhus/insight", "author": { "name": "Sindre Sorhus", "email": "sindresorhus@gmail.com", "url": "https://sindresorhus.com" }, - "main": "lib", + "type": "module", + "exports": "./lib/index.js", + "sideEffects": false, "engines": { - "node": ">=12.20" + "node": ">=18.18" }, "scripts": { "test": "xo && ava --timeout=20s" @@ -35,21 +37,18 @@ "metrica" ], "dependencies": { - "async": "^2.6.2", - "chalk": "^4.1.1", - "conf": "^10.0.1", - "inquirer": "^6.3.1", + "chalk": "^5.4.1", + "conf": "^13.1.0", + "inquirer": "^12.3.2", + "ky": "^1.7.4", "lodash.debounce": "^4.0.8", - "os-name": "^4.0.1", - "request": "^2.88.0", - "tough-cookie": "^4.0.0", - "uuid": "^8.3.2" + "os-name": "^6.0.0" }, "devDependencies": { - "ava": "^2.4.0", - "execa": "^1.0.0", - "object-values": "^2.0.0", - "sinon": "^7.3.1", - "xo": "^0.42.0" + "ava": "^6.2.0", + "execa": "^9.5.2", + "sinon": "^19.0.2", + "tough-cookie": "^5.1.0", + "xo": "^0.60.0" } } diff --git a/readme.md b/readme.md index 99e3f0d..3c2a8ae 100644 --- a/readme.md +++ b/readme.md @@ -1,11 +1,13 @@ -# Insight [![Build Status](https://secure.travis-ci.org/yeoman/insight.svg?branch=master)](https://travis-ci.org/yeoman/insight) +# Insight > Understand how your tool is being used by anonymously reporting usage metrics to [Google Analytics](https://www.google.com/analytics/) or [Yandex.Metrica](https://metrica.yandex.com) +**This package is in maintenance mode. No new features will be added.** + ## Install -``` -$ npm install insight +```sh +npm install insight ``` ## Access data / generate dashboards @@ -45,13 +47,13 @@ Below is what Insight is capable of tracking. Individual implementation can choo ### Google Analytics ```js -const Insight = require('insight'); -const pkg = require('./package.json'); +import Insight from 'insight'; +import packageJson from './package.json' with {type: 'json'}; const insight = new Insight({ // Google Analytics tracking code trackingCode: 'UA-XXXXXXXX-X', - pkg + pkg: packageJson }); // Ask for permission the first time @@ -74,14 +76,14 @@ insight.trackEvent({ ### Yandex.Metrica ```js -const Insight = require('insight'); -const pkg = require('./package.json'); +import Insight from 'insight'; +import packageJson from './package.json' with {type: 'json'}; const insight = new Insight({ // Yandex.Metrica counter id trackingCode: 'XXXXXXXXX' trackingProvider: 'yandex', - pkg + pkg: packageJson }); // Ask for permission the first time diff --git a/test/_sub-process.js b/test/_sub-process.js index cbda4c4..f60fdcc 100644 --- a/test/_sub-process.js +++ b/test/_sub-process.js @@ -1,5 +1,5 @@ -'use strict'; -const Insight = require('../lib/index.js'); +import process from 'node:process'; +import Insight from '../lib/index.js'; const insight = new Insight({ packageName: 'yeoman', @@ -11,7 +11,10 @@ if (process.env.permissionTimeout) { insight._permissionTimeout = process.env.permissionTimeout; } -(async () => { +try { await insight.askPermission(''); process.exit(145); // eslint-disable-line unicorn/no-process-exit -})(); +} catch (error) { + console.error(error); + process.exit(1); // eslint-disable-line unicorn/no-process-exit +} diff --git a/test/ask-permission.js b/test/ask-permission.js index c9f0005..d8b3b41 100644 --- a/test/ask-permission.js +++ b/test/ask-permission.js @@ -1,22 +1,27 @@ -import execa from 'execa'; +import process from 'node:process'; +import {execa} from 'execa'; import test from 'ava'; test('skip in TTY mode', async t => { const error = await t.throwsAsync(execa('node', ['./test/_sub-process.js'])); - t.is(error.code, 145); + t.is(error.exitCode, 145); }); test('skip when using the --no-insight flag', async t => { - const error = await t.throwsAsync(execa('node', ['./test/_sub-process.js', '--no-insight'], {stdio: 'inherit'})); - t.is(error.code, 145); + const error = await t.throwsAsync(execa('node', ['./test/_sub-process.js', '--no-insight'], { + stdio: 'inherit', + })); + t.is(error.exitCode, 145); }); test('skip in CI mode', async t => { const {CI} = process.env; process.env.CI = true; - const error = await t.throwsAsync(execa('node', ['./test/_sub-process.js'], {stdio: 'inherit'})); - t.is(error.code, 145); + const error = await t.throwsAsync(execa('node', ['./test/_sub-process.js'], { + stdio: 'inherit', + })); + t.is(error.exitCode, 145); process.env.CI = CI; }); @@ -28,8 +33,10 @@ test('skip after timeout', async t => { process.env.CI = true; process.env.permissionTimeout = 0.1; - const error = await t.throwsAsync(execa('node', ['./test/_sub-process.js'], {stdio: 'inherit'})); - t.is(error.code, 145); + const error = await t.throwsAsync(execa('node', ['./test/_sub-process.js'], { + stdio: 'inherit', + })); + t.is(error.exitCode, 145); process.env.CI = CI; process.env.permissionTimeout = permissionTimeout; diff --git a/test/insight.js b/test/insight.js index eb36c0b..3c74c4c 100644 --- a/test/insight.js +++ b/test/insight.js @@ -1,4 +1,4 @@ -import objectValues from 'object-values'; +import {setTimeout as delay, setImmediate as setImmediatePromise} from 'node:timers/promises'; import sinon from 'sinon'; import test from 'ava'; import Insight from '../lib/index.js'; @@ -7,82 +7,100 @@ test('throw exception when trackingCode or packageName is not provided', t => { /* eslint-disable no-new */ t.throws(() => { new Insight({}); - }, Error); + }, {instanceOf: Error}); t.throws(() => { new Insight({trackingCode: 'xxx'}); - }, Error); + }, {instanceOf: Error}); t.throws(() => { new Insight({packageName: 'xxx'}); - }, Error); + }, {instanceOf: Error}); /* eslint-enable no-new */ }); -test.cb('forks a new tracker right after track()', t => { +test('forks a new tracker right after track()', async t => { const insight = newInsight(); insight.track('test'); - setImmediate(() => { - t.deepEqual(forkedCalls(insight), [ - // A single fork with a single path - ['/test'], - ]); - t.end(); - }); + + await setImmediatePromise(); + + t.deepEqual(forkedCalls(insight), [ + // A single fork with a single path + ['/test'], + ]); }); -test.cb('only forks once if many pages are tracked in the same event loop run', t => { +test('only forks once if many pages are tracked in the same event loop run', async t => { const insight = newInsight(); insight.track('foo'); insight.track('bar'); - setImmediate(() => { - t.deepEqual(forkedCalls(insight), [ - // A single fork with both paths - ['/foo', '/bar'], - ]); - t.end(); - }); + + await setImmediatePromise(); + + t.deepEqual(forkedCalls(insight), [ + // A single fork with both paths + ['/foo', '/bar'], + ]); }); -test.cb('debounces forking every 100 millis (close together)', t => { +test('debounces forking every 100 millis (close together)', async t => { const insight = newInsight(); insight.track('0'); - setTimeout(() => insight.track('50'), 50); - setTimeout(() => insight.track('100'), 100); - setTimeout(() => insight.track('150'), 150); - setTimeout(() => insight.track('200'), 200); - setTimeout(() => { - t.deepEqual(forkedCalls(insight), [ - // The first one is sent straight away because of the leading debounce - ['/0'], - // The others are grouped together because they're all < 100ms apart - ['/50', '/100', '/150', '/200'], - ]); - t.end(); - }, 1000); + + await delay(50); + insight.track('50'); + + await delay(50); + insight.track('100'); + + await delay(50); + insight.track('150'); + + await delay(50); + insight.track('200'); + + await delay(1000); + + t.deepEqual(forkedCalls(insight), [ + // The first one is sent straight away because of the leading debounce + ['/0'], + // The others are grouped together because they're all < 100ms apart + ['/50', '/100', '/150', '/200'], + ]); }); -test.cb('debounces forking every 100 millis (far apart)', t => { +test('debounces forking every 100 millis (far apart)', async t => { const insight = newInsight(); insight.track('0'); - setTimeout(() => insight.track('50'), 50); - setTimeout(() => insight.track('100'), 100); - setTimeout(() => insight.track('150'), 150); - setTimeout(() => insight.track('300'), 300); - setTimeout(() => insight.track('350'), 350); - setTimeout(() => { - t.deepEqual(forkedCalls(insight), [ - // Leading call - ['/0'], - // Sent together since there is an empty 100ms window afterwards - ['/50', '/100', '/150'], - // Sent on its own because it's a new leading debounce - ['/300'], - // Finally, the last one is sent - ['/350'], - ]); - t.end(); - }, 1000); + + await delay(50); + insight.track('50'); + + await delay(50); + insight.track('100'); + + await delay(50); + insight.track('150'); + + await delay(150); + insight.track('300'); + + await delay(50); + insight.track('350'); + + await delay(1000); + + t.deepEqual(forkedCalls(insight), [ + // Leading call + ['/0'], + // Sent together since there is an empty 100ms window afterwards + ['/50', '/100', '/150'], + // Sent on its own because it's a new leading debounce + ['/300'], + // Finally, the last one is sent + ['/350'], + ]); }); // Return a valid insight instance which doesn't actually send analytics (mocked) @@ -104,5 +122,7 @@ function newInsight() { // ['/three', 'four'], // second call tracked 2 more paths // ] function forkedCalls(insight) { - return insight._fork.args.map(callArgs => objectValues(callArgs[0].queue).map(q => q.path)); + return insight._fork.args.map(callArguments => + Object.values(callArguments[0].queue).map(q => q.path), + ); } diff --git a/test/providers-google-analytics.js b/test/providers-google-analytics.js index 0bca5d4..343af53 100644 --- a/test/providers-google-analytics.js +++ b/test/providers-google-analytics.js @@ -1,10 +1,10 @@ -import qs from 'querystring'; // eslint-disable-line unicorn/prefer-node-protocol, no-restricted-imports +import process from 'node:process'; import osName from 'os-name'; import test from 'ava'; import Insight from '../lib/index.js'; -const pkg = 'yeoman'; -const ver = '0.0.0'; +const package_ = 'yeoman'; +const version = '0.0.0'; const code = 'GA-1234567-1'; const ts = Date.UTC(2013, 7, 24, 22, 33, 44); const pageviewPayload = { @@ -21,35 +21,35 @@ const eventPayload = { const insight = new Insight({ trackingCode: code, - packageName: pkg, - packageVersion: ver, + packageName: package_, + packageVersion: version, }); test('form valid request for pageview', t => { const requestObject = insight._getRequestObj(ts, pageviewPayload); - const _qs = qs.parse(requestObject.body); + const parameters = new URLSearchParams(requestObject.body); - t.is(_qs.tid, code); - t.is(Number(_qs.cid), Number(insight.clientId)); - t.is(_qs.dp, pageviewPayload.path); - t.is(_qs.cd1, osName()); - t.is(_qs.cd2, process.version); - t.is(_qs.cd3, ver); + t.is(parameters.get('tid'), code); + t.is(Number(parameters.get('cid')), Number(insight.clientId)); + t.is(parameters.get('dp'), pageviewPayload.path); + t.is(parameters.get('cd1'), osName()); + t.is(parameters.get('cd2'), process.version); + t.is(parameters.get('cd3'), version); }); test('form valid request for eventTracking', t => { const requestObject = insight._getRequestObj(ts, eventPayload); - const _qs = qs.parse(requestObject.body); + const parameters = new URLSearchParams(requestObject.body); - t.is(_qs.tid, code); - t.is(Number(_qs.cid), Number(insight.clientId)); - t.is(_qs.ec, eventPayload.category); - t.is(_qs.ea, eventPayload.action); - t.is(_qs.el, eventPayload.label); - t.is(_qs.ev, eventPayload.value); - t.is(_qs.cd1, osName()); - t.is(_qs.cd2, process.version); - t.is(_qs.cd3, ver); + t.is(parameters.get('tid'), code); + t.is(Number(parameters.get('cid')), Number(insight.clientId)); + t.is(parameters.get('ec'), eventPayload.category); + t.is(parameters.get('ea'), eventPayload.action); + t.is(parameters.get('el'), eventPayload.label); + t.is(parameters.get('ev'), eventPayload.value); + t.is(parameters.get('cd1'), osName()); + t.is(parameters.get('cd2'), process.version); + t.is(parameters.get('cd3'), version); }); /* eslint-disable ava/no-skip-test */ diff --git a/test/providers-yandex-metrica.js b/test/providers-yandex-metrica.js index bfdf895..63dced8 100644 --- a/test/providers-yandex-metrica.js +++ b/test/providers-yandex-metrica.js @@ -1,8 +1,9 @@ import test from 'ava'; +import {Cookie} from 'tough-cookie'; import Insight from '../lib/index.js'; -const pkg = 'yeoman'; -const ver = '0.0.0'; +const package_ = 'yeoman'; +const version = '0.0.0'; const code = 'GA-1234567-1'; const ts = Date.UTC(2013, 7, 24, 22, 33, 44); const pageviewPayload = { @@ -13,26 +14,19 @@ const pageviewPayload = { const insight = new Insight({ trackingCode: code, trackingProvider: 'yandex', - packageName: pkg, - packageVersion: ver, + packageName: package_, + packageVersion: version, }); -test('form valid request', async t => { - const request = require('request'); - - // Test querystrings +test('form valid request', t => { const requestObject = insight._getRequestObj(ts, pageviewPayload); - const _qs = requestObject.qs; - t.is(_qs['page-url'], `http://${pkg}.insight/test/path?version=${ver}`); - t.is(_qs['browser-info'], `i:20130824223344:z:0:t:${pageviewPayload.path}`); + const _qs = Object.fromEntries(requestObject.searchParams); - // Test cookie - await request(requestObject); + t.is(_qs['page-url'], `http://${package_}.insight/test/path?version=${version}`); + t.is(_qs['browser-info'], `i:20130824223344:z:0:t:${pageviewPayload.path}`); - // Cookie string looks like: - // [{"key":"name","value":"yandexuid", - // "extensions":["value=80579748502"],"path":"/","creation":... - const cookieClientId = requestObject.jar.getCookies(requestObject.url)[0].extensions[0].split('=')[1]; - t.is(Number(cookieClientId), Number(insight.clientId)); + const cookieHeader = requestObject.headers.Cookie; + const cookie = Cookie.parse(cookieHeader); + t.is(Number(cookie.value), Number(insight.clientId)); }); diff --git a/test/providers.js b/test/providers.js index d980e1b..862fe09 100644 --- a/test/providers.js +++ b/test/providers.js @@ -2,8 +2,8 @@ import sinon from 'sinon'; import test from 'ava'; import Insight from '../lib/index.js'; -const pkg = 'yeoman'; -const ver = '0.0.0'; +const package_ = 'yeoman'; +const version = '0.0.0'; let config; let insight; @@ -16,8 +16,8 @@ test.beforeEach(() => { insight = new Insight({ trackingCode: 'xxx', - packageName: pkg, - packageVersion: ver, + packageName: package_, + packageVersion: version, config, }); });