├── .eslintrc ├── .gitignore ├── HISTORY.md ├── LICENSE ├── Makefile ├── README.md ├── circle.yml ├── component.json ├── karma.conf.ci.js ├── karma.conf.js ├── lib └── index.js ├── package.json └── test ├── .eslintrc └── index.test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@segment/eslint-config/browser/legacy" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 2.0.0 / 2016-06-16 2 | ================== 3 | 4 | * Modernize test harness; remove Duo support, add Browserify support 5 | 6 | 1.1.0 / 2015-08-27 7 | ================== 8 | 9 | * add .strict method, which only returns Google Analytics's specced utm params 10 | 11 | 1.0.3 / 2015-08-19 12 | ================== 13 | 14 | * refactor and add CI 15 | * ignore utm params that do not start with `utm` 16 | * reflect campaign->name conversion in readme 17 | 18 | 1.0.2 / 2014-11-11 19 | ================== 20 | 21 | * use duo 22 | * fix: replace "?" with "&" 23 | 24 | 1.0.1 / 2014-04-15 25 | ================== 26 | 27 | * unpin querystring since component is broken 28 | 29 | 1.0.0 / 2014-03-13 30 | ================== 31 | 32 | * rename .campaign to .name 33 | 34 | 0.0.1 / 2014-02-05 35 | ================== 36 | 37 | * Initial commit 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Segment.io, Inc. (friends@segment.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## 2 | # Binaries 3 | ## 4 | 5 | ESLINT := node_modules/.bin/eslint 6 | KARMA := node_modules/.bin/karma 7 | 8 | ## 9 | # Files 10 | ## 11 | 12 | LIBS = $(shell find lib -type f -name "*.js") 13 | TESTS = $(shell find test -type f -name "*.test.js") 14 | SUPPORT = $(wildcard karma.conf*.js) 15 | ALL_FILES = $(LIBS) $(TESTS) $(SUPPORT) 16 | 17 | ## 18 | # Program options/flags 19 | ## 20 | 21 | # A list of options to pass to Karma 22 | # Overriding this overwrites all options specified in this file (e.g. BROWSERS) 23 | KARMA_FLAGS ?= 24 | 25 | # A list of Karma browser launchers to run 26 | # http://karma-runner.github.io/0.13/config/browsers.html 27 | BROWSERS ?= 28 | ifdef BROWSERS 29 | KARMA_FLAGS += --browsers $(BROWSERS) 30 | endif 31 | 32 | ifdef CI 33 | KARMA_CONF ?= karma.conf.ci.js 34 | else 35 | KARMA_CONF ?= karma.conf.js 36 | endif 37 | 38 | # Mocha flags. 39 | GREP ?= . 40 | 41 | ## 42 | # Tasks 43 | ## 44 | 45 | # Install node modules. 46 | node_modules: package.json $(wildcard node_modules/*/package.json) 47 | @npm install 48 | @touch $@ 49 | 50 | # Install dependencies. 51 | install: node_modules 52 | 53 | # Remove temporary files and build artifacts. 54 | clean: 55 | rm -rf *.log coverage 56 | .PHONY: clean 57 | 58 | # Remove temporary files, build artifacts, and vendor dependencies. 59 | distclean: clean 60 | rm -rf node_modules 61 | .PHONY: distclean 62 | 63 | # Lint JavaScript source files. 64 | lint: install 65 | @$(ESLINT) $(ALL_FILES) 66 | .PHONY: lint 67 | 68 | # Attempt to fix linting errors. 69 | fmt: install 70 | @$(ESLINT) --fix $(ALL_FILES) 71 | .PHONY: fmt 72 | 73 | # Run browser unit tests in a browser. 74 | test-browser: install 75 | @$(KARMA) start $(KARMA_FLAGS) $(KARMA_CONF) 76 | 77 | # Default test target. 78 | test: lint test-browser 79 | .PHONY: test 80 | .DEFAULT_GOAL = test 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # utm-params 2 | 3 | > **Note** 4 | > Segment has paused maintenance on this project, but may return it to an active status in the future. Issues and pull requests from external contributors are not being considered, although internal contributions may appear from time to time. The project remains available under its open source license for anyone to use. 5 | 6 | [![CircleCI](https://circleci.com/gh/segmentio/utm-params.svg?style=shield&circle-token=d49fc6469182ce8392a332106af7a5fb76b2f8b7)](https://circleci.com/gh/segmentio/utm-params) 7 | [![Codecov](https://img.shields.io/codecov/c/github/segmentio/utm-params/master.svg?maxAge=2592000)](https://codecov.io/gh/segmentio/utm-params) 8 | 9 | Parse a URL, returning all UTM parameters. 10 | 11 | ## Installation 12 | 13 | ```sh 14 | $ npm install @segment/utm-params 15 | ``` 16 | 17 | ## API 18 | 19 | ### utm(querystring : string) 20 | 21 | ```js 22 | utm('?utm_source=google&utm_medium=medium&utm_term=keyword&utm_content=some%20content&utm_campaign=some%20campaign&utm_test=other%20value'); 23 | ``` 24 | 25 | ```json 26 | { 27 | "source": "google", 28 | "medium": "medium", 29 | "term": "keyword", 30 | "content": "some content", 31 | "name": "some campaign", 32 | "test": "other value" 33 | } 34 | ``` 35 | 36 | ###utm.strict(querystring : string) 37 | 38 | Will *only* return the 5 Google Analytics spec'd utm params 39 | 40 | ```js 41 | utm.strict('?utm_source=google&utm_medium=medium&utm_term=keyword&utm_content=some%20content&utm_campaign=some%20campaign&utm_test=other%20value'); 42 | ``` 43 | 44 | ```json 45 | { 46 | "source": "google", 47 | "medium": "medium", 48 | "term": "keyword", 49 | "content": "some content", 50 | "name": "some campaign" 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 4 4 | environment: 5 | NPM_CONFIG_PROGRESS: false 6 | NPM_CONFIG_SPIN: false 7 | 8 | dependencies: 9 | pre: 10 | - npm config set "//registry.npmjs.org/:_authToken" $NPM_AUTH 11 | - npm -g install codecov 12 | override: 13 | - make install 14 | 15 | test: 16 | override: 17 | - make test 18 | post: 19 | - cp -R coverage $CIRCLE_ARTIFACTS/ 20 | - codecov 21 | 22 | deployment: 23 | publish: 24 | owner: segmentio 25 | # Works on e.g. `1.0.0-alpha.1` 26 | tag: /[0-9]+(\.[0-9]+)*(-.+)?/ 27 | commands: 28 | - npm publish . 29 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "utm-params", 3 | "repo": "segmentio/utm-params", 4 | "version": "1.1.0", 5 | "description": "Parse a URL, returning all UTM parameters.", 6 | "main": "index.js", 7 | "scripts": [ 8 | "index.js" 9 | ], 10 | "dependencies": { 11 | "component/querystring": "2.0.0", 12 | "ndhoule/includes": "^1.0.0", 13 | "ndhoule/foldl": "^1.0.3" 14 | }, 15 | "development": { 16 | "component/assert": "*", 17 | "visionmedia/mocha": "*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /karma.conf.ci.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | var baseConfig = require('./karma.conf'); 5 | 6 | var customLaunchers = { 7 | sl_chrome_latest: { 8 | base: 'SauceLabs', 9 | browserName: 'chrome', 10 | platform: 'linux', 11 | version: 'latest' 12 | }, 13 | sl_chrome_latest_1: { 14 | base: 'SauceLabs', 15 | browserName: 'chrome', 16 | platform: 'linux', 17 | version: 'latest-1' 18 | }, 19 | sl_firefox_latest: { 20 | base: 'SauceLabs', 21 | browserName: 'firefox', 22 | platform: 'linux', 23 | version: 'latest' 24 | }, 25 | sl_firefox_latest_1: { 26 | base: 'SauceLabs', 27 | browserName: 'firefox', 28 | platform: 'linux', 29 | version: 'latest-1' 30 | }, 31 | sl_safari_9: { 32 | base: 'SauceLabs', 33 | browserName: 'safari', 34 | version: '9.0' 35 | }, 36 | sl_ie_7: { 37 | base: 'SauceLabs', 38 | browserName: 'internet explorer', 39 | version: '7' 40 | }, 41 | sl_ie_8: { 42 | base: 'SauceLabs', 43 | browserName: 'internet explorer', 44 | version: '8' 45 | }, 46 | sl_ie_9: { 47 | base: 'SauceLabs', 48 | browserName: 'internet explorer', 49 | version: '9' 50 | }, 51 | sl_ie_10: { 52 | base: 'SauceLabs', 53 | browserName: 'internet explorer', 54 | version: '10' 55 | }, 56 | sl_ie_11: { 57 | base: 'SauceLabs', 58 | browserName: 'internet explorer', 59 | version: '11' 60 | }, 61 | sl_edge_latest: { 62 | base: 'SauceLabs', 63 | browserName: 'microsoftedge' 64 | } 65 | }; 66 | 67 | module.exports = function(config) { 68 | baseConfig(config); 69 | 70 | if (!process.env.SAUCE_USERNAME || !process.env.SAUCE_ACCESS_KEY) { 71 | throw new Error('SAUCE_USERNAME and SAUCE_ACCESS_KEY environment variables are required but are missing'); 72 | } 73 | 74 | config.set({ 75 | browserDisconnectTolerance: 1, 76 | 77 | singleRun: true, 78 | 79 | browsers: ['PhantomJS'].concat(Object.keys(customLaunchers)), 80 | 81 | customLaunchers: customLaunchers, 82 | 83 | sauceLabs: { 84 | testName: require('./package.json').name 85 | }, 86 | 87 | coverageReporter: { 88 | reporters: [ 89 | { type: 'lcov' } 90 | ] 91 | } 92 | }); 93 | }; 94 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | files: [ 7 | 'test/**/*.test.js' 8 | ], 9 | 10 | browsers: ['PhantomJS'], 11 | 12 | frameworks: ['browserify', 'mocha'], 13 | 14 | reporters: ['spec', 'coverage'], 15 | 16 | preprocessors: { 17 | 'test/**/*.js': 'browserify' 18 | }, 19 | 20 | client: { 21 | mocha: { 22 | grep: process.env.GREP 23 | } 24 | }, 25 | 26 | browserify: { 27 | debug: true, 28 | transform: [ 29 | [ 30 | 'browserify-istanbul', 31 | { 32 | instrumenterConfig: { 33 | embedSource: true 34 | } 35 | } 36 | ] 37 | ] 38 | }, 39 | 40 | coverageReporter: { 41 | reporters: [ 42 | { type: 'text' }, 43 | { type: 'html' }, 44 | { type: 'json' } 45 | ] 46 | } 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var foldl = require('@ndhoule/foldl'); 8 | var parse = require('component-querystring').parse; 9 | 10 | /** 11 | * hasOwnProperty reference. 12 | */ 13 | 14 | var has = Object.prototype.hasOwnProperty; 15 | 16 | /** 17 | * Get all utm params from the given `querystring` 18 | * 19 | * @param {String} query 20 | * @return {Object} 21 | * @api private 22 | */ 23 | 24 | function utm(query) { 25 | // Remove leading ? if present 26 | if (query.charAt(0) === '?') { 27 | query = query.substring(1); 28 | } 29 | 30 | query = query.replace(/\?/g, '&'); 31 | 32 | var param; 33 | var params = parse(query); 34 | var results = {}; 35 | 36 | for (var key in params) { 37 | if (has.call(params, key)) { 38 | if (key.substr(0, 4) === 'utm_') { 39 | param = key.substr(4); 40 | if (param === 'campaign') param = 'name'; 41 | results[param] = params[key]; 42 | } 43 | } 44 | } 45 | 46 | return results; 47 | } 48 | 49 | var allowedKeys = { 50 | name: true, 51 | term: true, 52 | source: true, 53 | medium: true, 54 | content: true 55 | }; 56 | 57 | /** 58 | * Get strict utm params - from the given `querystring` 59 | * 60 | * @param {String} query 61 | * @return {Object} 62 | * @api private 63 | */ 64 | 65 | function strict(query) { 66 | return foldl(function(acc, val, key) { 67 | if (has.call(allowedKeys, key)) acc[key] = val; 68 | return acc; 69 | }, {}, utm(query)); 70 | } 71 | 72 | /* 73 | * Exports. 74 | */ 75 | 76 | module.exports = utm; 77 | module.exports.strict = strict; 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@segment/utm-params", 3 | "version": "2.0.0", 4 | "description": "Parse a URL, returning all UTM parameters.", 5 | "keywords": [ 6 | "date", 7 | "utm-params" 8 | ], 9 | "main": "lib/index.js", 10 | "scripts": { 11 | "test": "make test" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/segmentio/utm-params" 16 | }, 17 | "author": "Segment ", 18 | "license": "SEE LICENSE IN LICENSE", 19 | "bugs": { 20 | "url": "https://github.com/segmentio/utm-params/issues" 21 | }, 22 | "homepage": "https://github.com/segmentio/utm-params#readme", 23 | "dependencies": { 24 | "@ndhoule/foldl": "^2.0.1", 25 | "component-querystring": "^2.0.0" 26 | }, 27 | "devDependencies": { 28 | "@segment/eslint-config": "^3.1.1", 29 | "browserify": "^13.0.0", 30 | "browserify-istanbul": "^2.0.0", 31 | "component-each": "^0.2.6", 32 | "eslint": "^2.9.0", 33 | "eslint-plugin-mocha": "^2.2.0", 34 | "eslint-plugin-require-path-exists": "^1.1.5", 35 | "istanbul": "^0.4.3", 36 | "karma": "^0.13.22", 37 | "karma-browserify": "^5.0.4", 38 | "karma-chrome-launcher": "^1.0.1", 39 | "karma-coverage": "^1.0.0", 40 | "karma-mocha": "^1.0.1", 41 | "karma-phantomjs-launcher": "^1.0.0", 42 | "karma-sauce-launcher": "^1.0.0", 43 | "karma-spec-reporter": "0.0.26", 44 | "mocha": "^2.2.5", 45 | "phantomjs-prebuilt": "^2.1.7", 46 | "proclaim": "^3.4.1", 47 | "watchify": "^3.7.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@segment/eslint-config/mocha" 3 | } 4 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('proclaim'); 4 | var utm = require('../lib'); 5 | 6 | /** 7 | * Tests. 8 | */ 9 | 10 | describe('utm-params', function() { 11 | it('should parse utm params', function() { 12 | var params = utm('?utm_source=source&utm_medium=medium&utm_term=term&utm_content=content&utm_campaign=name&utm_test=test&utm_fake=fake'); 13 | 14 | assert.deepEqual(params, { 15 | content: 'content', 16 | medium: 'medium', 17 | name: 'name', 18 | source: 'source', 19 | term: 'term', 20 | fake: 'fake', 21 | test: 'test' 22 | }); 23 | }); 24 | 25 | it('should ignore parameters that don\'t begin with `utm_`', function() { 26 | var params = utm('xyz=abc&utm_woot=woot&bar=foo&abc_utm_def=hi'); 27 | 28 | assert.deepEqual(params, { woot: 'woot' }); 29 | }); 30 | 31 | it('should rename `utm_campaign` to `name`', function() { 32 | var params = utm('?utm_campaign=foo'); 33 | 34 | assert.deepEqual(params, { name: 'foo' }); 35 | }); 36 | 37 | it('should replace "?" with "&"', function() { 38 | var params = utm('?utm_campaign=foo?utm_source=baz'); 39 | 40 | assert.deepEqual(params, { name: 'foo', source: 'baz' }); 41 | }); 42 | }); 43 | 44 | describe('utm-params.strict', function() { 45 | it('should omit unspecced utm params', function() { 46 | var params = utm.strict('?utm_source=source&utm_medium=medium&utm_term=term&utm_content=content&utm_campaign=name&utm_test=test&utm_fake=fake'); 47 | assert.deepEqual(params, { 48 | content: 'content', 49 | medium: 'medium', 50 | name: 'name', 51 | source: 'source', 52 | term: 'term' 53 | }); 54 | }); 55 | }); 56 | --------------------------------------------------------------------------------