New upstream version 12.9.2+dfsg

This commit is contained in:
Pirate Praveen 2020-04-13 14:06:41 +05:30
parent b0a17338f5
commit c5badf79b1
76 changed files with 18439 additions and 0 deletions

View file

@ -0,0 +1,10 @@
{
"presets": [["@babel/preset-env", {
"targets": {
"chrome": 32,
"ie": 9,
"firefox": 27,
"safari": 8
}
}]]
}

34
snowplow-javascript-tracker/.gitignore vendored Normal file
View file

@ -0,0 +1,34 @@
# Tests suite
ngrok
# node.js
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
npm-debug.log
node_modules
**/lodash.js
tests/pages/*.js
tests/pages/integration.html
tests/local/serve/
tags/tag.min.js
# creds for Grunt
aws.json
# Vagrant
.vagrant
VERSION
src/js/lib_managed

View file

@ -0,0 +1,58 @@
node_js:
- 8.12.0
language: node_js
install:
- npm install -g npm@6.4.1
- npm install -g grunt-cli
# install packages and build core
- cd core
- rm -rf node_modules
- npm install
- grunt ts
- grunt dtsGenerator
# install packages for tracker
- cd ..
- rm -rf node_modules
- npm install
# get ngrok
- wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
- unzip ./ngrok-stable-linux-amd64.zip
before_script:
- export SUBDOMAIN=$RANDOM$RANDOM
- ./ngrok http -authtoken $NGROK_AUTH -subdomain $SUBDOMAIN -log=stdout 8500 > /dev/null &
script:
- cd core && grunt intern && cd ..
- grunt travis
deploy:
- provider: script
skip_cleanup: true
script: ./.travis/deploy.py
on:
condition: '"$(.travis/is_core_release_tag.sh $TRAVIS_TAG)" == "" && $? == 0'
tags: true
- provider: script
skip_cleanup: true
script: ./.travis/deploy.py
on:
condition: '"$(.travis/is_tracker_release_tag.sh $TRAVIS_TAG)" == "" && $? == 0'
tags: true
env:
global:
# SAUCE_USERNAME
- secure: S/6Ild1n9SmW8xYqarjkigDqmyFgWS4Hi0M07e/ryPeS1MNQdiS5+WuSmlSTevM/wU/oSCvzXnKhO9W/1tvz4qj/BMQYxYWFjwWAXJrkjJPvWoa5ogYkR3ETSYrqdhkT+8LVof8iLPlpHjS1y2fozyJbYf1YNSkcVCLxMdIwnk0=
# SAUCE_AUTH_KEY
- secure: bkmtL2PXFGdOB6YNfWw6thEyVAbX/l7Q99pfT2jHmfzhTDf4/FT1CprsAacf0JeYkwMg5UQDMqAcZSojjO6GeAY8rhC94Tor5hKoEwCL4wg5NatXBUUI+WWaoBSg2KWYG2MfkPxtGJyNk1LPQoV+nUUzwEGejV8lJvaHVxkBsqA=
# NGROK_AUTH
- secure: JwP5MuqZ6k8FlHg8AnzYTrCZByJLnlu0to2srEwfVOhW1efGTEmtWf/SLlplYHcdelsFakdiSbD3BkZyEqpe/2/AIb56KLAqtE2Ng3WS8Vz/80yknxII0xcSPzAZdhZYRUJlbIrA1Ua3XGgirBenjn7ILFXNX/qLgvwJ1uLKCPo=
# AWS_ACCESS_KEY
- secure: AEvIKW1H/DPU5yPKfRPHY9XpBCjXGtwVqpQERx0wYGwV8j9mzz8EMMrx6yPMaLOkeBMcaH/db3aXEePHWAlnxrTuUuhwwr9A7sihaaAwZ9GllUlUEJzfXeBcCM9rhsooY2roMzJM3Ener1W+yKHThagYxUZ4PyZrPJCMMzbc0qw=
# AWS_SECRET_KEY
- secure: C+PTon3PsELG2bFlAZ5qfinWVih8XxlZV45Gk2W5i2CK1AROJ60lnfhgYZM4YidIRcA/V/zbm8tDgZbA8i/MVKcO5f1qbD5Skyo2fEFRlXoSWhtwaScViihN338U/SsEviKThIGSSLgdfsQ0CW7TYxvetFSwXFyZPPrYrenF3dk=
# NPM_AUTH_TOKEN
- secure: Dtc3aFnUfquuC+E3ZOvs57SZy3Sp+3QdXQHR0xjekvpF4FgaKvDpISBfLTCs1ODuHZ+K1pPn9/i4yGNE7ei8z3VTbRY4nYPM5VG7jlqwP1iaFqRopnQmpV9c7yhMSHKhXrSWj/Gwo/9UHpia6muQUSK4hbPvhZMf0Rd3s06Z07M=
- AWS_S3_BUCKET=snowplow-static-js
- AWS_REGION=eu-west-1
- AWS_CF_DISTRIBUTION=E1RICD7QD0F2KE

View file

@ -0,0 +1,174 @@
#!/usr/bin/env python
"""
Script responsible for deploying both snowplow-tracker-core and snowplow-tracker
Need to be executed in Travis CI environment by tag (core/x.y.z OR x.y.z)
Publish core locally, build tracker using locally publiched core and then
publish altogether
"""
from contextlib import contextmanager
import os
import sys
import subprocess
# Initial setup
if 'TRAVIS_TAG' in os.environ:
TRAVIS_TAG = os.environ.get('TRAVIS_TAG')
else:
sys.exit("Environment variable TRAVIS_TAG is unavailable")
if 'TRAVIS_BUILD_DIR' in os.environ:
TRAVIS_BUILD_DIR = os.environ.get('TRAVIS_BUILD_DIR')
else:
sys.exit("Environment variable TRAVIS_BUILD_DIR is unavailable")
if 'NPM_AUTH_TOKEN' in os.environ:
NPM_AUTH_TOKEN = os.environ.get('NPM_AUTH_TOKEN')
else:
sys.exit("Environment variable NPM_AUTH_TOKEN is unavailable")
if TRAVIS_TAG.startswith('core/'):
PROJECT = 'core'
VERSION = TRAVIS_TAG[5:]
else:
PROJECT = 'tracker'
VERSION = TRAVIS_TAG
if 'AWS_ACCESS_KEY' in os.environ:
AWS_ACCESS_KEY = os.environ.get('AWS_ACCESS_KEY')
else:
sys.exit("Environment variable AWS_ACCESS_KEY is unavailable (required for tracker publishing)")
if 'AWS_SECRET_KEY' in os.environ:
AWS_SECRET_KEY = os.environ.get('AWS_SECRET_KEY')
else:
sys.exit("Environment variable AWS_SECRET_KEY is unavailable (required for tracker publishing)")
if 'AWS_S3_BUCKET' in os.environ:
AWS_S3_BUCKET = os.environ.get('AWS_S3_BUCKET')
else:
sys.exit("Environment variable AWS_S3_BUCKET is unavailable (required for tracker publishing)")
if 'AWS_REGION' in os.environ:
AWS_REGION = os.environ.get('AWS_REGION')
else:
sys.exit("Environment variable AWS_REGION is unavailable (required for tracker publishing)")
if 'AWS_CF_DISTRIBUTION' in os.environ:
AWS_CF_DISTRIBUTION = os.environ.get('AWS_CF_DISTRIBUTION')
else:
sys.exit("Environment variable AWS_CF_DISTRIBUTION is unavailable (required for tracker publishing)")
# Helper functions
def output_if_error(sbt_output):
"""Callback to print stderr and fail deploy if exit status not successful"""
(stdout, stderr) = sbt_output.communicate()
if sbt_output.returncode != 0:
print("Process has been failed.\n" + stdout)
sys.exit(stderr)
def execute(command, callback=output_if_error):
"""Execute shell command with optional callback"""
formatted_command = " ".join(command) if (type(command) == list) else command
print("Executing [{0}]".format(formatted_command))
output = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if hasattr(callback, '__call__'):
return callback(output)
else:
return output
def check_version():
"""Fail deploy if tag version doesn't match SBT version"""
if PROJECT == 'core':
get_version = """var fs=require('fs'); fs.readFile('./core/package.json', 'utf8', function(e,d) { console.log(JSON.parse(d)['version']) });"""
elif PROJECT == 'tracker':
get_version = """var fs=require('fs'); fs.readFile('./package.json', 'utf8', function(e,d) { console.log(JSON.parse(d)['version']) });"""
else:
sys.exit("Unknown project " + str(PROJECT))
node_output = execute(['node', '-e', get_version], None)
print(node_output.stderr.read())
for line in node_output.stdout.read().split("\n"):
print(line)
if line:
if line != VERSION:
sys.exit("Version extracted from TRAVIS_TAG [{0}] doesn't conform declared in package.json [{1}]".format(VERSION, line))
else:
return
sys.exit("Cannot find version in core output:\n" + str(node_output))
@contextmanager
def npm_credentials():
"""Context manager allowing to use different credentials and delete them after use"""
npmrc = os.path.expanduser("~/.npmrc")
if os.path.isfile(npmrc):
os.remove(npmrc)
print("WARNING! ~/.npmrc already exists. It should be deleted after each use")
print("Overwriting existing ~/.npmrc")
else:
print("Creating ~/.npmrc")
with open(npmrc, 'a') as f:
f.write("registry=https://registry.npmjs.org/\n//registry.npmjs.org/:_authToken=" + NPM_AUTH_TOKEN)
yield
print("Deleting ~/.npmrc")
os.remove(npmrc)
@contextmanager
def aws_credentials():
"""Context manager allowing to use different credentials and delete them after use"""
awsjson = os.path.join(TRAVIS_BUILD_DIR, "aws.json")
if os.path.isfile(awsjson):
sys.exit("aws.json already exists. It should be deleted after each use")
else:
print("Creating aws.json")
with open(awsjson, 'a') as f:
f.write(
'{"key":"%(key)s","secret":"%(secret)s","bucket":"%(bucket)s","region":"%(region)s", "distribution":"%(distribution)s"}' % \
{'key': AWS_ACCESS_KEY, 'secret': AWS_SECRET_KEY, 'bucket': AWS_S3_BUCKET, 'distribution': AWS_CF_DISTRIBUTION, 'region': AWS_REGION}
)
yield
print("Deleting aws.json")
os.remove(awsjson)
def publish_core():
os.chdir(os.path.join(TRAVIS_BUILD_DIR, "core"))
with npm_credentials():
execute(['npm', 'publish'])
def publish_tracker():
os.chdir(TRAVIS_BUILD_DIR)
with aws_credentials():
execute(['grunt', 'publish'])
def publish():
if PROJECT == 'core':
publish_core()
elif PROJECT == 'tracker':
publish_tracker()
else:
sys.exit("Unknown project " + str(PROJECT))
if __name__ == "__main__":
# Publish locally all dependencies
check_version()
publish()

View file

@ -0,0 +1,15 @@
#!/bin/bash
tag=$1
cicd=${tag:0:4}
release=${tag:5}
if [ "${cicd}" == "core" ]; then
if [ "${release}" == "" ]; then
echo "Warning! No release specified! Ignoring."
exit 2
fi
exit 0
else
exit 1
fi

View file

@ -0,0 +1,14 @@
#!/bin/bash
tag=$1
release=$tag
if [ "${release}" == "" ]; then
echo "Warning! No release specified! Ignoring."
exit 2
else
if [ "${release:0:4}" == "core" ]; then
echo "Core relase, not tracker"
fi
fi
exit 0

View file

@ -0,0 +1,548 @@
Version 2.10.0 (2019-01-17)
--------------------------
Transpile helpers.js and detectors.js (#693)
Allow dynamic context callbacks for link and form tracking (#585)
Fix default configOptOutCookie value (#672)
Remove outdated addClickListener method (#667)
Error-handling for tracker methods (#675)
Beacon API option for sending events to collector (#674)
Tracking click events on forms (#579)
Update tracker script banner (#684)
Add new local testing workflow (#686)
Clean up indentation of integration test template (#691)
Update outdated dependencies (#685)
Fix typo in sesname variable (#671)
Add global contexts features (#405)
Add Babel to build process (#665)
Replace YUI Compressor with UglifyJS (#687)
Fix log output for failed integration tests (#689)
Use modularized imports for lodash (#502)
Update npm steps in .travis.yml (#690)
Consolidate request method API (#700)
Fix Beacon API support (#702)
Version 2.9.3 (2019-01-09)
--------------------------
Add option to set a custom POST request path (#696)
Refresh npm authentication token (#688)
Version 2.9.2 (2018-07-24)
--------------------------
Remove wheel and passive event listener feature flags (#661)
Version 2.9.1 (2018-06-20)
--------------------------
Keep node type definitions at version 9.6.7 (#649)
Update page ping context on every call to trackPageView (#612)
Core: fix type incompatibility in consent methods (#652)
Added src/js/lib_managed to .gitignore (#650)
Use passive event listeners for mouse wheel event (#478)
Check if browser has full support of Performance Timing API (#539)
Add Java 10 to Ansible playbooks (#657)
Version 2.9.0 (2018-02-27)
--------------------------
Add ability to change field content before sending form tracking (#465)
Add a method to start a new session (#515)
Make newDocumentTitle variable local (#580)
Enforce that geolocation.timestamp is an integer (#602)
Remove respectOptOutCookie from the Tracker function comments (#605)
Add jsDelivr hits badge (#611)
Add identifyUser as alias for setUserId (#621)
Add trackConsentGranted method (#623)
Add trackConsentWithdrawn method (#624)
Bump semver to 4.3.2 (#625)
LocalStorage domain user ID is not persisted properly (#627)
Core: add trackConsentGranted method (#629)
Core: add trackConsentWithdrawn method (#630)
Update OS X test targets for Saucelabs (#632)
Install dependencies and update shrinkwrap before deployment (#633)
Version 2.8.2 (2017-08-21)
--------------------------
Fix opt-out cookie check (#604)
Version 2.8.1 (2017-07-26)
--------------------------
Use trackCallback for all events (#589)
Fix grunt ts in CI/CD (#598)
Fix tech docs icon (#596)
Fix string ids in Optimizely Summary context (#591)
Fix race condition with pageViewId creation (#592)
Version 2.8.0 (2017-05-18)
--------------------------
Add support for users opting out of state tracking using cookie or localStorage (#459)
Add support for recording state in localStorage rather than cookies (#506)
Exclude password fields from form tracking (#521)
Add Parrable context (#525)
Add support for OptimizelyX context (#551)
Update README markdown in according with CommonMark (#561)
Add support for not recording any state (#563)
Deprecate useCookies (#565)
Deprecate useLocalStorage (#566)
Prevent multiple setInterval from being created (#571)
Guard against non-integer values for minimumVisitLength and heartBeatDelay (#572)
Provide read-only access to domainSessionIndex (#573)
Provide read-only access to cookieName (#574)
Provide read-only access to pageViewId (#575)
Version 2.7.2 (2017-03-07)
--------------------------
Add defensive check for window.optimizely.data in getOptimizelyStateContexts (#555)
Version 2.7.1 (2017-03-06)
--------------------------
Add defensive check for window.optimizely.data (#553)
Version 2.7.0 (2017-01-09)
--------------------------
Add CI/CD (#529)
Add ngrok credentials (#527)
Add Sauce Labs credentials (#528)
Add npm credentials to .travis.yml (#536)
Add AWS credentials to .travis.yml (#537)
Remove random upload path (#538)
Add an option to change life of the visitor cookie, or disable, on tracker creation (#504)
Make tracker Content Security Policy (CSP) compliant (#490)
Add Optimizely summary context (#466)
Add the option to regenerate the page view ID with each page view event (#436)
Add automatic & manual tracking of JS errors to JavaScript tracker (#16)
Get Code Climate badge to green (#152)
Add ability to set true timestamp (#484)
Add timestamp argument for tracking methods (#532)
Remove deprecated visibility state check (#470)
Update Selenium version to 2.48.0 (#487)
Add date for release 2.6.2 to CHANGELOG (#520)
Add trackSelfDescribingEvent method (#471)
Fix integration tests suite (#523)
Bump Core version to 0.5.0 (#301)
Restrict package versions (#522)
Core: only send custom contexts if non-empty array (#540)
Core: add support of true timestamp (#531)
Core: add trackSelfDescribing method (#533)
Core: port to TypeScript (#526)
Version 2.6.2 (2016-07-14)
--------------------------
Add date for release 2.6.1 to CHANGELOG (#495)
Don't send invalid viewport dimensions (#488)
Check whether elements of navigator.plugins array are undefined (#492)
Only call navigator.javaEnabled on instances of Navigator (#491)
Stop using String.startsWith (#493)
Ensure page view ID is passed to synchronous tracker (#485)
Version 2.6.1 (2016-04-14)
--------------------------
Prevent CSS class listing from failing for elements with no CSS classes (#473)
Explicitly set suites field in Intern config (#475)
Version 2.6.0 (2016-03-03)
--------------------------
Bumped ngrok version used in CI to 2.x (#460)
Allowed random upload path in grunt task (#461)
Ensured that PerformanceTiming context doesn't contain properties inherited from Object.prototype (#458)
Added `forceUnsecureTracker` Tracker argument, thanks @bloodyowl! (#374)
Added subset of Augur data as a new context (#386)
Added deprecation warning to setSessionCookieTimeout (#394)
Added setting to automatically use top-level domain for duid (#409)
Added Optimizely contexts (#448)
Added trackEnhancedEcommerceAction() method (#452)
Added addEnhancedEcommerceActionContext() method (#453)
Added addEnhancedEcommerceImpressionContext() method (#454)
Added addEnhancedEcommerceProductContext() method (#455)
Added addEnhancedEcommercePromoContext() method (#456)
Made domainUserId a UUID (#274)
Attached device sent timestamp (stm) to events at last possible moment (#355)
Attempting to create a new tracker using an existing namespace should do nothing (#411)
Using a different library to publish to S3 (#422)
Prevented error running grunt-cloudfront (#426)
Respected doNotTrack in IE 11 and Safari 7.1.3+, thanks @grzegorzewald! (#440)
Version 2.5.3 (2015-11-10)
--------------------------
Bumped Node version to 4.1.2 in .travis.yml (#420)
Bumped Intern version to 3.0.6 (#370)
Bumped version of temporary to 0.0.8 (#425)
Bumped grunt-yui-compressor to 0.4.0 (#424)
Bumped grunt-browserify to 3.28.1 (#427)
Fixed jstimezonedetect version at 1.0.5 (#429)
Removed Browserify from devDependencies (#428)
Made extraction of DOM element classes compatible with IE9 (#418)
Stopped dereferencing undefined nodes when setting up form tracking (#423)
Version 2.5.2 (2015-08-13)
--------------------------
Prevented the tracker from setting cookies on initialization if "useCookies" is disabled (#403)
Remove tests for unsupported environments (#406)
Version 2.5.1 (2015-07-27)
--------------------------
Fixed prerender detection (#391)
Made page title tracking dynamic (#392)
Added warning about using a file URL to example pages (#397)
Version 2.5.0 (2015-07-22)
--------------------------
Generated a unique session ID for each new session (#347)
Added a page view UUID (#369)
Maintained visit count when cookies are disabled (#388)
Bumped payload_data schema to 1-0-3 (#385)
Added Grunt task to build the tracker skipping the lodash and minification tasks (#382)
Added ability to configure the session cookie timeout in the argmap (#383)
Removed deprecated performanceTiming argument to trackPageView (#375)
Added ability to pass a context-generating function to trackPageView (#372)
Removed configWriteCookies setting (#390)
Updated browser feature detection tests (#378)
Version 2.4.3 (2015-04-15)
--------------------------
Added License button to README (#357)
Set the ID cookie as soon as the tracker loads (#358)
Updated the session count as soon as the tracker loads (#361)
Made single events exceeding the maximum POST request size attempt to fire exactly once (#359)
Fixed querystring decoration for links with inner elements (#360)
Version 2.4.2 (2015-04-07)
--------------------------
Set a maximum size for POST requests (#353)
Fixed QuotaExceededError bug (#352)
Version 2.4.1 (2015-03-27)
--------------------------
Counted any 2xx or 3xx collector response to a POST request as successful (#343)
Counted any 4xx or 5xx collector response to a POST request as failed (#344)
Prevented the localStorage event buffer from being flushed more than once simultaneously (#345)
Cancelled the XMLHttpRequest timeout callback when the request fails (#348)
Stopped adding null PerformanceTiming context (#354)
Version 2.4.0 (2015-03-16)
--------------------------
Added ability to modify links allowing cross-domain tracking (#109)
Added timing event (#320)
Increased safety of document size detection (#334)
Started randomly generating ngrok subdomain in integration tests (#333)
Fixed Vagrant setup to use latest Peru version (#336)
Stopped caching page URL and referrer URL (#337)
Stopped caching PerformanceTiming context (#339)
Added common contexts to link_click, change_form, and submit_form events (#340)
Version 2.3.0 (2015-03-03)
-------------------------
Added support for sending events via POST (#168)
Removed dependency on fblundun fork of grunt-yui-compressor (#172)
Added support for batching events (#186)
Started sending PerformanceTiming context with all events (#317)
Added ability to send geolocation context with all events (#191)
Added ability to send context containing all GA cookies with all events (#253)
Improved document height detection (#236)
Added integration tests (#154)
Added functional tests for document size detection and browser features (#270)
Added ability to whitelist or blacklist specific forms and specific form fields (#287)
Added dedicated Vagrant setup (#312)
Added Vagrant "push core" to build and publish Tracker Core (#315)
Added Vagrant "push tracker" to build and deploy JavaScript Tracker (#313)
Renamed deploy to dist (#319)
Stopped sending NaN for page scroll offsets (#324)
Version 2.2.2 (2015-03-03)
--------------------------
Corrected time at which minimum and maximum scroll offsets are reset (#325)
Version 2.2.1 (2015-01-28)
--------------------------
Fixed QuotaExceededError bug with localStorage in Safari (#308)
Stopped sending empty PerformanceTiming context (#306)
Prevented PerformanceTiming context being sent multiple times (#309)
Stopped automatically setting Cloudfront URL in synchronous tracker, thanks @vassilevsky! (#311)
Fixed lodash-cli version at 2.4.2 (#314)
Prevented accidental publication of snowplow-tracker to npm (#300)
Added missing tid ticket to CHANGELOG under 2.1.0 (#302)
Version 2.2.0 (2014-12-15)
--------------------------
Made trackerDictionary object available in onload callbacks, thanks @murphybob! (#294)
Ensured all page offsets are integers (#291)
Added public method to get duid (#289)
Added public method to get user fingerprint (#288)
Added bundle.js to deploy/.gitignore (#281)
Started using grunt-cloudfront for cache invalidation (#276)
Added ability to disable use of localStorage (#181)
Added ability to disable cookies (#140)
URL encoded custom contexts if base 64 encoding is disabled (#299)
Version 2.1.2 (2014-11-15)
--------------------------
Removed requestEnd field from PerformanceTiming context (#285)
Version 2.1.1 (2014-11-06)
--------------------------
Rounded chromeFirstPaint field to an integer (#282)
Version 2.1.0 (2014-11-05)
--------------------------
Added automated form submission tracking (#252)
Stopped outbound queue from triggering multiple times per event (#251)
Added PerformanceTiming context using HTML5 Web Performance API (#248)
Added ability to execute a custom callback once sp.js is loaded (#246)
Added internal site search event (#241)
Started using grunt-cloudfront-clear for CloudFront cache invalidation (#230)
Renamed /dist to /deploy (#216)
Moved context querystring to end of JS-generated beacons (#204)
Added guard to wait until outbound queue is empty before unloading (#202)
Added event_id generation (#190)
Stopped sending tid (#218)
Added content field to link click events (#187)
Replaced "Getting started" with Vagrant-using "Contributing quickstart" (#169)
Added async-large.html (#162)
Improved F rating for tracker.js in CodeClimate (#150)
Added trackAddToCart and trackRemoveFromCart events (#97)
Added further Intern unit tests (#76)
Added social tracking features (#12)
Improved efficiency of enableLinkClickTracking (#254)
Integrated the Tracker Core (#255)
Removed deprecated trackImpression method (#256)
Added forceSecureTracker boolean option to the argmap, thanks @kujo4pmZ! (#247)
Moved link click tracking into its own file (#266)
Made IP address regex more strict (#267)
Updated expected browser fingerprints in functional detectors test (#275)
Added check to ensure outQueue is an array, thanks @kevinsimper! (#277)
Version 2.0.2 (2014-10-20)
--------------------------
Changed default configCookiePath to "/" (#250)
Version 2.0.1 (2014-10-12)
--------------------------
Made error logging compatible with Internet Explorer (#264)
Fixed SauceLabs red status (#235)
Version 2.0.0 (2014-07-03)
--------------------------
Moved fixUpUrl into its own file, called lib/proxies.js (#112)
Fixed duplication of querystring parameter lookup (#111)
Added tests for helpers.js (#96)
Added tests for detectors.js (#95)
Replaced cookie.js with browser-cookie-lite (#88)
Added ad conversion tracking (#60)
Added ad click tracking (#59)
Added initial localStorage support for intermittent offline beacons, thanks @rcs! (#24)
Added new trackAdImpression, mapping to unstructured event (#13)
Removed references to referral cookie (#118)
Implemented enableLinkTracking support (#51)
Replaced hard-coded version with template value (#120)
Added Sauce Labs small button at top of README (#123)
Added Sauce full test summary widget (long bar) at bottom of README (#124)
Added support for namespacing (#4)
Passed tracker namespace through to collector in Tracker Protocol (#126)
Moved to argmap-style tracker creation with 'newTracker' (#132)
Added support for cookie namespacing (#131)
Added new tag which allows queue to be renamed (#130)
Started rigorously checking whether a page is cached by Yahoo (#142)
Upgraded Intern to 1.5.0 (#119)
Fixed link to code climate button in README.md (#149)
Added examples of tracker namespacing (#159)
Split async.html into async-small.html, async-medium.html (#160)
Linked the Technical Docs and Setup Guide images to the appropriate pages (#164)
Made JS invocation tag part of the build process (#158)
Fixed warnings generated by the Closure Compiler, thanks @steve-gh! (#170)
Added untracked files which should be ignored to .gitignore (#173)
Removed ads/sync.html (#182)
Updated ads/async.html (#183)
Added pageUnloadTimer option to argmap (#171)
Removed type hints from unstructured events and custom contexts (#163)
Added hardcoded schema to custom context arrays (#199)
Added hardcoded schema to unstructured events (#196)
Changed trackUnstructEvent to take a JSON containing schema and data fields (#197)
Version 1.0.3 (2014-06-27)
--------------------------
Changed Base64 encoding function to prevent character encoding errors, thanks @shermozle! (#231)
Version 1.0.2 (2014-06-24)
--------------------------
Added guard to prevent document size field from being set as "NaNxNaN" (#220)
Fixed Grunt publish tasks to build sp.js as well as upload it to S3 (#224)
Added cache control to Grunt upload for full semantic version (#225)
Version 1.0.1 (2014-04-09)
--------------------------
Fixed lodash.js to work in the presence of AMD modules (#165)
Added missing variable declarations (#166)
Version 1.0.0 (2014-03-27)
--------------------------
Added extra meta-data to package.json (#83)
Moved part of banner.js into Gruntfile with grunt-concat's banner option so its values are based on package.json (#82)
Started using Browserify for modules (#74)
Replaced some/all of lib/.js with modules (#7)
Added user fingerprinting on/off switch and configurable hash seed (#7)
Deprecated trackImpression (#66)
Removed attachUserId as fully deprecated now (#64)
Removed setSiteId as fully deprecated now (#63)
Removed getVisitor-Id, -Info as fully deprecated now (#62)
Removed trackEvent as fully deprecated now (#61)
Tightened public API for SnowPlow (#29)
Renamed SnowPlow everywhere to Snowplow (#69)
Prepended window. or SnowPlow.windowAlias. onto _snaq everywhere (#39)
Removed legacy Piwik plugin framework (#56)
Moved hasSessionStorage and hasLocalStorage into detectors.js (#91)
Wrote tests for AsyncQueueProxy (#100)
Added Travis CI to the project (#103)
Added a built with Grunt button to the README (#102)
Added codeclimate button to README (#137)
Added named Grunt tasks (#86)
Added Intern unit tests for payload.js (#5)
Replaced all functions in identifiers.js which are directly available from lodash (#85)
Moved functions from identifers.js into payload.js and wrote Intern tests for them (#108)
Added getting started info for developers to README, thanks @pkallos! (#129)
Version 0.14.1 (2014-03-12)
---------------------------
Fixed bug where fromQuerystring was matching fragments instead of just the querystring (#116)
Version 0.14.0 (2014-02-12)
---------------------------
Bumped version to 0.14.0
Removed all DEBUG blocks from codebase (#65)
Renamed requestStringBuilder to payloadBuilder and moved it into its own file, payload.js (#55)
Introduced gzipped sp.js library (#48)
Updated grunt and intern dependencies (#54)
Replaced snowpak.sh with Grunt and grunt-yui-compressor (#53)
Added setUserIdFromReferrer and setUserIdFromLocation (#57)
Added ability to pass a referrer to Snowplow from an IFRAME (#1)
Tested setDoNotTrack and renamed it to respectDoNotTrack (#28)
Moved detect...() functions into new file context.js (#37)
Moved cookie-related functionality into new file cookie.js (#77)
Removed getLegacyCookieName as no longer needed for migrating cookie IDs (#50)
Switched deployment to use Grunt (#58)
Added setUserIdFromCookie (#78)
Version 0.13.1 (2014-01-28)
---------------------------
Fixed bug where non-String values are not being added to our payload (#71)
Version 0.13.0 (2014-01-26)
---------------------------
Added fully retrospective CHANGELOG (#20)
Added setPlatform support, thanks @rcs! (#25)
Added currency field to ecommerce transactions (#34)
Added custom unstructured contexts (#49)
Added base64decode to Tracker (#36)
Added null check to requestStringBuilder() (#40)
Added array helpers (#41)
Fixed (harmless) bug in base64.js (#35)
Update .gitignore to be node-friendly .gitignore (#52)
Switched to Semantic versioning & only put MAJOR version in hosted path to snowplow.js (#47)
Added package.json (#38)
Added retrospective tags back in (#22)
Restructured folders (#21)
Version 0.12.0 (2013-07-07)
---------------------------
Fixed document reference to use documentAlias (#247)
Fixed bug with setCustomUrl (#267)
Changed ev_ to se_ for structured events (#197)
Fixed Firefox failure when "Always ask" set for cookies (#163)
Fixed bug in page ping functionality detected in IE 8 (#260)
Replaced forEach as not supported in IE 6-8 (#295)
Version 0.11.2 (2013-05-14)
---------------------------
Added unstructured events, thanks @rgabo, @tarsolya, @lackac (#198)
Remove leading ampersand in querystring (#188)
Version 0.11.1 (2013-02-25)
---------------------------
Fixed bug with cookie secure flag killing user ID cookies (#181)
Version 0.11.0 (2013-02-22)
---------------------------
Introduced setAppId() and deprecated setSiteId() (#168)
1st party user ID now transmitted as duid (domain uid) (part of #150)
Now sends dtm - the client timestamp (#149)
Deprecated and disabled attachUserId()
Deprecated getVisitorId() and getVisitorInfo() - use getDomainUserId() and getDomainUserInfo() instead
Added setUserId which sets the uid field (#167)
Snowplow cookies no longer tied to site ID (#148)
Version 0.10.0 (2013-02-15)
---------------------------
Updated copyright notices
Removed deprecated setAccount(), setTracker(), setHeartBeatTimer() - BREAKING CHANGE (#86)
Added document charset to querystring (#138)
Page ping no longer killed by 1 heartbeat w/o activity (#132)
Added document & viewport dimensions (#94)
Introduced trackStructEvent and deprecated trackEvent (#143)
Cleaned up getRequest code to use improved requestStringBuilder
Fixed logImpression (was using wrong argument names) (#162)
Added scroll offsets to page ping (#127)
Version 0.9.1 (2013-01-29)
--------------------------
Fixed bug where secure flag not being set on cookies sent via HTTPS
Version 0.9.0 (2012-12-26)
--------------------------
Each event now sent with an event type `e` (#63)
Refactoring of event definition code
Added attachUserId(boolean) method (#92)
Removed configCustomData from logImpression (#115)
Cleaned up activity tracking (page pings)
Added a combine only option to snowpak.sh
Version 0.8.2 (2012-12-18)
--------------------------
Fixed regressions from splitting JS into multiple files (#103)
Version 0.8.1 (2012-11-29)
--------------------------
Fixed bug with trailing comma (#102)
Removed console.log when not debugging (#101)
Removed minified sp.js from version control (added .gitignore to keep it out)
Version 0.8.0 (2012-11-28)
--------------------------
Rename ice.png to i - BREAKING CHANGE (#29)
Added setCollectorCf() and deprecated setAccount() (#32)
Tracker constructor now supports Cf or Url (part of #44)
getTrackerCf() and -Url() added, getTracker() deprecated (part of #44)
Added tracker version (`tv`) to querystring (#41)
Added color depth tracking (part of #69)
Added timezone tracking (part of #69)
Added user fingerprinting (#70)
Broke out .js into multiple files (#55)
Version 0.7.0 (2012-10-01)
--------------------------
Renamed said to aid for application ID
Version 0.6 (2012-09-05)
------------------------
Added setSiteId functionality
Added ecommerce tracking
Version 0.5 (2012-08-18)
--------------------------
Changed header comments from Doxygen format to JsDoc
Added support for specifying collectorUrl directly
Added versioning into header comment (so survives minification)
Took f_ off res and cookie; added url onto end, renamed rdm to tid
Version 0.4 (2012-05-30)
------------------------
Improved names of querystring params
Added page-url to querystring as fallback
Version 0.3 (2012-05-18)
------------------------
Updated to prepend f_ to browser features
Revised the querystring name-value pairs to make them more user-friendly
Version 0.2 (2012-05-08)
------------------------
Formalised minification process
Version 0.1 (2012-03-21)
------------------------
Initial release

View file

@ -0,0 +1,279 @@
/*
* JavaScript tracker for Snowplow: Gruntfile.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var semver = require('semver');
/*global module:false*/
module.exports = function(grunt) {
var pkg = grunt.file.readJSON('package.json');
var semVer = semver.parse(pkg.version);
pkg.pinnedVersion = semVer.major;
var banner = "/*\n" +
" * Snowplow - The world's most powerful web analytics platform\n" +
" *\n" +
" * @description <%= pkg.description %>\n" +
" * @version <%= pkg.version %>\n" +
" * @author " + pkg.contributors.join(', ') +"\n" +
" * @copyright Anthon Pang, Snowplow Analytics Ltd\n" +
" * @license <%= pkg.license %>\n" +
" *\n" +
" * For technical documentation:\n" +
" * https://github.com/snowplow/snowplow/wiki/javascript-tracker\n" +
" *\n" +
" * For the setup guide:\n" +
" * https://github.com/snowplow/snowplow/wiki/javascript-tracker-setup\n" +
" *\n" +
" * Minimum supported browsers:\n" +
" * - Firefox 27 \n" +
" * - Chrome 32 \n" +
" * - IE 9 \n" +
" * - Safari 8 \n" +
" */\n";
grunt.initConfig({
banner: banner,
pkg: pkg,
subdomain: process.env.SUBDOMAIN,
browserify: {
main: {
files: {
'dist/bundle.js': ['src/js/init.js']
}
},
test: {
files: {
'tests/pages/helpers.bundle.js': ['tests/scripts/helpers.js'],
'tests/pages/detectors.bundle.js': ['tests/scripts/detectors.js'],
'tests/pages/bundle.js': ['src/js/init.js']
}
}
},
babel: {
options: {
presets: ['@babel/preset-env']
},
dist: {
files: {
'dist/bundle-postbabel.js': 'dist/bundle.js'
}
},
test: {
files: {
'tests/pages/helpers.js': 'tests/pages/helpers.bundle.js',
'tests/pages/detectors.js': 'tests/pages/detectors.bundle.js',
'tests/pages/snowplow.js': 'tests/pages/bundle.js'
}
},
local: {
files: {
'tests/local/serve/snowplow.js': 'tests/pages/bundle.js'
}
}
},
concat: {
deploy: {
options: {
'report': 'gzip',
'process': true
},
src: ['dist/bundle-postbabel.js'],
dest: 'dist/snowplow.js'
},
tag: {
options: {
banner: ';'
},
src: ['tags/tag.min.js'],
dest: 'tags/tag.min.js'
},
test: {
options: {
'process': true
},
src: ['tests/pages/integration-template.html'],
dest: 'tests/pages/integration.html'
},
local: {
options: {
'process': function(src, filepath) {
return src.replace(/'\<\%= subdomain \%\>' \+ '\.ngrok\.io'/g, '\'127.0.0.1:8000\'');
}
},
src: ['tests/pages/integration-template.html'],
dest: 'tests/local/serve/integration.html'
}
},
uglify: {
deploy: {
options: {
'banner': '<%= banner %>'
},
files: {
'dist/sp.js': ['dist/snowplow.js']
}
},
tag: {
files: {
'tags/tag.min.js': ['tags/tag.js']
}
}
},
intern: {
// Common
options: {
config: 'tests/intern.js'
},
nonfunctional: {
options: {
runType: 'client',
suites: [
'tests/nonfunctional/helpers.js',
'tests/nonfunctional/in_queue.js',
'tests/nonfunctional/proxies.js'
]
}
},
functional: {
options: {
runType: 'runner',
functionalSuites: [
'tests/functional/detectors.js',
'tests/functional/helpers.js'
]
}
},
integration: {
options: {
runType: 'runner',
functionalSuites: [
'tests/integration/setup.js', // required prior to integration.js
'tests/integration/integration.js' // request_recorder and ngrok need to be running
]
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-aws');
grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('intern');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-babel');
grunt.registerTask('upload_setup', 'Read aws.json and configure upload tasks', function() {
var aws = grunt.file.readJSON('aws.json');
grunt.config('aws', aws);
grunt.config('s3', {
options: {
accessKeyId: '<%= aws.key %>',
secretAccessKey: '<%= aws.secret %>',
bucket: '<%= aws.bucket %>',
access: 'public-read',
region: '<%= aws.region %>',
gzip: true,
cache: false
},
not_pinned: {
options: {
headers: {
CacheControl: "max-age=315360000"
}
},
files: [
{
src: ["dist/sp.js"],
dest: "<%= pkg.version %>/sp.js"
}
]
},
pinned: {
options: {
headers: {
CacheControl: "max-age=3600"
}
},
files: [
{
src: ["dist/sp.js"],
dest: "<%= pkg.pinnedVersion %>/sp.js"
}
]
}
});
grunt.config('cloudfront', {
options: {
accessKeyId: '<%= aws.key %>',
secretAccessKey: '<%= aws.secret %>',
distributionId: '<%= aws.distribution %>'
},
not_pinned: {
options: {
invalidations: [
'/<%= pkg.version %>/sp.js'
]
}
},
pinned: {
options: {
invalidations: [
'/<%= pkg.pinnedVersion %>/sp.js'
]
}
}
});
});
grunt.registerTask('default', 'Build Browserify, add banner, and minify', ['browserify:main', 'babel:dist', 'concat:deploy', 'uglify:deploy']);
grunt.registerTask('publish', 'Upload to S3 and invalidate Cloudfront (full semantic version only)', ['upload_setup', 'browserify:main', 'babel:dist', 'concat:deploy', 'uglify:deploy', 's3:not_pinned', 'cloudfront:not_pinned']);
grunt.registerTask('publish-pinned', 'Upload to S3 and invalidate Cloudfront (full semantic version and semantic major version)', ['upload_setup', 'browserify:main', 'babel:dist', 'concat:deploy', 'uglify:deploy', 's3', 'cloudfront']);
grunt.registerTask('quick', 'Build snowplow.js, skipping building and minifying', ['browserify:main', 'babel:dist', 'concat:deploy']);
grunt.registerTask('test', 'Intern tests', ['browserify:test', 'babel:test', 'intern']);
grunt.registerTask('travis', 'Intern tests for Travis CI', ['concat:test', 'browserify:test', 'babel:test', 'intern']);
grunt.registerTask('tags', 'Minifiy the Snowplow invocation tag', ['uglify:tag', 'concat:tag']);
grunt.registerTask('local', 'Builds and places files read to serve and test locally', ['browserify:test', 'concat:local', 'babel:local']);
};

View file

@ -0,0 +1,29 @@
Significant portions copyright 2010 Anthon Pang. Remainder copyright
2012 Snowplow Analytics Ltd. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
names of their contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,88 @@
# JavaScript web analytics for Snowplow
[![Build Status][travis-image]][travis]
[![Selenium Test Status][saucelabs-button-image]][saucelabs]
[![Code Climate][codeclimate-image]][codeclimate]
[![Built with Grunt][grunt-image]][grunt]
[![License][license-image]][bsd]
[![jsDelivr Hits](https://data.jsdelivr.com/v1/package/gh/snowplow/snowplow-javascript-tracker/badge?style=rounded)](https://www.jsdelivr.com/package/gh/snowplow/snowplow-javascript-tracker)
## Overview
Add analytics to your websites and web apps with the [Snowplow][snowplow] event tracker for
JavaScript.
With this tracker you can collect user event data (page views, e-commerce transactions etc) from the
client-side tier of your websites and web apps.
## Find out more
| Technical Docs | Setup Guide | Roadmap & Contributing |
|-------------------------------------|------------------------------|--------------------------------------|
| [![i1][techdocs-image]][tech-docs] | [ ![i2][setup-image]][setup] | ![i3][roadmap-image] |
| [Technical Docs][tech-docs] | [Setup Guide][setup] | _coming soon_ |
## Developers
### Contributing quickstart
Assuming git, [Vagrant][vagrant-install] and [VirtualBox][virtualbox-install] installed:
```
host$ git clone https://github.com/snowplow/snowplow-javascript-tracker.git
host$ cd snowplow-javascript-tracker
host$ vagrant up && vagrant ssh
guest$ cd /vagrant
guest$ sudo npm install
guest$ cd core
guest$ sudo npm install
```
Set up an `./aws.json` file using the example `./aws.sample.json`. If you just want to concat +
minify without uploading then you don't need to fill out the `aws.json` file with valid credentials.
Build the package (default task concatenates and minifies) using `grunt`.
## Testing
[![Selenium Test Status][saucelabs-matrix-image]][saucelabs]
## Copyright and license
The Snowplow JavaScript Tracker is based on Anthon Pang's [`piwik.js`][piwikjs], the JavaScript
tracker for the open-source [Piwik][piwik] project, and is distributed under the same license
([Simplified BSD][bsd]).
Significant portions of the Snowplow JavaScript Tracker copyright 2010 Anthon Pang. Remainder
copyright 2012-14 Snowplow Analytics Ltd.
Licensed under the [Simplified BSD][bsd] license.
[snowplow]: http://snowplowanalytics.com/
[vagrant-install]: http://docs.vagrantup.com/v2/installation/index.html
[virtualbox-install]: https://www.virtualbox.org/wiki/Downloads
[piwik]: http://piwik.org/
[piwikjs]: https://github.com/piwik/piwik/blob/master/js/piwik.js
[piwikphp]: https://github.com/piwik/piwik/blob/master/piwik.php
[bsd]: http://www.opensource.org/licenses/bsd-license.php
[integrating]: /snowplow/snowplow/blob/master/docs/03_integrating_snowplowjs.md
[selfhosting]: /snowplow/snowplow/blob/master/docs/04_selfhosting_snowplow.md
[setup]: https://github.com/snowplow/snowplow/wiki/javascript-tracker-setup
[integrating-js-on-website]: https://github.com/snowplow/snowplow/wiki/integrating-javascript-tags-onto-your-website
[tech-docs]: https://github.com/snowplow/snowplow/wiki/javascript-tracker
[techdocs-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/techdocs.png
[setup-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/setup.png
[roadmap-image]: https://d3i6fms1cm1j0i.cloudfront.net/github/images/roadmap.png
[grunt-image]: https://cdn.gruntjs.com/builtwith.png
[grunt]: http://gruntjs.com/
[travis-image]: https://travis-ci.org/snowplow/snowplow-javascript-tracker.png?branch=master
[travis]: http://travis-ci.org/snowplow/snowplow-javascript-tracker
[codeclimate-image]: https://codeclimate.com/github/snowplow/snowplow-javascript-tracker.png
[codeclimate]: https://codeclimate.com/github/snowplow/snowplow-javascript-tracker
[saucelabs]: https://saucelabs.com/u/snowplow
[saucelabs-button-image]: https://saucelabs.com/buildstatus/snowplow
[saucelabs-matrix-image]: https://saucelabs.com/browser-matrix/snowplow.svg
[license-image]: http://img.shields.io/badge/license-simplified--bsd-blue.svg?style=flat

29
snowplow-javascript-tracker/Vagrantfile vendored Normal file
View file

@ -0,0 +1,29 @@
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.hostname = "snowplow-javascript-tracker"
config.ssh.forward_agent = true
config.vm.provider :virtualbox do |vb|
vb.name = Dir.pwd().split("/")[-1] + "-" + Time.now.to_f.to_i.to_s
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
vb.customize [ "guestproperty", "set", :id, "--timesync-threshold", 10000 ]
# node is not particularly memory hungry
vb.memory = 1024
end
config.vm.provision :shell do |sh|
sh.path = "vagrant/up.bash"
end
# Requires Vagrant 1.7.0+
config.push.define "push-tracker", strategy: "local-exec" do |push|
push.script = "vagrant/push/publish-tracker.bash"
end
# Requires Vagrant 1.7.0+
config.push.define "push-core", strategy: "local-exec" do |push|
push.script = "vagrant/push/publish-core.bash"
end
end

View file

@ -0,0 +1,7 @@
{
"key": "ADD HERE",
"secret": "ADD HERE",
"bucket": "ADD HERE",
"region": "ADD HERE",
"distribution": "ADD HERE"
}

View file

@ -0,0 +1,4 @@
index.js
lib/*.js
lib/*.js.map
.tscache

View file

@ -0,0 +1,2 @@
tests/
Gruntfile.js

View file

@ -0,0 +1,28 @@
Version 0.4.0 (2014-12-03)
--------------------------
Updated contexts schema to 1-0-1 (#292)
Added ability to send an empty contexts array (#296)
Version 0.3.0 (2014-11-04)
--------------------------
Applied callback to the Payload for an event rather than the event dictionary (#259)
Added page scroll parameters to trackPagePing (#257)
Added social tracking (#258)
Added trackAddToCart and trackRemoveFromCart methods (#260)
Added trackFormChange and trackFormSubmission methods (#261)
Added trackSiteSearch method (#263)
Added content field to trackLinkClick (#262)
Version 0.2.0 (2014-08-07)
--------------------------
Added UUID to payload (#244)
Added automatic timestamp generation (#243)
Added setter methods (#242)
Added timestamp parameter to tracker methods (#240)
Added dependendencies field to package.json (#239)
Added npm button to README (#238)
Updated README (#237)
Version 0.1.0 (2014-08-01)
--------------------------
Initial release

View file

@ -0,0 +1,58 @@
/*
* JavaScript tracker core for Snowplow: Gruntfile.js
*
* Copyright (c) 2014-2016 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
/*global module:false*/
module.exports = function (grunt) {
grunt.initConfig({
ts: {
default: {
tsconfig: true
}
},
dtsGenerator: {
default: {
options: {
name: 'snowplow-tracker',
project: '.',
out: 'main.d.ts'
}
}
},
intern: {
unit: {
options: {
runType: 'client',
config: 'tests/intern.js',
suites: [
'tests/unit/base64.js',
'tests/unit/payload.js',
'tests/unit/core.js',
'tests/unit/contexts.js'
]
}
}
}
});
grunt.loadNpmTasks('intern');
grunt.loadNpmTasks('grunt-ts');
grunt.loadNpmTasks('dts-generator');
grunt.registerTask('default', 'Compile and test', ['ts', 'dtsGenerator', 'intern']);
};

View file

@ -0,0 +1,132 @@
# Snowplow JavaScript Tracker Core [![npm version][npm-image]][npm-url]
Core module to be used by all Snowplow JavaScript trackers.
## Installation
With npm:
```bash
npm install snowplow-tracker-core
```
## Example
```js
var core = require('snowplow-tracker-core');
// Create an instance with base 64 encoding set to false (it defaults to true)
var coreInstance = core(false);
// Add a name-value pair to all payloads
coreInstance.addPayloadPair('vid', 2);
// Add each name-value pair in a dictionary to all payloads
coreInstance.addPayloadDict({
'ds': '1160x620',
'fp': 4070134789
});
// Add name-value pairs to all payloads using convenience methods
coreInstance.setTrackerVersion('js-3.0.0');
coreInstance.setPlatform('web');
coreInstance.setUserId('user-321');
coreInstance.setColorDepth(24);
coreInstance.setViewport(600, 400);
// Track a page view with URL and title
var pageViewPayload = coreInstance.trackPageView('http://www.example.com', 'landing page');
console.log(pageViewPayload);
/*
{
'e': 'pv',
'url': 'http://www.example.com',
'page': 'landing page',
'uid': 'user-321',
'vd': 2,
'ds': '1160x620',
'fp': 4070134789
'tv': 'js-3.0.0',
'p': 'web',
'cd': 24,
'vp': '600x400',
'dtm': 1406879959702, // timestamp
'eid': '0718a85a-45dc-4f71-a949-27870442ed7d' // UUID
}
*/
// Stop automatically adding tv, p, and dtm to the payload
coreInstance.resetPayloadPairs();
// Track an unstructured event
var unstructEventPayload = coreInstance.trackUnstructEvent({
'schema': 'iglu:com.snowplowanalytics.snowplow/link_click/jsonschema/1-0-0',
'data': {
'targetUrl': 'http://www.destination.com',
'elementId': 'bannerLink'
}
});
console.log(unstructEventPayload);
/*
{
'e': 'ue',
'ue_pr': {
'schema': 'iglu:com.snowplowanalytics.snowplow/unstruct_even/jsonschema/1-0-0',
'data': {
'schema': 'iglu:com.snowplowanalytics.snowplow/link_click/jsonschema/1-0-0',
'data': {
'targetUrl': 'http://www.destination.com',
'elementId': 'bannerLink'
}
}
},
'dtm': 1406879973439,
'eid': '956c6670-cbf6-460b-9f96-143e0320fdf6'
}
*/
```
## Other features
Core instances can be initialized with two parameters. The first is a boolean and determines whether custom contexts and unstructured events will be base 64 encoded. The second is an optional callback function which gets applied to every payload created by the instance.
```js
var coreInstance = core(true, console.log);
```
The above example would base 64 encode all unstructured events and custom contexts and would log each payload to the console.
Use the `setBase64Encoding` method to turn base 64 encoding on or off after initializing a core instance:
```js
var core = require('snowplow-tracker-core');
var coreInstance = core(); // Base 64 encoding on by default
coreInstance.setBase64Encoding(false); // Base 64 encoding is now off
```
## Documentation
For more information on the Snowplow JavaScript Tracker Core's API, view its [wiki page][wiki].
## Copyright and license
The Snowplow JavaScript Tracker Core is copyright 2014 Snowplow Analytics Ltd.
Licensed under the [Apache License, Version 2.0][apache-license] (the "License");
you may not use this software except in compliance with the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
[apache-license]: http://www.apache.org/licenses/LICENSE-2.0
[npm-url]: http://badge.fury.io/js/snowplow-tracker-core
[npm-image]: https://badge.fury.io/js/snowplow-tracker-core.svg
[wiki]: https://github.com/snowplow/snowplow/wiki/Javascript-Tracker-Core

View file

@ -0,0 +1,16 @@
/*
* JavaScript tracker core for Snowplow: index.js
*
* Copyright (c) 2014-2016 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
export {trackerCore} from "./lib/core";

View file

@ -0,0 +1,170 @@
/*
* Copyright (c) 2013 Kevin van Zonneveld (http://kvz.io)
* and Contributors (http://phpjs.org/authors)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// Deprecated and removed in TypeScript
declare function unescape(s: string): string;
export function base64urldecode(data: string): string {
if (!data) {
return data;
}
var padding = 4 - data.length % 4;
switch (padding) {
case 2:
data += "==";
break;
case 3:
data += "=";
break;
}
var b64Data = data.replace(/-/g, '+').replace(/_/g, '/');
return base64decode(b64Data);
}
/**
* Encode string as base64.
* Any type can be passed, but will be stringified
*
* @param data string to encode
* @returns base64-encoded string
*/
export function base64encode(data: string): string {
// discuss at: http://phpjs.org/functions/base64_encode/
// original by: Tyler Akins (http://rumkin.com)
// improved by: Bayron Guevara
// improved by: Thunder.m
// improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// improved by: Rafał Kukawski (http://kukawski.pl)
// bugfixed by: Pellentesque Malesuada
// example 1: base64_encode('Kevin van Zonneveld');
// returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA=='
// example 2: base64_encode('a');
// returns 2: 'YQ=='
// example 3: base64_encode('✓ à la mode');
// returns 3: '4pyTIMOgIGxhIG1vZGU='
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
ac = 0,
enc: string,
tmp_arr: Array<string> = [];
if (!data) {
return data;
}
data = unescape(encodeURIComponent(data));
do {
// pack three octets into four hexets
o1 = data.charCodeAt(i++);
o2 = data.charCodeAt(i++);
o3 = data.charCodeAt(i++);
bits = o1 << 16 | o2 << 8 | o3;
h1 = bits >> 18 & 0x3f;
h2 = bits >> 12 & 0x3f;
h3 = bits >> 6 & 0x3f;
h4 = bits & 0x3f;
// use hexets to index into b64, and append result to encoded string
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
} while (i < data.length);
enc = tmp_arr.join('');
var r = data.length % 3;
return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3);
}
export function base64decode(encodedData:string): string {
// discuss at: http://locutus.io/php/base64_decode/
// original by: Tyler Akins (http://rumkin.com)
// improved by: Thunder.m
// improved by: Kevin van Zonneveld (http://kvz.io)
// improved by: Kevin van Zonneveld (http://kvz.io)
// input by: Aman Gupta
// input by: Brett Zamir (http://brett-zamir.me)
// bugfixed by: Onno Marsman (https://twitter.com/onnomarsman)
// bugfixed by: Pellentesque Malesuada
// bugfixed by: Kevin van Zonneveld (http://kvz.io)
// improved by: Indigo744
// example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA==')
// returns 1: 'Kevin van Zonneveld'
// example 2: base64_decode('YQ==')
// returns 2: 'a'
// example 3: base64_decode('4pyTIMOgIGxhIG1vZGU=')
// returns 3: '✓ à la mode'
// decodeUTF8string()
// Internal function to decode properly UTF8 string
// Adapted from Solution #1 at https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
var decodeUTF8string = function (str) {
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(str.split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
}).join(''))
};
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
ac = 0,
dec = '',
tmpArr: Array<string> = [];
if (!encodedData) {
return encodedData;
}
encodedData += '';
do {
// unpack four hexets into three octets using index points in b64
h1 = b64.indexOf(encodedData.charAt(i++));
h2 = b64.indexOf(encodedData.charAt(i++));
h3 = b64.indexOf(encodedData.charAt(i++));
h4 = b64.indexOf(encodedData.charAt(i++));
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
o1 = bits >> 16 & 0xff;
o2 = bits >> 8 & 0xff;
o3 = bits & 0xff;
if (h3 === 64) {
tmpArr[ac++] = String.fromCharCode(o1);
} else if (h4 === 64) {
tmpArr[ac++] = String.fromCharCode(o1, o2);
} else {
tmpArr[ac++] = String.fromCharCode(o1, o2, o3);
}
} while (i < encodedData.length);
dec = tmpArr.join('');
return decodeUTF8string(dec.replace(/\0+$/, ''));
}

View file

@ -0,0 +1,492 @@
/*
* JavaScript tracker core for Snowplow: contexts.js
*
* Copyright (c) 2014-2018 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
import { PayloadData, isNonEmptyJson } from "./payload";
import { SelfDescribingJson } from "./core";
import { base64urldecode } from "./base64";
import isEqual = require('lodash/isEqual');
import has = require('lodash/has');
import get = require('lodash/get');
import isPlainObject = require('lodash/isPlainObject');
import every = require('lodash/every');
import compact = require('lodash/compact');
import map = require('lodash/map');
/**
* Datatypes (some algebraic) for representing context types
*/
/**
* A context generator is a callback that returns a self-describing JSON
* @param {Object} args - Object that contains: event, eventType, eventSchema
* @return {SelfDescribingJson} A self-describing JSON
*/
export type ContextGenerator = (args?: Object) => SelfDescribingJson;
/**
* A context primitive is either a self-describing JSON or a context generator
*/
export type ContextPrimitive = SelfDescribingJson | ContextGenerator;
/**
* A context filter is a user-supplied callback that is evaluated for each event
* to determine if the context associated with the filter should be attached to the event
*/
export type ContextFilter = (args?: Object) => boolean;
/**
* A filter provider is an array that has two parts: a context filter and context primitives
* If the context filter evaluates to true, the tracker will attach the context primitive(s)
*/
export type FilterProvider = [ContextFilter, Array<ContextPrimitive> | ContextPrimitive];
/**
* A ruleset has accept or reject properties that contain rules for matching Iglu schema URIs
*/
export interface RuleSet {
accept?: string[] | string;
reject?: string[] | string;
}
/**
* A ruleset provider is an array that has two parts: a ruleset and context primitives
* If the ruleset allows the current event schema URI, the tracker will attach the context primitive(s)
*/
export type RuleSetProvider = [RuleSet, Array<ContextPrimitive> | ContextPrimitive];
/**
* Conditional context providers are two element arrays used to decide when to attach contexts, where:
* - the first element is some conditional criterion
* - the second element is any number of context primitives
*/
export type ConditionalContextProvider = FilterProvider | RuleSetProvider;
export function getSchemaParts(input: string): Array<string> | undefined {
let re = new RegExp('^iglu:([a-zA-Z0-9-_\.]+)\/([a-zA-Z0-9-_]+)\/jsonschema\/([1-9][0-9]*)\-(0|[1-9][0-9]*)\-(0|[1-9][0-9]*)$');
let matches = re.exec(input);
if (matches !== null)
return matches.slice(1, 6);
return undefined;
}
export function validateVendorParts(parts: Array<string>): boolean {
if (parts[0] === '*' || parts[1] === '*') {
return false; // no wildcard in first or second part
}
if (parts.slice(2).length > 0) {
let asterisk = false;
for (let part of parts.slice(2)) {
if (part === '*') // mark when we've found a wildcard
asterisk = true;
else if (asterisk) // invalid if alpha parts come after wildcard
return false;
}
return true;
} else if (parts.length == 2)
return true;
return false;
}
export function validateVendor(input: string): boolean {
let parts = input.split('.');
if (parts && parts.length > 1)
return validateVendorParts(parts);
return false;
}
export function getRuleParts(input: string): Array<string> | undefined {
const re = new RegExp('^iglu:((?:(?:[a-zA-Z0-9-_]+|\\*)\.)+(?:[a-zA-Z0-9-_]+|\\*))\/([a-zA-Z0-9-_.]+|\\*)\/jsonschema\/([1-9][0-9]*|\\*)-(0|[1-9][0-9]*|\\*)-(0|[1-9][0-9]*|\\*)$');
let matches = re.exec(input);
if (matches !== null && validateVendor(matches[1]))
return matches.slice(1,6);
return undefined;
}
export function isValidRule(input: string): boolean {
let ruleParts = getRuleParts(input);
if (ruleParts) {
let vendor = ruleParts[0];
return ruleParts.length === 5 && validateVendor(vendor);
}
return false;
}
export function isStringArray(input: any): boolean {
return Array.isArray(input) && input.every((x) => { return typeof x === 'string' });
}
export function isValidRuleSetArg(input: any): boolean {
if (isStringArray(input))
return input.every((x) => { return isValidRule(x) });
else if (typeof input === 'string')
return isValidRule(input);
return false;
}
export function isSelfDescribingJson(input: any) : boolean {
if (isNonEmptyJson(input))
if ('schema' in input && 'data' in input)
return (typeof(input.schema) === 'string' && typeof(input.data) === 'object');
return false;
}
export function isEventJson(input: any) : boolean {
if (isNonEmptyJson(input) && ('e' in input))
return (typeof(input.e) === 'string');
return false;
}
export function isRuleSet(input: any) : boolean {
let ruleCount = 0;
if (isPlainObject(input)) {
if (has(input, 'accept')) {
if (isValidRuleSetArg(input['accept'])) {
ruleCount += 1;
} else {
return false;
}
}
if (has(input, 'reject')) {
if (isValidRuleSetArg(input['reject'])) {
ruleCount += 1;
} else {
return false;
}
}
// if either 'reject' or 'accept' or both exists,
// we have a valid ruleset
return ruleCount > 0 && ruleCount <= 2;
}
return false;
}
export function isContextGenerator(input: any) : boolean {
return typeof(input) === 'function' && input.length <= 1;
}
export function isContextFilter(input: any) : boolean {
return typeof(input) === 'function' && input.length <= 1;
}
export function isContextPrimitive(input: any) : boolean {
return (isContextGenerator(input) || isSelfDescribingJson(input));
}
export function isFilterProvider(input: any) : boolean {
if (Array.isArray(input)) {
if (input.length === 2) {
if (Array.isArray(input[1])) {
return isContextFilter(input[0]) && every(input[1], isContextPrimitive);
}
return isContextFilter(input[0]) && isContextPrimitive(input[1]);
}
}
return false;
}
export function isRuleSetProvider(input: any) : boolean {
if (Array.isArray(input) && input.length === 2) {
if (!isRuleSet(input[0]))
return false;
if (Array.isArray(input[1]))
return every(input[1], isContextPrimitive);
return isContextPrimitive(input[1]);
}
return false;
}
export function isConditionalContextProvider(input: any) : boolean {
return isFilterProvider(input) || isRuleSetProvider(input);
}
export function matchSchemaAgainstRule(rule: string, schema: string) : boolean {
if (!isValidRule(rule))
return false;
let ruleParts = getRuleParts(rule);
let schemaParts = getSchemaParts(schema);
if (ruleParts && schemaParts) {
if (!matchVendor(ruleParts[0], schemaParts[0]))
return false;
for (let i=1; i<5; i++) {
if (!matchPart(ruleParts[i], schemaParts[i]))
return false;
}
return true; // if it hasn't failed, it passes
}
return false;
}
export function matchVendor(rule: string, vendor: string): boolean {
// rule and vendor must have same number of elements
let vendorParts = vendor.split('.');
let ruleParts = rule.split('.');
if (vendorParts && ruleParts) {
if (vendorParts.length !== ruleParts.length)
return false;
for (let i=0; i<ruleParts.length; i++) {
if (!matchPart(vendorParts[i], ruleParts[i]))
return false;
}
return true;
}
return false;
}
export function matchPart(rule: string, schema: string): boolean {
// parts should be the string nested between slashes in the URI: /example/
return (rule && schema && rule === '*' || rule === schema);
}
export function matchSchemaAgainstRuleSet(ruleSet: RuleSet, schema: string) : boolean {
let rejectCount = 0;
let acceptCount = 0;
let acceptRules = get(ruleSet, 'accept');
if (Array.isArray(acceptRules)) {
if ((ruleSet.accept as Array<string>).some((rule) => (matchSchemaAgainstRule(rule, schema)))) {
acceptCount++;
}
} else if (typeof(acceptRules) === 'string') {
if (matchSchemaAgainstRule(acceptRules, schema)) {
acceptCount++;
}
}
let rejectRules = get(ruleSet, 'reject');
if (Array.isArray(rejectRules)) {
if ((ruleSet.reject as Array<string>).some((rule) => (matchSchemaAgainstRule(rule, schema)))) {
rejectCount++;
}
} else if (typeof(rejectRules) === 'string') {
if (matchSchemaAgainstRule(rejectRules, schema)) {
rejectCount++;
}
}
if (acceptCount > 0 && rejectCount === 0) {
return true;
} else if (acceptCount === 0 && rejectCount > 0) {
return false;
}
return false;
}
// Returns the "useful" schema, i.e. what would someone want to use to identify events.
// The idea being that you can determine the event type from 'e', so getting the schema from
// 'ue_px.schema'/'ue_pr.schema' would be redundant - it'll return the unstructured event schema.
// Instead the schema nested inside the unstructured event is more useful!
// This doesn't decode ue_px, it works because it's called by code that has already decoded it
export function getUsefulSchema(sb: SelfDescribingJson): string {
if (typeof get(sb, 'ue_px.data.schema') === 'string')
return get(sb, 'ue_px.data.schema');
else if (typeof get(sb, 'ue_pr.data.schema') === 'string')
return get(sb, 'ue_pr.data.schema');
else if (typeof get(sb, 'schema') === 'string')
return get(sb, 'schema');
return '';
}
export function getDecodedEvent(sb: SelfDescribingJson): SelfDescribingJson {
let decodedEvent = {...sb}; // spread operator, instantiates new object
try {
if (has(decodedEvent, 'ue_px')) {
decodedEvent['ue_px'] = JSON.parse(base64urldecode(get(decodedEvent, ['ue_px'])));
}
} catch(e) {}
return decodedEvent;
}
export function getEventType(sb: {}): string {
return get(sb, 'e', '');
}
export function buildGenerator(generator: ContextGenerator,
event: SelfDescribingJson,
eventType: string,
eventSchema: string) : SelfDescribingJson | Array<SelfDescribingJson> | undefined {
let contextGeneratorResult : SelfDescribingJson | Array<SelfDescribingJson> | undefined = undefined;
try {
// try to evaluate context generator
let args = {
event: event,
eventType: eventType,
eventSchema: eventSchema
};
contextGeneratorResult = generator(args);
// determine if the produced result is a valid SDJ
if (isSelfDescribingJson(contextGeneratorResult)) {
return contextGeneratorResult;
} else if (Array.isArray(contextGeneratorResult) && every(contextGeneratorResult, isSelfDescribingJson)) {
return contextGeneratorResult;
} else {
return undefined;
}
} catch (error) {
contextGeneratorResult = undefined;
}
return contextGeneratorResult;
}
export function normalizeToArray(input: any) : Array<any> {
if (Array.isArray(input)) {
return input;
}
return Array.of(input);
}
export function generatePrimitives(contextPrimitives: Array<ContextPrimitive> | ContextPrimitive,
event: SelfDescribingJson,
eventType: string,
eventSchema: string) : Array<SelfDescribingJson> {
let normalizedInputs : Array<ContextPrimitive> = normalizeToArray(contextPrimitives);
let partialEvaluate = (primitive) => {
let result = evaluatePrimitive(primitive, event, eventType, eventSchema);
if (result && result.length !== 0) {
return result;
}
};
let generatedContexts = map(normalizedInputs, partialEvaluate);
return [].concat(...compact(generatedContexts));
}
export function evaluatePrimitive(contextPrimitive: ContextPrimitive,
event: SelfDescribingJson,
eventType: string,
eventSchema: string) : Array<SelfDescribingJson> | undefined {
if (isSelfDescribingJson(contextPrimitive)) {
return [contextPrimitive as SelfDescribingJson];
} else if (isContextGenerator(contextPrimitive)) {
let generatorOutput = buildGenerator(contextPrimitive as ContextGenerator, event, eventType, eventSchema);
if (isSelfDescribingJson(generatorOutput)) {
return [generatorOutput as SelfDescribingJson];
} else if (Array.isArray(generatorOutput)) {
return generatorOutput;
}
}
return undefined;
}
export function evaluateProvider(provider: ConditionalContextProvider,
event: SelfDescribingJson,
eventType: string,
eventSchema: string): Array<SelfDescribingJson> {
if (isFilterProvider(provider)) {
let filter : ContextFilter = (provider as FilterProvider)[0];
let filterResult = false;
try {
let args = {
event: event,
eventType: eventType,
eventSchema: eventSchema
};
filterResult = filter(args);
} catch(error) {
filterResult = false;
}
if (filterResult === true) {
return generatePrimitives((provider as FilterProvider)[1], event, eventType, eventSchema);
}
} else if (isRuleSetProvider(provider)) {
if (matchSchemaAgainstRuleSet((provider as RuleSetProvider)[0], eventSchema)) {
return generatePrimitives((provider as RuleSetProvider)[1], event, eventType, eventSchema);
}
}
return [];
}
export function generateConditionals(providers: Array<ConditionalContextProvider> | ConditionalContextProvider,
event: SelfDescribingJson,
eventType: string,
eventSchema: string) : Array<SelfDescribingJson> {
let normalizedInput : Array<ConditionalContextProvider> = normalizeToArray(providers);
let partialEvaluate = (provider) => {
let result = evaluateProvider(provider, event, eventType, eventSchema);
if (result && result.length !== 0) {
return result;
}
};
let generatedContexts = map(normalizedInput, partialEvaluate);
return [].concat(...compact(generatedContexts));
}
export function contextModule() {
let globalPrimitives : Array<ContextPrimitive> = [];
let conditionalProviders : Array<ConditionalContextProvider> = [];
function assembleAllContexts(event: SelfDescribingJson) : Array<SelfDescribingJson> {
let eventSchema = getUsefulSchema(event);
let eventType = getEventType(event);
let contexts : Array<SelfDescribingJson> = [];
let generatedPrimitives = generatePrimitives(globalPrimitives, event, eventType, eventSchema);
contexts.push(...generatedPrimitives);
let generatedConditionals = generateConditionals(conditionalProviders, event, eventType, eventSchema);
contexts.push(...generatedConditionals);
return contexts;
}
return {
getGlobalPrimitives: function () {
return globalPrimitives;
},
getConditionalProviders: function () {
return conditionalProviders;
},
addGlobalContexts: function (contexts: Array<any>) {
let acceptedConditionalContexts : ConditionalContextProvider[] = [];
let acceptedContextPrimitives : ContextPrimitive[] = [];
for (let context of contexts) {
if (isConditionalContextProvider(context)) {
acceptedConditionalContexts.push(context);
} else if (isContextPrimitive(context)) {
acceptedContextPrimitives.push(context);
}
}
globalPrimitives = globalPrimitives.concat(acceptedContextPrimitives);
conditionalProviders = conditionalProviders.concat(acceptedConditionalContexts);
},
clearGlobalContexts: function () {
conditionalProviders = [];
globalPrimitives = [];
},
removeGlobalContexts: function (contexts: Array<any>) {
for (let context of contexts) {
if (isConditionalContextProvider(context)) {
conditionalProviders = conditionalProviders.filter(item => !isEqual(item, context));
} else if (isContextPrimitive(context)) {
globalPrimitives = globalPrimitives.filter(item => !isEqual(item, context));
} else {
// error message here?
}
}
},
getApplicableContexts: function (event: PayloadData) : Array<SelfDescribingJson> {
const builtEvent = event.build();
if (isEventJson(builtEvent)) {
const decodedEvent = getDecodedEvent(builtEvent as SelfDescribingJson);
return assembleAllContexts(decodedEvent);
} else {
return [];
}
}
};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,112 @@
/*
* JavaScript tracker core for Snowplow: payload.js
*
* Copyright (c) 2014-2016 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
import * as base64 from './base64';
/**
* Interface for mutable object encapsulating tracker payload
*/
export interface PayloadData {
add: (key: string, value?: string) => void,
addDict: (dict: Object) => void,
addJson: (keyIfEncoded: string, keyIfNotEncoded: string, json: Object) => void,
build: () => Object;
}
/**
* Bas64 encode data with URL and Filename Safe Alphabet (base64url)
*
* See: http://tools.ietf.org/html/rfc4648#page-7
*/
function base64urlencode(data: string): string {
if (!data) {
return data;
}
var enc = base64.base64encode(data);
return enc.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
}
/**
* Is property a non-empty JSON?
*/
export function isNonEmptyJson(property): boolean {
if (!isJson(property)) {
return false;
}
for (var key in property) {
if (property.hasOwnProperty(key)) {
return true;
}
}
return false;
}
/**
* Is property a JSON?
*/
export function isJson(property: Object): boolean {
return (typeof property !== 'undefined' && property !== null &&
(property.constructor === {}.constructor || property.constructor === [].constructor));
}
/**
* A helper to build a Snowplow request string from an
* an optional initial value plus a set of individual
* name-value pairs, provided using the add method.
*
* @param base64Encode boolean Whether or not JSONs should be
* Base64-URL-safe-encoded
*
* @return object The request string builder, with add, addRaw and build methods
*/
export function payloadBuilder(base64Encode: boolean): PayloadData {
var dict = {};
var add = function (key: string, value?: string): void {
if (value != null && value !== '') { // null also checks undefined
dict[key] = value;
}
};
var addDict = function (dict: Object) {
for (var key in dict) {
if (dict.hasOwnProperty(key)) {
add(key, dict[key]);
}
}
};
var addJson = function (keyIfEncoded: string, keyIfNotEncoded: string, json?: Object) {
if (isNonEmptyJson(json)) {
var str = JSON.stringify(json);
if (base64Encode) {
add(keyIfEncoded, base64urlencode(str));
} else {
add(keyIfNotEncoded, str);
}
}
};
return {
add: add,
addDict: addDict,
addJson: addJson,
build: function () {
return dict;
}
};
}

View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,37 @@
{
"name": "snowplow-tracker-core",
"version": "0.7.0",
"devDependencies": {
"@types/es6-shim": "0.31.34",
"@types/node": "^9.6.7",
"dts-generator": "^2.0.0",
"grunt": "0.4.5",
"grunt-ts": "5.5.1",
"intern": "3.3.2",
"typescript": "2.2.2"
},
"dependencies": {
"@types/uuid": "^2.0.29",
"lodash": "^4.17.11",
"uuid": "2.0.3"
},
"contributors": [
"Alex Dean",
"Simon Andersson",
"Fred Blundun"
],
"types": "./main.d.ts",
"description": "Core functionality for the Snowplow JavaScript trackers (browser JavaScript; Node.js; Segment.io server-side)",
"repository": {
"type": "git",
"url": "https://github.com/snowplow/snowplow-javascript-tracker.git"
},
"bugs": "https://github.com/snowplow/snowplow-javascript-tracker/issues",
"keywords": [
"tracking",
"web analytics",
"events",
"open source"
],
"license": "Apache-2.0"
}

View file

@ -0,0 +1,20 @@
/*
* JavaScript tracker core for Snowplow: tests/intern.js
*
* Copyright (c) 2014-2016 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
define({
excludeInstrumentation: /^(?:tests|node_modules)\//
});

View file

@ -0,0 +1,32 @@
/*
* JavaScript tracker core for Snowplow: tests/base64.js
*
* Copyright (c) 2014 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
define([
"intern!object",
"intern/chai!assert",
"intern/dojo/node!../../lib/base64.js"
], function (registerSuite, assert, base64) {
registerSuite({
name: "Base 64 encoding test",
"Encode a string": function () {
assert.strictEqual(base64.base64encode('my_string'), 'bXlfc3RyaW5n', 'Base64-encode a string');
},
"Encode a string containing special characters": function () {
assert.strictEqual(base64.base64encode('™®字'), '4oSiwq7lrZc=', 'Base64-encode a containing TM, Registered Trademark, and Chinese characters');
}
});
});

View file

@ -0,0 +1,327 @@
/*
* JavaScript tracker core for Snowplow: tests/integration.js
*
* Copyright (c) 2014-2016 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
define([
"intern!object",
"intern/chai!assert",
"intern/chai!expect",
"intern/dojo/node!../../lib/contexts.js",
"intern/dojo/node!../../lib/payload.js"
], function (registerSuite, assert, expect, contexts, payload) {
registerSuite({
name: "Global context tests",
"Identify context primitives": function () {
let geolocationContext = {
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
data: {
'latitude': 40.0,
'longitude' : 55.1
}
};
function eventTypeContextGenerator(args) {
let context = {};
context['schema'] = 'iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1';
context['data']['osType'] = 'ubuntu';
context['data']['osVersion'] = '2018.04';
context['data']['deviceManufacturer'] = 'ASUS';
context['data']['deviceModel'] = String(args[eventType]);
return context;
}
assert.isTrue(contexts.isContextPrimitive(geolocationContext), 'A context primitive should be identified');
assert.isTrue(contexts.isContextPrimitive(eventTypeContextGenerator), 'A context primitive should be identified');
},
"Validating vendors": function () {
// Vendor validation
assert.isTrue(contexts.validateVendor('com.acme.marketing'), 'A valid vendor without wildcard is accepted');
assert.isTrue(contexts.validateVendor('com.acme.*'), 'A valid vendor with wildcard is accepted');
assert.isFalse(contexts.validateVendor('*.acme.*'), 'A vendor starting with asterisk is rejected');
assert.isFalse(contexts.validateVendor('com.acme.*.marketing'), 'A vendor with asterisk out of order is rejected');
},
"Identify rule sets": function () {
const acceptRuleSet = {
accept: ['iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*']
};
const rejectRuleSet = {
reject: ['iglu:com.snowplowanalytics.snowplow/*/jsonschema/1-0-1']
};
const bothRuleSet = {
accept: ['iglu:com.snowplowanalytics.snowplow/*/jsonschema/1-*-1'],
reject: ['iglu:com.snowplowanalytics.snowplow/some_event/jsonschema/1-*-*']
};
const pageview_schema = 'iglu:com.snowplowanalytics.snowplow/pageview/jsonschema/1-0-1';
assert.isTrue(contexts.isValidRule(acceptRuleSet.accept[0]), 'All rule elements are correctly identified as valid rules');
assert.isTrue(contexts.isValidRuleSetArg(acceptRuleSet.accept), 'A rule set arg is correctly identified');
assert.isTrue(contexts.isRuleSet(acceptRuleSet), 'An accept rule set is identified');
assert.isTrue(contexts.isRuleSet(rejectRuleSet), 'A reject rule set is identified');
assert.isTrue(contexts.isRuleSet(bothRuleSet), 'A rule set with both elements is identified');
assert.deepEqual(contexts.getSchemaParts(pageview_schema), ['com.snowplowanalytics.snowplow', 'pageview', '1', '0', '1'], 'Gets correct parts for schema');
assert.isTrue(contexts.matchSchemaAgainstRule('iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*', pageview_schema), 'Matches schema against wildcarded rule');
assert.isTrue(contexts.matchSchemaAgainstRuleSet(acceptRuleSet, pageview_schema), 'Accept rule set accepts matching schema');
assert.isFalse(contexts.matchSchemaAgainstRuleSet(rejectRuleSet, pageview_schema), 'Reject rule set rejects matching schema');
},
"Identify filter function": function () {
let filterFunction = function (args) {
return args['eventType'] === 'ue';
};
assert.isTrue(contexts.isContextFilter(filterFunction), 'A valid filter function is identified');
},
"Identify rule set provider": function () {
let bothRuleSet = {
accept: ['iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*'],
reject: ['iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*']
};
let geolocationContext = {
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
data: {
'latitude': 40.0,
'longitude' : 55.1
}
};
function eventTypeContextGenerator(args) {
let context = {};
context['schema'] = 'iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1';
context['data']['osType'] = 'ubuntu';
context['data']['osVersion'] = '2018.04';
context['data']['deviceManufacturer'] = 'ASUS';
context['data']['deviceModel'] = String(args[eventType]);
return context;
}
let ruleSetProvider = [bothRuleSet, [geolocationContext, eventTypeContextGenerator]];
assert.isTrue(contexts.isRuleSetProvider(ruleSetProvider), 'Valid rule set provider is correctly identified');
},
"Identify filter provider": function () {
let filterFunction = function (args) {
return args['eventType'] === 'ue';
};
let geolocationContext = {
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
data: {
'latitude': 40.0,
'longitude' : 55.1
}
};
function eventTypeContextGenerator(args) {
let context = {};
context['schema'] = 'iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1';
context['data']['osType'] = 'ubuntu';
context['data']['osVersion'] = '2018.04';
context['data']['deviceManufacturer'] = 'ASUS';
context['data']['deviceModel'] = String(args[eventType]);
return context;
}
let filterProvider = [filterFunction, [geolocationContext, eventTypeContextGenerator]];
assert.isTrue(contexts.isFilterProvider(filterProvider), 'A valid filter provider is identified');
},
"Add global contexts": function () {
let geolocationContext = {
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
data: {
'latitude': 40.0,
'longitude' : 55.1
}
};
function eventTypeContextGenerator(args) {
let context = {};
context['schema'] = 'iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1';
context['data']['osType'] = 'ubuntu';
context['data']['osVersion'] = '2018.04';
context['data']['deviceManufacturer'] = 'ASUS';
context['data']['deviceModel'] = String(args[eventType]);
return context;
}
let bothRuleSet = {
accept: ['iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*'],
reject: ['iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*']
};
let filterFunction = function (args) {
return args['eventType'] === 'ue';
};
let filterProvider = [filterFunction, [geolocationContext, eventTypeContextGenerator]];
let ruleSetProvider = [bothRuleSet, [geolocationContext, eventTypeContextGenerator]];
let contextArray = [filterProvider, ruleSetProvider, geolocationContext, eventTypeContextGenerator];
let module = contexts.contextModule();
module.addGlobalContexts(contextArray);
assert.strictEqual(module.getGlobalPrimitives().length, 2,
'Correct number of primitives added');
assert.strictEqual(module.getConditionalProviders().length, 2,
'Correct number of conditional providers added');
},
"Remove global contexts": function () {
let geolocationContext = {
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
data: {
'latitude': 40.0,
'longitude' : 55.1
}
};
function eventTypeContextGenerator(args) {
let context = {};
context['schema'] = 'iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1';
context['data']['osType'] = 'ubuntu';
context['data']['osVersion'] = '2018.04';
context['data']['deviceManufacturer'] = 'ASUS';
context['data']['deviceModel'] = String(args[eventType]);
return context;
}
let bothRuleSet = {
accept: ['iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*'],
reject: ['iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*']
};
let filterFunction = function (args) {
return args['eventType'] === 'ue';
};
let filterProvider = [filterFunction, [geolocationContext, eventTypeContextGenerator]];
let ruleSetProvider = [bothRuleSet, [geolocationContext, eventTypeContextGenerator]];
let module = contexts.contextModule();
module.addGlobalContexts([filterProvider, ruleSetProvider, geolocationContext, eventTypeContextGenerator]);
module.removeGlobalContexts([filterProvider, geolocationContext, eventTypeContextGenerator]);
assert.strictEqual(module.getGlobalPrimitives().length, 0,
'Correct number of primitives added');
assert.strictEqual(module.getConditionalProviders().length, 1,
'Correct number of conditional providers added');
},
"Clear global contexts": function () {
let geolocationContext = {
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
data: {
'latitude': 40.0,
'longitude' : 55.1
}
};
function eventTypeContextGenerator(args) {
let context = {};
context['schema'] = 'iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1';
context['data']['osType'] = 'ubuntu';
context['data']['osVersion'] = '2018.04';
context['data']['deviceManufacturer'] = 'ASUS';
context['data']['deviceModel'] = String(args[eventType]);
return context;
}
let bothRuleSet = {
accept: ['iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*'],
reject: ['iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*']
};
let filterFunction = function (args) {
return args['eventType'] === 'ue';
};
let filterProvider = [filterFunction, [geolocationContext, eventTypeContextGenerator]];
let ruleSetProvider = [bothRuleSet, [geolocationContext, eventTypeContextGenerator]];
let contextArray = [filterProvider, ruleSetProvider, geolocationContext, eventTypeContextGenerator];
let module = contexts.contextModule();
module.addGlobalContexts(contextArray);
module.clearGlobalContexts();
assert.strictEqual(module.getGlobalPrimitives().length, 0,
'Correct number of primitives added');
assert.strictEqual(module.getConditionalProviders().length, 0,
'Correct number of conditional providers added');
},
"Get applicable contexts": function () {
let geolocationContext = {
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
data: {
'latitude': 40.0,
'longitude' : 55.1
}
};
function eventTypeContextGenerator(args) {
let context = {};
context['schema'] = 'iglu:com.snowplowanalytics.snowplow/some_context/jsonschema/1-0-1';
context['data'] = {};
context['data']['osType'] = 'ubuntu';
context['data']['osVersion'] = '2018.04';
context['data']['deviceManufacturer'] = 'ASUS';
context['data']['eventType'] = String(args['eventType']);
return context;
}
let unstructuredEventRuleset = {
accept: ['iglu:com.acme_company/*/jsonschema/*-*-*']
};
let filterFunction = function (args) {
return args['eventType'] === 'ue';
};
let filterProvider = [filterFunction, [geolocationContext, eventTypeContextGenerator]];
let ruleSetProvider = [unstructuredEventRuleset, [geolocationContext, eventTypeContextGenerator]];
let eventJson = {
e: 'ue',
ue_px: {
schema: 'iglu:com.snowplowanalytics.snowplow/unstructured_event/jsonschema/1-0-0',
data: {
schema: 'iglu:com.acme_company/some_event/jsonschema/1-0-0',
data: {}
}
}
};
let contextArray = [filterProvider, ruleSetProvider, geolocationContext, eventTypeContextGenerator];
let module = contexts.contextModule();
let event = payload.payloadBuilder(false);
for (let property in eventJson) {
if (eventJson.hasOwnProperty(property)) {
event.add(property, eventJson[property]);
}
}
module.addGlobalContexts(contextArray);
assert.strictEqual(module.getApplicableContexts(event).length, 6,
'Correct number of contexts returned');
}
});
});

View file

@ -0,0 +1,747 @@
/*
* JavaScript tracker core for Snowplow: tests/integration.js
*
* Copyright (c) 2014-2016 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
define([
"intern!object",
"intern/chai!assert",
"intern/chai!expect",
"intern/dojo/node!../../lib/core.js"
], function (registerSuite, assert, expect, core) {
var unstructEventSchema = 'iglu:com.snowplowanalytics.snowplow/unstruct_event/jsonschema/1-0-0';
var tracker = core.trackerCore(false);
// Doesn't check true timestamp
function compare(result, expected, message) {
result = result.build();
assert.ok(result['eid'], 'A UUID should be attached to all events');
delete result['eid'];
assert.ok(result['dtm'], 'A timestamp should be attached to all events');
delete result['dtm'];
assert.deepEqual(result, expected, message);
}
registerSuite({
name: "Tracking events",
"Track a page view": function () {
var url = 'http://www.example.com';
var page = 'title page';
var expected = {
e: 'pv',
url: url,
page: page
};
compare(tracker.trackPageView(url, page), expected, 'A page view should be tracked correctly');
},
"Track a page ping": function () {
var url = 'http://www.example.com';
var referer = 'http://www.google.com';
var expected = {
e: 'pp',
url: url,
refr: referer,
pp_mix: '1',
pp_max: '2',
pp_miy: '3',
pp_may: '4'
};
compare(tracker.trackPagePing(url, null, referer, 1, 2, 3, 4), expected, 'A page ping should be tracked correctly');
},
"Track a structured event": function () {
var expected = {
e: 'se',
se_ca: 'cat',
se_ac: 'act',
se_la: 'lab',
se_pr: 'prop',
se_va: 'val'
};
compare(tracker.trackStructEvent('cat', 'act', 'lab', 'prop', 'val'), expected, 'A structured event should be tracked correctly');
},
"Track an ecommerce transaction event": function () {
var orderId = 'ak0008';
var totalValue = 50;
var taxValue = 6;
var shipping = 0;
var city = 'Phoenix';
var state = 'Arizona';
var country = 'USA';
var currency = 'USD';
var expected = {
e: 'tr',
tr_id: orderId,
tr_tt: totalValue,
tr_tx: taxValue,
tr_sh: shipping,
tr_ci: city,
tr_st: state,
tr_co: country,
tr_cu: currency
};
compare(tracker.trackEcommerceTransaction(orderId, null, totalValue, taxValue, shipping, city, state, country, currency), expected, 'A transaction event should be tracked correctly');
},
"Track an ecommerce transaction item event": function () {
var orderId = 'ak0008';
var sku = '4q345';
var price = 17;
var quantity = 2;
var name = 'red shoes';
var category = 'clothing';
var currency = 'USD';
var expected = {
e: 'ti',
ti_id: orderId,
ti_sk: sku,
ti_nm: name,
ti_ca: category,
ti_pr: price,
ti_qu: quantity,
ti_cu: currency
};
compare(tracker.trackEcommerceTransactionItem(orderId, sku, name, category, price, quantity, currency), expected, 'A transaction item event should be tracked correctly');
},
"Track an unstructured event": function () {
var inputJson = {
schema: 'iglu:com.acme/user/jsonschema/1-0-1',
data: {
name: 'Eric'
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackUnstructEvent(inputJson), expected, 'An unstructured event should be tracked correctly');
},
"Track a self-describing event": function () {
var inputJson = {
schema: 'iglu:com.acme/user/jsonschema/1-0-1',
data: {
name: 'Eric'
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackSelfDescribingEvent(inputJson), expected, 'A self-describing event should be tracked correctly');
},
"Track a link click": function () {
var targetUrl = 'http://www.example.com';
var elementId = 'first header';
var elementClasses = ['header'];
var elementContent = 'link';
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/link_click/jsonschema/1-0-1',
data: {
targetUrl: targetUrl,
elementId: elementId,
elementClasses: elementClasses,
elementContent: elementContent
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackLinkClick(targetUrl, elementId, elementClasses, null, elementContent), expected, 'A link click should be tracked correctly');
},
"Track a screen view": function () {
var name = 'intro';
var id = '7398-4352-5345-1950';
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/screen_view/jsonschema/1-0-0',
data: {
name: name,
id: id
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackScreenView(name, id), expected, 'A screen view should be tracked correctly');
},
"Track an ad impression": function () {
var impressionId = 'a0e8f8780ab3';
var costModel = 'cpc';
var cost = 0.5;
var targetUrl = 'http://adsite.com';
var bannerId = '123';
var zoneId = 'zone-14';
var advertiserId = 'ad-company';
var campaignId = 'campaign-7592';
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/ad_impression/jsonschema/1-0-0',
data: {
impressionId: impressionId,
costModel: costModel,
cost: cost,
targetUrl: targetUrl,
bannerId: bannerId,
zoneId: zoneId,
advertiserId: advertiserId,
campaignId: campaignId
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackAdImpression(impressionId, costModel, cost, targetUrl, bannerId, zoneId, advertiserId, campaignId), expected, 'An ad impression should be tracked correctly');
},
"Track an ad click": function () {
var targetUrl = 'http://adsite.com';
var clickId = 'click-321';
var costModel = 'cpc';
var cost = 0.5;
var bannerId = '123';
var zoneId = 'zone-14';
var impressionId = 'a0e8f8780ab3';
var advertiserId = 'ad-company';
var campaignId = 'campaign-7592';
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/ad_click/jsonschema/1-0-0',
data: {
targetUrl: targetUrl,
clickId: clickId,
costModel: costModel,
cost: cost,
bannerId: bannerId,
zoneId: zoneId,
impressionId: impressionId,
advertiserId: advertiserId,
campaignId: campaignId
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackAdClick(targetUrl, clickId, costModel, cost, bannerId, zoneId, impressionId, advertiserId, campaignId), expected, 'An ad click should be tracked correctly');
},
"Track an ad conversion": function () {
var conversionId = 'conversion-59';
var costModel = 'cpc';
var cost = 0.5;
var category = 'cat';
var action = 'act';
var property = 'prop';
var initialValue = 7;
var advertiserId = 'ad-company';
var campaignId = 'campaign-7592';
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/ad_conversion/jsonschema/1-0-0',
data: {
conversionId: conversionId,
costModel: costModel,
cost: cost,
category: category,
action: action,
property: property,
initialValue: initialValue,
advertiserId: advertiserId,
campaignId: campaignId
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackAdConversion(conversionId, costModel, cost, category, action, property, initialValue, advertiserId, campaignId), expected, 'An ad conversion should be tracked correctly');
},
"Track a social interaction": function () {
var action = 'like';
var network = 'facebook';
var target = 'status-0000345345';
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/social_interaction/jsonschema/1-0-0',
data: {
action: action,
network: network,
target: target
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackSocialInteraction(action, network, target), expected);
},
"Track an add-to-cart event": function () {
var sku = '4q345';
var unitPrice = 17;
var quantity = 2;
var name = 'red shoes';
var category = 'clothing';
var currency = 'USD';
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/add_to_cart/jsonschema/1-0-0',
data: {
sku: sku,
name: name,
category: category,
unitPrice: unitPrice,
quantity: quantity,
currency: currency
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackAddToCart(sku, name, category, unitPrice, quantity, currency), expected);
},
"Track a remove-from-cart event": function () {
var sku = '4q345';
var unitPrice = 17;
var quantity = 2;
var name = 'red shoes';
var category = 'clothing';
var currency = 'USD';
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/remove_from_cart/jsonschema/1-0-0',
data: {
sku: sku,
name: name,
category: category,
unitPrice: unitPrice,
quantity: quantity,
currency: currency
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackRemoveFromCart(sku, name, category, unitPrice, quantity, currency), expected);
},
"Track a form focus event": function () {
var formId = "parent";
var elementId = "child";
var nodeName = "INPUT";
var type = "text";
var elementClasses = ["important"];
var value = "male";
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/focus_form/jsonschema/1-0-0',
data: {
formId: formId,
elementId: elementId,
nodeName: nodeName,
type: type,
elementClasses: elementClasses,
value: value
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackFormFocusOrChange('focus_form', formId, elementId, nodeName, type, elementClasses, value), expected);
},
"Track a form change event": function () {
var formId = "parent";
var elementId = "child";
var nodeName = "INPUT";
var type = "text";
var elementClasses = ["important"];
var value = "male";
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/change_form/jsonschema/1-0-0',
data: {
formId: formId,
elementId: elementId,
nodeName: nodeName,
type: type,
elementClasses: elementClasses,
value: value
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackFormFocusOrChange('change_form', formId, elementId, nodeName, type, elementClasses, value), expected);
},
"Track a form submission event": function () {
var formId = "parent";
var formClasses = ["formclass"];
var elements = [{
name: "gender",
value: "male",
nodeName: "INPUT",
type: "text"
}];
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/submit_form/jsonschema/1-0-0',
data: {
formId: formId,
formClasses: formClasses,
elements: elements
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackFormSubmission(formId, formClasses, elements), expected);
},
"Track a site seach event": function () {
var terms = ["javascript", "development"];
var filters = {
"safeSearch": true,
"category": "books"
};
var totalResults = 35;
var pageResults = 10;
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/site_search/jsonschema/1-0-0',
data: {
terms: terms,
filters: filters,
totalResults: totalResults,
pageResults: pageResults
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
})
};
compare(tracker.trackSiteSearch(terms, filters, totalResults, pageResults), expected);
},
"Track a consent withdrawn event": function () {
var all = false;
var id = 1234;
var version = 2;
var name = "consent_form";
var description = "user withdraws consent for form";
var tstamp = 1000000000000;
var inputContext = [{
schema: 'iglu:com.snowplowanalytics.snowplow/consent_document/jsonschema/1-0-0',
data: {
id: id,
version: version,
name: name,
description: description
}
}];
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/consent_withdrawn/jsonschema/1-0-0',
data: {
all: all
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
}),
co: JSON.stringify({
schema: 'iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0',
data: inputContext
})
};
compare(tracker.trackConsentWithdrawn(all, id, version, name, description, [], tstamp), expected);
},
"Track a consent granted event": function () {
var id = 1234;
var version = 2;
var name = "consent_form";
var description = "user grants consent for form";
var tstamp = 1000000000000;
var expiry = "01 January, 1970 00:00:00 Universal Time (UTC)";
var inputContext = [{
schema: 'iglu:com.snowplowanalytics.snowplow/consent_document/jsonschema/1-0-0',
data: {
id: id,
version: version,
name: name,
description: description
}
}];
var inputJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/consent_granted/jsonschema/1-0-0',
data: {
expiry: expiry
}
};
var expected = {
e: 'ue',
ue_pr: JSON.stringify({
schema: unstructEventSchema,
data: inputJson
}),
co: JSON.stringify({
schema: 'iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0',
data: inputContext
})
};
compare(tracker.trackConsentGranted(id, version, name, description, expiry, [], tstamp), expected);
},
"Track a page view with custom context": function () {
var url = 'http://www.example.com';
var page = 'title page';
var inputContext = [{
schema: 'iglu:com.acme/user/jsonschema/1-0-0',
data: {
userType: 'tester',
userName: 'Jon'
}
}];
var expected = {
e: 'pv',
url: url,
page: page,
co: JSON.stringify({
schema: 'iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0',
data: inputContext
})
};
compare(tracker.trackPageView(url, page, null, inputContext), expected, 'A custom context should be attached correctly');
},
"Track a page view with a timestamp": function () {
var tstamp = 1000000000000;
assert.strictEqual(tracker.trackPageView('http://www.example.com', null, null, null, tstamp).build()['dtm'], '1000000000000', 'A timestamp should be attached correctly');
},
"Add individual name-value pairs to the payload": function () {
var tracker = core.trackerCore(false);
var url = 'http://www.example.com';
var expected = {
e: 'pv',
url: url,
tna: 'cf',
tv: 'js-2.0.0'
};
tracker.addPayloadPair('tna', 'cf');
tracker.addPayloadPair('tv', 'js-2.0.0');
compare(tracker.trackPageView(url), expected, 'Payload name-value pairs should be set correctly');
},
"Add a dictionary of name-value pairs to the payload": function () {
var tracker = core.trackerCore(false);
var url = 'http://www.example.com';
var expected = {
e: 'pv',
url: url,
tv: 'js-2.0.0',
tna: 'cf',
aid: 'cf325'
};
tracker.addPayloadPair('tv', 'js-2.0.0');
tracker.addPayloadDict({
tna: 'cf',
aid: 'cf325'
});
compare(tracker.trackPageView(url), expected, 'Payload name-value pairs should be set correctly');
},
"Reset payload name-value pairs": function () {
var tracker = core.trackerCore(false);
var url = 'http://www.example.com';
var expected = {
e: 'pv',
url: url,
tna: 'cf'
};
tracker.addPayloadPair('tna', 'mistake');
tracker.resetPayloadPairs({'tna': 'cf'});
compare(tracker.trackPageView(url), expected, 'Payload name-value pairs should be reset correctly');
},
"Execute a callback": function () {
var callbackTarget;
var tracker = core.trackerCore(false, function (payload) {
callbackTarget = payload;
});
var url = 'http://www.example.com';
var expected = {
e: 'pv',
url: url
};
tracker.trackPageView(url);
compare(callbackTarget, expected, 'The callback should be executed correctly');
},
"Use setter methods": function () {
var tracker = core.trackerCore(false);
tracker.setTrackerVersion('js-3.0.0');
tracker.setTrackerNamespace('cf1');
tracker.setAppId('my-app');
tracker.setPlatform('web');
tracker.setUserId('jacob');
tracker.setScreenResolution(400, 200);
tracker.setViewport(500, 800);
tracker.setColorDepth(24);
tracker.setTimezone('Europe London');
tracker.setIpAddress('37.151.33.154');
var url = 'http://www.example.com';
var page = 'title page';
var expected = {
e: 'pv',
url: url,
page: page,
tna: 'cf1',
tv: 'js-3.0.0',
aid: 'my-app',
p: 'web',
uid: 'jacob',
res: '400x200',
vp: '500x800',
cd: 24,
tz: 'Europe London',
ip: '37.151.33.154'
};
compare(tracker.trackPageView(url, page), expected, 'setXXX methods should work correctly');
},
"Set true timestamp": function () {
var url = 'http://www.example.com';
var page = 'title page';
var result = tracker.trackPageView(url, page, null, null, { type: 'ttm', value: 1477403862 }).build();
assert('ttm' in result, 'ttm should be attached');
assert.strictEqual(result['ttm'] , '1477403862', 'ttm should be attached as is');
assert(!('dtm' in result), 'dtm should absent');
},
"Set device timestamp as ADT": function () {
var inputJson = {
schema: 'iglu:com.acme/user/jsonschema/1-0-1',
data: {
name: 'Eric'
}
};
// Object structure should be enforced by typesystem
var result = tracker.trackSelfDescribingEvent(inputJson, [inputJson], {type: 'dtm', value: 1477403869}).build();
assert('dtm' in result, 'dtm should be attached');
assert.strictEqual(result['dtm'] , '1477403869', 'dtm should be attached as is');
assert(!('ttm' in result), 'ttm should absent');
}
});
});

View file

@ -0,0 +1,121 @@
/*
* JavaScript tracker for Snowplow: tests/payload.js
*
* Copyright (c) 2014-2016 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
define([
'intern!object',
'intern/chai!assert',
'intern/dojo/node!../../lib/payload.js'
], function (registerSuite, assert, payload) {
var sampleJson = {
schema: 'iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0',
data: [
{
schema: "iglu:com.example_company/page/jsonschema/1-2-1",
data: {
pageType: 'test',
lastUpdated: new Date(Date.UTC(2014, 1, 26))
}
},
{
schema: "iglu:com.example_company/user/jsonschema/2-0-0",
data: {
userType: 'tester'
}
}
]
};
var expectedPayloads = [
{co: '{"schema":"iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0","data":[{"schema":"iglu:com.example_company/page/jsonschema/1-2-1","data":{"pageType":"test","lastUpdated":"2014-02-26T00:00:00.000Z"}},{"schema":"iglu:com.example_company/user/jsonschema/2-0-0","data":{"userType":"tester"}}]}'},
{cx: 'eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvdy9jb250ZXh0cy9qc29uc2NoZW1hLzEtMC0wIiwiZGF0YSI6W3sic2NoZW1hIjoiaWdsdTpjb20uZXhhbXBsZV9jb21wYW55L3BhZ2UvanNvbnNjaGVtYS8xLTItMSIsImRhdGEiOnsicGFnZVR5cGUiOiJ0ZXN0IiwibGFzdFVwZGF0ZWQiOiIyMDE0LTAyLTI2VDAwOjAwOjAwLjAwMFoifX0seyJzY2hlbWEiOiJpZ2x1OmNvbS5leGFtcGxlX2NvbXBhbnkvdXNlci9qc29uc2NoZW1hLzItMC0wIiwiZGF0YSI6eyJ1c2VyVHlwZSI6InRlc3RlciJ9fV19'}
];
registerSuite({
name: 'Payload test',
'Identify a JSON': function () {
var json = {
'name': 'john',
'properties': {
'age': 30,
'languages': ['English', 'French']
}
};
assert.strictEqual(payload.isJson(json), true, 'JSON should be identified');
},
'Identify a non-JSON': function () {
var nonJson = function () {};
assert.strictEqual(payload.isJson(nonJson), false, 'Non-JSON should be rejected');
},
'Identify an empty JSON': function () {
var emptyJson = {};
assert.strictEqual(payload.isNonEmptyJson(emptyJson), false, '{} should be identified as empty');
},
'Build a payload': function () {
var sb = payload.payloadBuilder(false);
sb.add('e', 'pv');
sb.add('tv', 'js-2.0.0');
assert.deepEqual(sb.build(), {e: 'pv', tv: 'js-2.0.0'}, 'Individual name-value pairs should be added to the payload');
},
'Do not add undefined values to a payload': function () {
var sb = payload.payloadBuilder(false);
sb.add('e', undefined);
assert.deepEqual(sb.build(), {}, 'Undefined values should not be added to the payload');
},
'Do not add null values to a payload': function () {
var sb = payload.payloadBuilder(false);
sb.add('e', null);
assert.deepEqual(sb.build(), {}, 'Null values should not be added to the payload');
},
'Add a dictionary of name-value pairs to the payload': function () {
var sb = payload.payloadBuilder(false);
sb.addDict({
'e': 'pv',
'tv': 'js-2.0.0'
});
assert.deepEqual(sb.build(), {e: 'pv', tv: 'js-2.0.0'}, 'A dictionary of name-value pairs should be added to the payload');
},
'Add a JSON to the payload': function () {
var sb = payload.payloadBuilder(false);
sb.addJson('cx', 'co', sampleJson);
assert.deepEqual(sb.build(), expectedPayloads[0], 'JSON should be added correctly');
},
'Add a base 64 encoded JSON to the payload': function () {
var sb = payload.payloadBuilder(true);
sb.addJson('cx', 'co', sampleJson);
assert.deepEqual(sb.build(), expectedPayloads[1], 'JSON should be encoded correctly');
}
});
});

View file

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"isolatedModules": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"declaration": false,
"noImplicitAny": false,
"noImplicitUseStrict": false,
"strictNullChecks": true,
"removeComments": true,
"noLib": false,
"preserveConstEnums": true,
"suppressImplicitAnyIndexErrors": true
},
"exclude": [
"node_modules"
],
"compileOnSave": true,
"buildOnSave": false,
"atom": {
"rewriteTsconfig": false
}
}

View file

@ -0,0 +1,5 @@
{
"rules": {
"indent": [true, "tabs"]
}
}

View file

@ -0,0 +1,4 @@
# Assembled files
bundle.js
snowplow.js
sp.js

View file

@ -0,0 +1,164 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<!--
! Copyright (c) 2012 Snowplow Analytics Ltd. All rights reserved.
!
! This program is licensed to you under the Apache License Version 2.0,
! and you may not use this file except in compliance with the Apache License Version 2.0.
! You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
!
! Unless required by applicable law or agreed to in writing,
! software distributed under the Apache License Version 2.0 is distributed on an
! "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
! See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
-->
<html>
<head>
<title>Asynchronous ad tracking examples for snowplow.js</title>
</head>
<p>For each banner, viewing that banner fires an ad impression event and clicking it fires an ad click event.</p>
<p>Click the first banner and on the button that appears to fire an ad conversion event.</p>
<body>
<h1>Asynchronous ad tracking examples for snowplow.js</h1>
<p>
<img src="images/banner-1.png" id="banner1">
Banner ID 23, zone ID 7, campaign ID 12, advertiser ID 201
<!-- Snowplow starts plowing -->
<script type="text/javascript">
(function(imageId){
// Randomly generate tracker namespace to prevent clashes
var rnd = Math.random().toString(36).substring(2);
;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","//d1fc8wv8zag5ca.cloudfront.net/2.10.0/sp.js","adTracker"));
window.adTracker('newTracker', rnd, 'd3rkrsqld9gmqf.cloudfront.net', {
'encodeBase64': false
});
window.adTracker('trackAdImpression:' + rnd,
'67965967893', // impressionId
'cpa', // costModel - 'cpa', 'cpc', or 'cpm'
10, // cost - requires costModel
'http://www.example.com', // targetUrl
'23', // bannerId
'7', // zoneId
'201', // advertiserId
'12' // campaignId
);
function clickHandler() {
window.adTracker('trackAdClick:' + rnd,
'http://www.example.com', // targetUrl
'12243253', // clickId
'cpm', // costModel
0.5, // cost
'23', // bannerId
'7', // zoneId
'67965967893', // impressionId - the same as in trackAdImpression
'201', // advertiserId
'12' // campaignId
);
var button = document.createElement('button');
button.innerHTML = 'Click for an ad conversion';
document.body.appendChild(button);
button.onclick = function() {
window.adTracker('trackAdConversion:' + rnd,
'743560297', // conversionId
'cpm', // costModel
10, // cost
'ecommerce', // category
'purchase', // action
'', // property
99, // initialValue - how much the conversion is initially worth
'201', // advertiserId
'12' // campaignId
);
}
}
document.getElementById(imageId).addEventListener('click', clickHandler);
}('banner1'));
</script>
<!-- Snowplow stops plowing -->
</p>
<p>
<img src="images/banner-2.png" id="banner2">
Banner ID 127, no campaign or advertiser ID set
<!-- Snowplow starts plowing -->
<script type="text/javascript">
(function(imageId){
var rnd = Math.random().toString(36).substring(2);
;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","//d1fc8wv8zag5ca.cloudfront.net/2.10.0/sp.js","adTracker"));
window.adTracker('newTracker', rnd, 'd3rkrsqld9gmqf.cloudfront.net', {
'encodeBase64': false
});
window.adTracker('trackAdImpression:' + rnd, '17320923496', 'cpm', 6.5, 'http://www.example.com', '127', '', '');
function clickHandler() {
window.adTracker('trackAdClick:' + rnd, 'http://www.example.com', '', 'cpm', 6.5, '127', '', '', '17320923496', '');
}
document.getElementById(imageId).addEventListener('click', clickHandler);
}('banner2'));
</script>
<!-- Snowplow stops plowing -->
</p>
<p>
<img src="images/banner-3.png" id="banner3">
Banner ID 56ea2819ffc7da75f7e, no campaign, advertiser or user ID set
<!-- Snowplow starts plowing -->
<script type="text/javascript">
(function(imageId){
var rnd = Math.random().toString(36).substring(2);
;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","//d1fc8wv8zag5ca.cloudfront.net/2.10.0/sp.js","adTracker"));
window.adTracker('newTracker', rnd, 'd3rkrsqld9gmqf.cloudfront.net', {
'encodeBase64': false
});
window.adTracker('trackAdImpression:' + rnd, '', 'cpc', '', 'http://www.acme.com', '56ea2819ffc7da75f7e', '', '');
function clickHandler() {
window.adTracker('trackAdClick:' + rnd, 'http://www.acme.com', '', 'cpc', 0.2, '56ea2819ffc7da75f7e');
}
document.getElementById(imageId).addEventListener('click', clickHandler);
}('banner3'));
</script>
<!-- Snowplow stops plowing -->
</p>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,277 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<!--
! Copyright (c) 2012-2013 Snowplow Analytics Ltd. All rights reserved.
!
! This program is licensed to you under the Apache License Version 2.0,
! and you may not use this file except in compliance with the Apache License Version 2.0.
! You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
!
! Unless required by applicable law or agreed to in writing,
! software distributed under the Apache License Version 2.0 is distributed on an
! "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
! See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
-->
<html>
<head>
<title>Large asynchronous website/webapp examples for snowplow.js</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<!-- Snowplow starts plowing -->
<script type="text/javascript">
;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","//d1fc8wv8zag5ca.cloudfront.net/2.10.0/sp.js","snowplow_1"));
window.snowplow_1('newTracker', 'cf', 'd3rkrsqld9gmqf.cloudfront.net', { // Initialise a tracker
encodeBase64: false, // Default is true
appId: 'CFe23a', // Site ID can be anything you want. Set it if you're tracking more than one site in this account
platform: 'mob',
crossDomainLinker: function (link) {return link.id === 'legal'}, // Add duid and timestamp to the link with id "legal"
contexts: {
webPage: true,
performanceTiming: true
}
});
window.snowplow_1('setUserId', 'alex 123'); // Business-defined user ID
//window.snowplow_1('setUserIdFromLocation', 'id'); // To test this, reload the page with ?id=xxx
//window.snowplow_1('setUserIdFromCookie', '_sp_id.4209') // Test this using Firefox because Chrome doesn't allow local cookies.
window.snowplow_1('setCustomUrl', '/overridden-url/'); // Override the page URL
window.snowplow_1('enableActivityTracking', 10, 10); // Ping every 10 seconds after 10 seconds
window.snowplow_1('enableLinkClickTracking', {blacklist: ['barred']}, true); // Track clicks on links whose class is not "barred"
window.snowplow_1('enableGeolocationContext');
window.snowplow_1('enableFormTracking', {
forms: {
whitelist: ['formclass']
},
fields: {
blacklist: ['comments']
}
}); // Track changes to form fields and form submissions
// window.snowplow_1('trackPageView', 'Async Test'); // Track the page view with custom title
window.snowplow_1('trackPageView', null, [ // Auto-set page title; add page context
{
schema: "iglu:com.example_company/page/jsonschema/1-2-1",
data: {
pageType: 'test',
lastUpdated: new Date(2014,1,26)
}
},
{
schema: "iglu:com.example_company/user/jsonschema/2-0-0",
data: {
userType: 'tester',
}
}
]);
// Callback executed once the tracker loads which logs the user fingerprint
window.snowplow_1(function () {
console.log(this.cf.getUserFingerprint());
});
</script>
<!-- Snowplow stops plowing -->
<!-- Another Snowplow user on the same page only tracks page views, page pings, and link clicks
They use a different Snowplow function name ("snowplow_2"), preventing clashes -->
<script type="text/javascript">
;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","//d1fc8wv8zag5ca.cloudfront.net/2.10.0/sp.js","snowplow_2"));
window.snowplow_2('newTracker', 'cf', 'd3rkrsqld9gmqf.cloudfront.net', { // Initialise a tracker
encodeBase64: false, // Default is true
appId: 'CFe23a', // Site ID can be anything you want. Set it if you're tracking more than one site in this account
platform: 'mob',
contexts: {
performanceTiming: true,
gaCookies: true
}
});
window.snowplow_2('setUserId', 'alex 123'); // Business-defined user ID
//window.snowplow_1('setUserIdFromLocation', 'id'); // To test this, reload the page with ?id=xxx
//window.snowplow_1('setUserIdFromCookie', '_sp_id.4209') // Test this using Firefox because Chrome doesn't allow local cookies.
window.snowplow_2('setCustomUrl', '/overridden-url/'); // Override the page URL
window.snowplow_2('enableActivityTracking', 10, 10); // Ping every 10 seconds after 10 seconds
window.snowplow_2('enableLinkClickTracking', null, true); // Track clicks on all links
// window.snowplow_1('trackPageView', 'Async Test'); // Track the page view with custom title
window.snowplow_2('trackPageView', null);
</script>
<!-- Snowplow stops plowing -->
<!-- Example events -->
<script type="text/javascript">
function playMix() {
alert("Playing a DJ mix");
window.snowplow_1('trackStructEvent', 'Mixes', 'Play', 'MRC/fabric-0503-mix', '', '0.0');
}
function addProduct() {
alert("Adding a product to basket");
window.snowplow_1('trackStructEvent', 'Checkout', 'Add', 'ASO01043', 'blue:xxl', '2.0', [{
schema: 'iglu:com.acme_company/user/jsonschema/1-0-0',
data: { fbUid: '123456 x' }
}] );
}
function viewProduct() {
alert("Viewing a product");
window.snowplow_1('trackUnstructEvent', {
schema: 'iglu:com.acme_company/viewed_product/jsonschema/5-0-0',
data: {
productId: 'ASO01043',
category: 'Dresses',
brand: 'ACME',
returning: true,
price: 49.95,
sizes: ['xs', 's', 'l', 'xl', 'xxl'],
availableSince: new Date(2013,3,7)
}
});
}
function addEcommerceTransaction() {
alert('Adding an ecommerce transaction');
var orderId = 'order-123';
// addTrans sets up the transaction, should be called first.
window.snowplow_1('addTrans',
orderId, // order ID - required
'', // affiliation or store name
'8000', // total - required
'', // tax
'', // shipping
'', // city
'', // state or province
'', // country
'JPY' // currency
);
// addItem might be called for each item in the shopping cart,
// or not at all.
window.snowplow_1('addItem',
orderId, // order ID - required
'1001', // SKU - required
'Blue t-shirt', // product name
'', // category
'2000', // unit price - required
'2', // quantity - required
'JPY', // currency
[{ // context
schema: "iglu:com.example_company/products/jsonschema/1-0-0",
data: {
"launchDate": new Date(2013,3,7)
}
}]);
window.snowplow_1('addItem',
orderId, // order ID - required
'1002', // SKU - required
'Red shoes', // product name
'', // category
'4000', // unit price - required
'1', // quantity - required
'JPY' // currency
);
// trackTrans sends the transaction to Snowplow tracking servers.
// Must be called last to commit the transaction.
window.snowplow_1('trackTrans');
}
function addToCart () {
alert('adding some items to the cart');
window.snowplow_1('trackAddToCart',
'Blue hat', // SKU - required
'clothing', // category
500, // price - required
2, // quantity added - required
'GBP' // currency
);
}
function removeFromCart () {
alert('removing an item from the cart');
window.snowplow_1('trackRemoveFromCart', '1008', 'Blue hat', 'clothing', '500', '1', 'GBP');
}
function likeVideo () {
alert('tracking a social interaction')
window.snowplow_1('trackSocialInteraction',
'like', // action - required
'facebook', // network - required
'video-003254' // target of action
);
}
function searchSite () {
alert('Searching the site');
window.snowplow_1('trackSiteSearch',
['event', 'analytics'], // search terms - required
{ // search filters
category: 'books',
safeSearch: true
},
15, // number of results returned
10 // number of results displayed on page
);
}
</script>
</head>
<body>
<h1>Large_asynchronous_examples_for_snowplow.js</h1>
<h2>This shows how two users can simultaneously use Snowplow on the same page without any clash</h2>
<p>Warning: if your browser's Do Not Track feature is enabled and respectDoNotTrack is enabled, all tracking will be prevented.</p>
<p>If you are viewing the page using a file URL, you must edit the script URL in the Snowplow tag to include an http scheme. Otherwise a file scheme will be inferred and the page will attempt to load sp.js from the local filesystem..</p>
<p>Press the buttons below to trigger individual tracking events:<br>
<button type="button" onclick="playMix()">Play a mix</button><br>
<button type="button" onclick="addProduct()">Add a product</button><br>
<button type="button" onclick="viewProduct()">View a product</button><br>
<button type="button" onclick="addEcommerceTransaction()">Add an ecommerce transaction</button><br>
<button type="button" onclick="addToCart()">Add item to cart</button><br>
<button type="button" onclick="removeFromCart()">Remove item from cart</button><br>
<button type="button" onclick="likeVideo()">Like a video</button><br>
<button type="button" onclick="searchSite()">Search the site</button>
</p>
<p>
Middle click the links to trigger link click tracking:<br>
<!-- crossDomain functionality correctly alters a link with a multiple question marks and a fragment -->
<a href=http://en.wikipedia.org/wiki/Main_Page?a=b?c#fragment id=legal target="_blank" class="link out">Link</a>
<br>
<a href=http://en.wikipedia.org/wiki/Main_Page id=illegal class="class barred">Link ignored by one user</a>
</p>
<p>
Fill in the form fields and click the button to trigger form tracking:
<form id="signup" onsubmit="return false" class="formclass">
<fieldset>
<input name="gender" type="radio" value="Male"></input>Male<br>
<input name="gender" type="radio" value="Female"></input>Female<br>
<select name="color">
<option>Red</option>
<option>Green</option>
<option>Mauve</option>
</select><br>
<input name="name" type="text" placeholder="Name"></input><br>
<textarea name="other" placeholder="Other information"></textarea><br>
<textarea name="comments" placeholder="Comments"></textarea><br>
<input type="checkbox" name="email" value="on">Add me to the mailing list
</fieldset>
<input type="submit" value="Submit"></input>
</form>
</p>
</body>
</html>

View file

@ -0,0 +1,191 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<!--
! Copyright (c) 2012-2013 Snowplow Analytics Ltd. All rights reserved.
!
! This program is licensed to you under the Apache License Version 2.0,
! and you may not use this file except in compliance with the Apache License Version 2.0.
! You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
!
! Unless required by applicable law or agreed to in writing,
! software distributed under the Apache License Version 2.0 is distributed on an
! "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
! See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
-->
<html>
<head>
<title>Medium asynchronous website/webapp examples for snowplow.js</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<!-- Snowplow starts plowing -->
<script type="text/javascript">
// Load sp.js
;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","//d1fc8wv8zag5ca.cloudfront.net/2.10.0/sp.js","new_name_here"));
window.new_name_here('newTracker', 'cf', 'd3rkrsqld9gmqf.cloudfront.net', {
encodeBase64: false,
appId: 'CFe23a',
platform: 'mob',
cookieName: '_cf_3489563', // Cookie names should be unique to prevent mix-ups
contexts: {
webPage: true,
performanceTiming: true
}
});
// This second tracker namespace could have a different collector as its endpoint
window.new_name_here('newTracker', 'uri', 'collector.acme-company.com', {
encodeBase64: true,
appId: '8D4920',
userFingerprintSeed: 857463967, // Sets a custom seed for the "uri" tracker to generate the user fingerprint; the "cf" tracker will still use the default fingerprint
cookieName: '_uri_73457324',
});
window.new_name_here('setUserId', 'alex 123'); // Business-defined user ID
//window.new_name_here('setUserIdFromLocation', 'id'); // To test this, reload the page with ?id=xxx
//window.new_name_here('setUserIdFromCookie', '_sp_id.4209') // Test this using Firefox because Chrome doesn't allow local cookies.
window.new_name_here('setCustomUrl', '/overridden-url/'); // Override the page URL
window.new_name_here('enableActivityTracking', 10, 10); // Ping every 10 seconds after 10 seconds
window.new_name_here('enableLinkClickTracking:cf', {whitelist: ['inbound', 'out']}, true, true); // Track clicks on links whose class is not "barred"
window.new_name_here('enableLinkClickTracking:uri', {
filter: function(link){
return link.id === 'legal';
}
}, true);
// window.new_name_here('trackPageView', 'Async Test'); // Track the page view with custom title
window.new_name_here('trackPageView', null, [ // Auto-set page title; add page context
{
schema: "iglu:com.example_company/page/jsonschema/1-2-1",
data: {
pageType: 'test',
lastUpdated: new Date(2014,1,26)
}
},
{
schema: "iglu:com.example_company/user/jsonschema/2-0-0",
data: {
userType: 'tester',
}
}
]);
</script>
<!-- Snowplow stops plowing -->
<!-- Example events -->
<script type="text/javascript">
function playMix() {
alert("Playing a DJ mix");
// No tracker choice is specified, so both trackers will track the Play event
window.new_name_here('trackStructEvent', 'Mixes', 'Play', 'MRC/fabric-0503-mix', '', '0.0');
}
function addProduct() {
alert("Adding a product to basket");
// Only the "cf" tracker will tracke the Checkout event
window.new_name_here('trackStructEvent:cf', 'Checkout', 'Add', 'ASO01043', 'blue:xxl', '2.0', [{
schema: 'iglu:com.acme_company/user/jsonschema/1-0-0',
data: { fbUid: '123456 x' }
}] );
}
function viewProduct() {
alert("Viewing a product");
// Only the "uri" tracker will track the Viewed Product event
window.new_name_here('trackUnstructEvent:uri', {
schema: 'iglu:com.acme_company/viewed_product/jsonschema/5-0-0',
data: {
productId: 'ASO01043',
category: 'Dresses',
brand: 'ACME',
returning: true,
price: 49.95,
sizes: ['xs', 's', 'l', 'xl', 'xxl'],
availableSince: new Date(2013,3,7)
}
});
}
function addEcommerceTransaction() {
alert('Adding an ecommerce transaction');
var orderId = 'order-123';
// Both trackers will track the transaction due to the ":cf;uri" suffix
// addTrans sets up the transaction, should be called first.
window.new_name_here('addTrans:cf;uri',
orderId, // order ID - required
'', // affiliation or store name
'8000', // total - required
'', // tax
'', // shipping
'', // city
'', // state or province
'', // country
'JPY' // currency
);
// addItem might be called for each item in the shopping cart,
// or not at all.
window.new_name_here('addItem:cf;uri',
orderId, // order ID - required
'1001', // SKU - required
'Blue t-shirt', // product name
'', // category
'2000', // unit price - required
'2', // quantity - required
'JPY', // currency
[{ // context
schema: "iglu:com.example_company/products/jsonschema/1-0-0",
data: {
"launchDate": new Date(2013,3,7)
}
}]);
window.new_name_here('addItem:cf;uri',
orderId, // order ID - required
'1002', // SKU - required
'Red shoes', // product name
'', // category
'4000', // unit price - required
'1', // quantity - required
'JPY' // currency
);
// trackTrans sends the transaction to Snowplow tracking servers.
// Must be called last to commit the transaction.
window.new_name_here('trackTrans');
}
</script>
</head>
<body>
<h1>Medium_asynchronous_examples_for_snowplow.js</h1>
<p>Two tracker namespaces are instantiated on this page.</p>
<p>Warning: if your browser's Do Not Track feature is enabled and respectDoNotTrack is enabled, all tracking will be prevented.</p>
<p>If you are viewing the page using a file URL, you must edit the script URL in the Snowplow tag to include an http scheme. Otherwise a file scheme will be inferred and the page will attempt to load sp.js from the local filesystem..</p>
<p>Press the buttons below to trigger individual tracking events:<br>
<button type="button" onclick="playMix()">Play a mix</button><br>
<button type="button" onclick="addProduct()">Add a product</button><br>
<button type="button" onclick="viewProduct()">View a product</button><br>
<button type="button" onclick="addEcommerceTransaction()">Add an ecommerce transaction</button>
</p>
<a href=http://en.wikipedia.org/wiki/Main_Page id=legal class="link out">Both trackers will track this link</a>
<br>
<a href=http://en.wikipedia.org/wiki/Main_Page id=semilegal class="class inbound">Only the cf tracker will track this link</a>
<br>
<a href=http://en.wikipedia.org/wiki/Main_Page id=illegal class="class barred">Neither tracker will track this link</a>
</body>
</html>

View file

@ -0,0 +1,168 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<!--
! Copyright (c) 2012-2013 Snowplow Analytics Ltd. All rights reserved.
!
! This program is licensed to you under the Apache License Version 2.0,
! and you may not use this file except in compliance with the Apache License Version 2.0.
! You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
!
! Unless required by applicable law or agreed to in writing,
! software distributed under the Apache License Version 2.0 is distributed on an
! "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
! See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
-->
<html>
<head>
<title>Small asynchronous website/webapp examples for snowplow.js</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<!-- Snowplow starts plowing -->
<script type="text/javascript">
;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","//d1fc8wv8zag5ca.cloudfront.net/2.10.0/sp.js","snowplow"));
window.snowplow('newTracker', 'cf', 'd3rkrsqld9gmqf.cloudfront.net', { // Initialise a tracker
encodeBase64: false, // Default is true
appId: 'CFe23a', // Site ID can be anything you want. Set it if you're tracking more than one site in this account
platform: 'mob',
contexts: {
webPage: true,
gaCookies: true,
performanceTiming: true
}
});
window.snowplow('setUserId', 'alex 123'); // Business-defined user ID
//window.snowplow('setUserIdFromLocation', 'id'); // To test this, reload the page with ?id=xxx
//window.snowplow('setUserIdFromCookie', '_sp_id.4209') // Test this using Firefox because Chrome doesn't allow local cookies.
window.snowplow('setCustomUrl', '/overridden-url/'); // Override the page URL
window.snowplow('enableActivityTracking', 10, 10); // Ping every 10 seconds after 10 seconds
window.snowplow('enableLinkClickTracking', {blacklist: ['barred']}, true); // Track clicks on links whose class is not "barred"
// window.snowplow('trackPageView', 'Async Test'); // Track the page view with custom title
window.snowplow('trackPageView', null, [ // Auto-set page title; add page context
{
schema: "iglu:com.example_company/page/jsonschema/1-2-1",
data: {
pageType: 'test',
lastUpdated: new Date(2014,1,26)
}
},
{
schema: "iglu:com.example_company/user/jsonschema/2-0-0",
data: {
userType: 'tester',
}
}
]);
</script>
<!-- Snowplow stops plowing -->
<!-- Example events -->
<script type="text/javascript">
function playMix() {
alert("Playing a DJ mix");
window.snowplow('trackStructEvent', 'Mixes', 'Play', 'MRC/fabric-0503-mix', '', '0.0');
}
function addProduct() {
alert("Adding a product to basket");
window.snowplow('trackStructEvent', 'Checkout', 'Add', 'ASO01043', 'blue:xxl', '2.0', [{
schema: 'iglu:com.acme_company/user/jsonschema/1-0-0',
data: { fbUid: '123456 x' }
}] );
}
function viewProduct() {
alert("Viewing a product");
window.snowplow('trackUnstructEvent', {
schema: 'iglu:com.acme_company/viewed_product/jsonschema/5-0-0',
data: {
productId: 'ASO01043',
category: 'Dresses',
brand: 'ACME',
returning: true,
price: 49.95,
sizes: ['xs', 's', 'l', 'xl', 'xxl'],
availableSince: new Date(2013,3,7)
}
});
}
function addEcommerceTransaction() {
alert('Adding an ecommerce transaction');
var orderId = 'order-123';
// addTrans sets up the transaction, should be called first.
window.snowplow('addTrans',
orderId, // order ID - required
'', // affiliation or store name
'8000', // total - required
'', // tax
'', // shipping
'', // city
'', // state or province
'', // country
'JPY' // currency
);
// addItem might be called for each item in the shopping cart,
// or not at all.
window.snowplow('addItem',
orderId, // order ID - required
'1001', // SKU - required
'Blue t-shirt', // product name
'', // category
'2000', // unit price - required
'2', // quantity - required
'JPY', // currency
[{ // context
schema: "iglu:com.example_company/products/jsonschema/1-0-0",
data: {
"launchDate": new Date(2013,3,7)
}
}]);
window.snowplow('addItem',
orderId, // order ID - required
'1002', // SKU - required
'Red shoes', // product name
'', // category
'4000', // unit price - required
'1', // quantity - required
'JPY' // currency
);
// trackTrans sends the transaction to Snowplow tracking servers.
// Must be called last to commit the transaction.
window.snowplow('trackTrans');
}
</script>
</head>
<body>
<h1>Small_asynchronous_examples_for_snowplow.js</h1>
<p>Warning: if your browser's Do Not Track feature is enabled and respectDoNotTrack is enabled, all tracking will be prevented.</p>
<p>If you are viewing the page using a file URL, you must edit the script URL in the Snowplow tag to include an http scheme. Otherwise a file scheme will be inferred and the page will attempt to load sp.js from the local filesystem..</p>
<p>Press the buttons below to trigger individual tracking events:<br>
<button type="button" onclick="playMix()">Play a mix</button><br>
<button type="button" onclick="addProduct()">Add a product</button><br>
<button type="button" onclick="viewProduct()">View a product</button><br>
<button type="button" onclick="addEcommerceTransaction()">Add an ecommerce transaction</button>
</p>
<a href=http://en.wikipedia.org/wiki/Main_Page id=legal target="_blank" class="link out">Link</a>
<br>
<a href=http://en.wikipedia.org/wiki/Main_Page id=illegal class="class barred">Ignored link</a>
</body>
</html>

View file

@ -0,0 +1,74 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<!--
! Copyright (c) 2012-2013 Snowplow Analytics Ltd. All rights reserved.
!
! This program is licensed to you under the Apache License Version 2.0,
! and you may not use this file except in compliance with the Apache License Version 2.0.
! You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
!
! Unless required by applicable law or agreed to in writing,
! software distributed under the Apache License Version 2.0 is distributed on an
! "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
! See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
-->
<html>
<head>
<title>Synchronous website/webapp examples for snowplow.js</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<!-- Snowplow starts plowing -->
<script type="text/javascript">
var spSrc = ('https:' == document.location.protocol ? 'https' : 'http') + '://d1fc8wv8zag5ca.cloudfront.net/2.10.0/sp.js';
document.write(unescape("%3Cscript src='" + spSrc + "' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var snowplowTracker = Snowplow.getTrackerCf('d3rkrsqld9gmqf');
snowplowTracker.enableLinkTracking();
// snowplowTracker.enableActivityTracking(10, 10); Doesn't work yet, see https://github.com/snowplow/snowplow/issues/233
snowplowTracker.encodeBase64(false); // Default is true
snowplowTracker.trackPageView();
} catch ( err ) {}
</script>
<!-- Snowplow stops plowing -->
<!-- Example events -->
<script type="text/javascript">
function playMix() {
alert("Playing a DJ mix");
snowplowTracker.trackStructEvent('Mixes', 'Play', 'MrC/fabric-0503-mix', '','0.0');
}
function addProduct() {
alert("Adding a product to basket");
snowplowTracker.trackStructEvent('Checkout', 'Add', 'ASO01043', 'blue:xxl', '2.0');
}
function viewProduct() {
alert("Viewing a product");
snowplowTracker.trackUnstructEvent('Viewed Product',
{
product_id: 'ASO01043',
category: 'Dresses',
brand: 'ACME',
returning: true,
price: 49.95,
sizes: ['xs', 's', 'l', 'xl', 'xxl'],
available_since$dt: new Date(2013,3,7)
}
);
}
</script>
</head>
<body>
<h1>Synchronous examples for snowplow.js</h1>
<p>Warning: if your browser's Do Not Track feature is enabled and respectDoNotTrack is enabled, all tracking will be prevented.</p>
<p>If you are viewing the page using a file URL, you must edit the script URL in the Snowplow tag to include an http scheme. Otherwise a file scheme will be inferred and the page will attempt to load sp.js from the local filesystem..</p>
<p>Press the buttons below to trigger individual tracking events:<br>
<button type="button" onclick="playMix()">Play a mix</button><br>
<button type="button" onclick="addProduct()">Add a product</button><br>
<button type="button" onclick="viewProduct()">View product</button>
</p>
</body>
</html>

View file

@ -0,0 +1,15 @@
#!/usr/bin/env bash
cd core
grunt
cd ..
grunt local
if [[ "$(uname)" == "Darwin" ]]; then
(sleep 1 && open http:127.0.0.1:8000/integration.html)&
elif [[ "$(expr substr $(uname -s) 1 5)" == "Linux" ]]; then
(sleep 1 && xdg-open http:127.0.0.1:8000/integration.html)&
elif [[ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]]; then
:
elif [[ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]]; then
:
fi
python3 ./tests/local/http-server.py

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,49 @@
{
"name": "snowplow-tracker",
"version": "2.10.0",
"dependencies": {
"snowplow-tracker-core": "0.7.0",
"browser-cookie-lite": "0.3.1",
"jstimezonedetect": "1.0.5",
"murmurhash": "0.0.2",
"sha1": "git://github.com/pvorb/node-sha1.git#910081c83f3661507d9d89e66e3f38d8b59d5559",
"uuid": "2.0.3"
},
"devDependencies": {
"@babel/core": "^7.1.2",
"@babel/preset-env": "^7.1.0",
"JSON": "1.0.0",
"grunt": "^1.0.3",
"grunt-aws": "0.6.2",
"grunt-babel": "^8.0.0",
"lodash": "^4.17.11",
"grunt-browserify": "^5.3.0",
"grunt-contrib-concat": "^1.0.1",
"grunt-contrib-uglify": "^4.0.0",
"intern": "3.3.2",
"js-base64": "^2.4.9",
"semver": "4.3.2"
},
"contributors": [
"Alex Dean",
"Simon Andersson",
"Anthon Pang",
"Fred Blundun",
"Joshua Beemster",
"Michael Hadam"
],
"description": "JavaScript tracker for Snowplow",
"repository": {
"type": "git",
"url": "https://github.com/snowplow/snowplow-javascript-tracker.git"
},
"bugs": "https://github.com/snowplow/snowplow-javascript-tracker/issues",
"keywords": [
"tracking",
"web analytics",
"events",
"open source"
],
"license": "Simplified BSD",
"private": true
}

View file

@ -0,0 +1,127 @@
/*
* JavaScript tracker for Snowplow: tracker.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2016 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var isFunction = require('lodash/isFunction'),
helpers = require('./lib/helpers'),
object = typeof exports !== 'undefined' ? exports : this,
windowAlias = window;
object.errorManager = function (core) {
/**
* Send error as self-describing event
*
* @param message string Message appeared in console
* @param filename string Source file (not used)
* @param lineno number Line number
* @param colno number Column number (not used)
* @param error Error error object (not present in all browsers)
* @param contexts Array of custom contexts
*/
function track(message, filename, lineno, colno, error, contexts) {
var stack = (error && error.stack) ? error.stack : null;
core.trackSelfDescribingEvent({
schema: 'iglu:com.snowplowanalytics.snowplow/application_error/jsonschema/1-0-1',
data: {
programmingLanguage: "JAVASCRIPT",
message: message || "JS Exception. Browser doesn't support ErrorEvent API",
stackTrace: stack,
lineNumber: lineno,
lineColumn: colno,
fileName: filename
}
}, contexts)
}
/**
* Attach custom contexts using `contextAdder`
*
*
* @param contextsAdder function to get details from internal browser state
* @returns {Array} custom contexts
*/
function sendError(errorEvent, commonContexts, contextsAdder) {
var contexts;
if (isFunction(contextsAdder)) {
contexts = commonContexts.concat(contextsAdder(errorEvent));
} else {
contexts = commonContexts;
}
track(errorEvent.message, errorEvent.filename, errorEvent.lineno, errorEvent.colno, errorEvent.error, contexts)
}
return {
/**
* Track unhandled exception.
* This method supposed to be used inside try/catch block or with window.onerror
* (contexts won't be attached), but NOT with `addEventListener` - use
* `enableErrorTracker` for this
*
* @param message string Message appeared in console
* @param filename string Source file (not used)
* @param lineno number Line number
* @param colno number Column number (not used)
* @param error Error error object (not present in all browsers)
* @param contexts Array of custom contexts
*/
trackError: track,
/**
* Curried function to enable tracking of unhandled exceptions.
* Listen for `error` event and
*
* @param filter Function ErrorEvent => Bool to check whether error should be tracker
* @param contextsAdder Function ErrorEvent => Array<Context> to add custom contexts with
* internal state based on particular error
*/
enableErrorTracking: function (filter, contextsAdder, contexts) {
/**
* Closure callback to filter, contextualize and track unhandled exceptions
*
* @param errorEvent ErrorEvent passed to event listener
*/
function captureError (errorEvent) {
if (isFunction(filter) && filter(errorEvent) || filter == null) {
sendError(errorEvent, contexts, contextsAdder)
}
}
helpers.addEventListener(windowAlias, 'error', captureError, true);
}
}
};

View file

@ -0,0 +1,189 @@
/*
* JavaScript tracker for Snowplow: forms.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var forEach = require('lodash/forEach'),
filter = require('lodash/filter'),
find = require('lodash/find'),
helpers = require('./lib/helpers'),
object = typeof exports !== 'undefined' ? exports : this;
/**
* Object for handling automatic form tracking
*
* @param object core The tracker core
* @param string trackerId Unique identifier for the tracker instance, used to mark tracked elements
* @param function contextAdder Function to add common contexts like PerformanceTiming to all events
* @return object formTrackingManager instance
*/
object.getFormTrackingManager = function (core, trackerId, contextAdder) {
// Tag names of mutable elements inside a form
var innerElementTags = ['textarea', 'input', 'select'];
// Used to mark elements with event listeners
var trackingMarker = trackerId + 'form';
// Filter to determine which forms should be tracked
var formFilter = function () { return true };
// Filter to determine which form fields should be tracked
var fieldFilter = function () { return true };
// Default function applied to all elements, optionally overridden by transform field
var fieldTransform = function (x) { return x };
/*
* Get an identifier for a form, input, textarea, or select element
*/
function getFormElementName(elt) {
return elt[find(['name', 'id', 'type', 'nodeName'], function (propName) {
// If elt has a child whose name is "id", that element will be returned
// instead of the actual id of elt unless we ensure that a string is returned
return elt[propName] && typeof elt[propName] === 'string';
})];
}
/*
* Identifies the parent form in which an element is contained
*/
function getParentFormName(elt) {
while (elt && elt.nodeName && elt.nodeName.toUpperCase() !== 'HTML' && elt.nodeName.toUpperCase() !== 'FORM') {
elt = elt.parentNode;
}
if (elt && elt.nodeName && elt.nodeName.toUpperCase() === 'FORM') {
return getFormElementName(elt);
}
}
/*
* Returns a list of the input, textarea, and select elements inside a form along with their values
*/
function getInnerFormElements(elt) {
var innerElements = [];
forEach(innerElementTags, function (tagname) {
var trackedChildren = filter(elt.getElementsByTagName(tagname), function (child) {
return child.hasOwnProperty(trackingMarker);
});
forEach(trackedChildren, function (child) {
if (child.type === 'submit') {
return;
}
var elementJson = {
name: getFormElementName(child),
value: child.value,
nodeName: child.nodeName
};
if (child.type && child.nodeName.toUpperCase() === 'INPUT') {
elementJson.type = child.type;
}
if ((child.type === 'checkbox' || child.type === 'radio') && !child.checked) {
elementJson.value = null;
}
innerElements.push(elementJson);
});
});
return innerElements;
}
/*
* Return function to handle form field change event
*/
function getFormChangeListener(event_type, context) {
return function (e) {
var elt = e.target;
var type = (elt.nodeName && elt.nodeName.toUpperCase() === 'INPUT') ? elt.type : null;
var value = (elt.type === 'checkbox' && !elt.checked) ? null : fieldTransform(elt.value);
if (event_type === 'change_form' || (type !== 'checkbox' && type !== 'radio')) {
core.trackFormFocusOrChange(event_type, getParentFormName(elt), getFormElementName(elt), elt.nodeName, type, helpers.getCssClasses(elt), value, contextAdder(helpers.resolveDynamicContexts(context, elt, type, value)));
}
};
}
/*
* Return function to handle form submission event
*/
function getFormSubmissionListener(context) {
return function (e) {
var elt = e.target;
var innerElements = getInnerFormElements(elt);
forEach(innerElements, function (innerElement) {
innerElement.value = fieldTransform(innerElement.value);
});
core.trackFormSubmission(getFormElementName(elt), helpers.getCssClasses(elt), innerElements, contextAdder(helpers.resolveDynamicContexts(context, elt, innerElements)));
};
}
return {
/*
* Configures form tracking: which forms and fields will be tracked, and the context to attach
*/
configureFormTracking: function (config) {
if (config) {
formFilter = helpers.getFilter(config.forms, true);
fieldFilter = helpers.getFilter(config.fields, false);
fieldTransform = helpers.getTransform(config.fields);
}
},
/*
* Add submission event listeners to all form elements
* Add value change event listeners to all mutable inner form elements
*/
addFormListeners: function (context) {
forEach(document.getElementsByTagName('form'), function (form) {
if (formFilter(form) && !form[trackingMarker]) {
forEach(innerElementTags, function (tagname) {
forEach(form.getElementsByTagName(tagname), function (innerElement) {
if (fieldFilter(innerElement) && !innerElement[trackingMarker] && innerElement.type.toLowerCase() !== 'password') {
helpers.addEventListener(innerElement, 'focus', getFormChangeListener('focus_form', context), false);
helpers.addEventListener(innerElement, 'change', getFormChangeListener('change_form', context), false);
innerElement[trackingMarker] = true;
}
});
});
helpers.addEventListener(form, 'submit', getFormSubmissionListener(context));
form[trackingMarker] = true;
}
});
}
};
};

View file

@ -0,0 +1,60 @@
/*
* JavaScript tracker for Snowplow: tracker.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2018 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var object = typeof exports !== 'undefined' ? exports : this;
var makeSafe = function (fn) {
return function () {
try {
return fn.apply(this, arguments);
} catch (ex) {
// TODO: Debug mode
}
};
};
exports.productionize = function (methods) {
let safeMethods = {};
if (typeof methods === 'object' && methods !== null) {
Object.getOwnPropertyNames(methods).forEach(
function (val, idx, array) {
if (typeof methods[val] === 'function') {
safeMethods[val] = makeSafe(methods[val]);
}
}
);
}
return safeMethods;
};

View file

@ -0,0 +1,193 @@
/*
* JavaScript tracker for Snowplow: queue.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
;(function() {
var
map = require('lodash/map'),
isUndefined = require('lodash/isUndefined'),
isFunction = require('lodash/isFunction'),
helpers = require('./lib/helpers'),
object = typeof exports !== 'undefined' ? exports : this; // For eventual node.js environment support
/************************************************************
* Proxy object
* - this allows the caller to continue push()'ing to _snaq
* after the Tracker has been initialized and loaded
************************************************************/
object.InQueueManager = function(TrackerConstructor, version, mutSnowplowState, asyncQueue, functionName) {
// Page view ID should be shared between all tracker instances
var trackerDictionary = {};
/**
* Get an array of trackers to which a function should be applied.
*
* @param array names List of namespaces to use. If empty, use all namespaces.
*/
function getNamedTrackers(names) {
var namedTrackers = [];
if (!names || names.length === 0) {
namedTrackers = map(trackerDictionary);
} else {
for (var i = 0; i < names.length; i++) {
if (trackerDictionary.hasOwnProperty(names[i])) {
namedTrackers.push(trackerDictionary[names[i]]);
} else {
helpers.warn('Warning: Tracker namespace "' + names[i] + '" not configured');
}
}
}
if (namedTrackers.length === 0) {
helpers.warn('Warning: No tracker configured');
}
return namedTrackers;
}
/**
* Legacy support for input of the form _snaq.push(['setCollectorCf', 'd34uzc5hjrimh8'])
*
* @param string f Either 'setCollectorCf' or 'setCollectorUrl'
* @param string endpoint
* @param string namespace Optional tracker name
*
* TODO: remove this in 2.1.0
*/
function legacyCreateNewNamespace(f, endpoint, namespace) {
helpers.warn(f + ' is deprecated. Set the collector when a new tracker instance using newTracker.');
var name;
if (isUndefined(namespace)) {
name = 'sp';
} else {
name = namespace;
}
createNewNamespace(name);
trackerDictionary[name][f](endpoint);
}
/**
* Initiate a new tracker namespace
*
* @param string namespace
* @param string endpoint Of the form d3rkrsqld9gmqf.cloudfront.net
*/
function createNewNamespace(namespace, endpoint, argmap) {
argmap = argmap || {};
if (!trackerDictionary.hasOwnProperty(namespace)) {
trackerDictionary[namespace] = new TrackerConstructor(functionName, namespace, version, mutSnowplowState, argmap);
trackerDictionary[namespace].setCollectorUrl(endpoint);
} else {
helpers.warn('Tracker namespace ' + namespace + ' already exists.');
}
}
/**
* Output an array of the form ['functionName', [trackerName1, trackerName2, ...]]
*
* @param string inputString
*/
function parseInputString(inputString) {
var separatedString = inputString.split(':'),
extractedFunction = separatedString[0],
extractedNames = (separatedString.length > 1) ? separatedString[1].split(';') : [];
return [extractedFunction, extractedNames];
}
/**
* apply wrapper
*
* @param array parameterArray An array comprising either:
* [ 'methodName', optional_parameters ]
* or:
* [ functionObject, optional_parameters ]
*/
function applyAsyncFunction() {
var i, j, f, parameterArray, input, parsedString, names, namedTrackers;
// Outer loop in case someone push'es in zarg of arrays
for (i = 0; i < arguments.length; i += 1) {
parameterArray = arguments[i];
// Arguments is not an array, so we turn it into one
input = Array.prototype.shift.call(parameterArray);
// Custom callback rather than tracker method, called with trackerDictionary as the context
if (isFunction(input)) {
input.apply(trackerDictionary, parameterArray);
continue;
}
parsedString = parseInputString(input);
f = parsedString[0];
names = parsedString[1];
if (f === 'newTracker') {
createNewNamespace(parameterArray[0], parameterArray[1], parameterArray[2]);
continue;
}
if ((f === 'setCollectorCf' || f === 'setCollectorUrl') && (!names || names.length === 0)) {
legacyCreateNewNamespace(f, parameterArray[0], parameterArray[1]);
continue;
}
namedTrackers = getNamedTrackers(names);
for (j = 0; j < namedTrackers.length; j++) {
namedTrackers[j][f].apply(namedTrackers[j], parameterArray);
}
}
}
// We need to manually apply any events collected before this initialization
for (var i = 0; i < asyncQueue.length; i++) {
applyAsyncFunction(asyncQueue[i]);
}
return {
push: applyAsyncFunction
};
};
}());

View file

@ -0,0 +1,53 @@
/*
* JavaScript tracker for Snowplow: init.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// Snowplow Asynchronous Queue
/*
* Get the name of the global input function
*/
var snowplow = require('./snowplow'),
queueName,
queue,
windowAlias = window;
if (windowAlias.GlobalSnowplowNamespace && windowAlias.GlobalSnowplowNamespace.length > 0) {
queueName = windowAlias.GlobalSnowplowNamespace.shift();
queue = windowAlias[queueName];
queue.q = new snowplow.Snowplow(queue.q, queueName);
} else {
windowAlias._snaq = windowAlias._snaq || [];
windowAlias._snaq = new snowplow.Snowplow(windowAlias._snaq, '_snaq');
}

View file

@ -0,0 +1,254 @@
/*
* JavaScript tracker for Snowplow: detectors.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
;(function() {
var
isFunction = require('lodash/isFunction'),
isUndefined = require('lodash/isUndefined'),
murmurhash3_32_gc = require('murmurhash').v3,
tz = require('jstimezonedetect').jstz.determine(),
cookie = require('browser-cookie-lite'),
object = typeof exports !== 'undefined' ? exports : this, // For eventual node.js environment support
windowAlias = window,
navigatorAlias = navigator,
screenAlias = screen,
documentAlias = document;
/*
* Checks whether sessionStorage is available, in a way that
* does not throw a SecurityError in Firefox if "always ask"
* is enabled for cookies (https://github.com/snowplow/snowplow/issues/163).
*/
object.hasSessionStorage = function () {
try {
return !!windowAlias.sessionStorage;
} catch (e) {
return true; // SecurityError when referencing it means it exists
}
};
/*
* Checks whether localStorage is available, in a way that
* does not throw a SecurityError in Firefox if "always ask"
* is enabled for cookies (https://github.com/snowplow/snowplow/issues/163).
*/
object.hasLocalStorage = function () {
try {
return !!windowAlias.localStorage;
} catch (e) {
return true; // SecurityError when referencing it means it exists
}
};
/*
* Checks whether localStorage is accessible
* sets and removes an item to handle private IOS5 browsing
* (http://git.io/jFB2Xw)
*/
object.localStorageAccessible = function() {
var mod = 'modernizr';
if (!object.hasLocalStorage()) {
return false;
}
try {
windowAlias.localStorage.setItem(mod, mod);
windowAlias.localStorage.removeItem(mod);
return true;
} catch(e) {
return false;
}
};
/*
* Does browser have cookies enabled (for this site)?
*/
object.hasCookies = function(testCookieName) {
var cookieName = testCookieName || 'testcookie';
if (isUndefined(navigatorAlias.cookieEnabled)) {
cookie.cookie(cookieName, '1');
return cookie.cookie(cookieName) === '1' ? '1' : '0';
}
return navigatorAlias.cookieEnabled ? '1' : '0';
};
/**
* JS Implementation for browser fingerprint.
* Does not require any external resources.
* Based on https://github.com/carlo/jquery-browser-fingerprint
* @return {number} 32-bit positive integer hash
*/
object.detectSignature = function(hashSeed) {
var fingerprint = [
navigatorAlias.userAgent,
[ screenAlias.height, screenAlias.width, screenAlias.colorDepth ].join("x"),
( new Date() ).getTimezoneOffset(),
object.hasSessionStorage(),
object.hasLocalStorage()
];
var plugins = [];
if (navigatorAlias.plugins)
{
for(var i = 0; i < navigatorAlias.plugins.length; i++)
{
if (navigatorAlias.plugins[i]) {
var mt = [];
for(var j = 0; j < navigatorAlias.plugins[i].length; j++) {
mt.push([navigatorAlias.plugins[i][j].type, navigatorAlias.plugins[i][j].suffixes]);
}
plugins.push([navigatorAlias.plugins[i].name + "::" + navigatorAlias.plugins[i].description, mt.join("~")]);
}
}
}
return murmurhash3_32_gc(fingerprint.join("###") + "###" + plugins.sort().join(";"), hashSeed);
};
/*
* Returns visitor timezone
*/
object.detectTimezone = function() {
return (typeof (tz) === 'undefined') ? '' : tz.name();
};
/**
* Gets the current viewport.
*
* Code based on:
* - http://andylangton.co.uk/articles/javascript/get-viewport-size-javascript/
* - http://responsejs.com/labs/dimensions/
*/
object.detectViewport = function() {
var e = windowAlias, a = 'inner';
if (!('innerWidth' in windowAlias)) {
a = 'client';
e = documentAlias.documentElement || documentAlias.body;
}
var width = e[a+'Width'];
var height = e[a+'Height'];
if (width >= 0 && height >= 0) {
return width + 'x' + height;
} else {
return null;
}
};
/**
* Gets the dimensions of the current
* document.
*
* Code based on:
* - http://andylangton.co.uk/articles/javascript/get-viewport-size-javascript/
*/
object.detectDocumentSize = function() {
var de = documentAlias.documentElement, // Alias
be = documentAlias.body,
// document.body may not have rendered, so check whether be.offsetHeight is null
bodyHeight = be ? Math.max(be.offsetHeight, be.scrollHeight) : 0;
var w = Math.max(de.clientWidth, de.offsetWidth, de.scrollWidth);
var h = Math.max(de.clientHeight, de.offsetHeight, de.scrollHeight, bodyHeight);
return isNaN(w) || isNaN(h) ? '' : w + 'x' + h;
};
/**
* Returns browser features (plugins, resolution, cookies)
*
* @param boolean useCookies Whether to test for cookies
* @param string testCookieName Name to use for the test cookie
* @return Object containing browser features
*/
object.detectBrowserFeatures = function(useCookies, testCookieName) {
var i,
mimeType,
pluginMap = {
// document types
pdf: 'application/pdf',
// media players
qt: 'video/quicktime',
realp: 'audio/x-pn-realaudio-plugin',
wma: 'application/x-mplayer2',
// interactive multimedia
dir: 'application/x-director',
fla: 'application/x-shockwave-flash',
// RIA
java: 'application/x-java-vm',
gears: 'application/x-googlegears',
ag: 'application/x-silverlight'
},
features = {};
// General plugin detection
if (navigatorAlias.mimeTypes && navigatorAlias.mimeTypes.length) {
for (i in pluginMap) {
if (Object.prototype.hasOwnProperty.call(pluginMap, i)) {
mimeType = navigatorAlias.mimeTypes[pluginMap[i]];
features[i] = (mimeType && mimeType.enabledPlugin) ? '1' : '0';
}
}
}
// Safari and Opera
// IE6/IE7 navigator.javaEnabled can't be aliased, so test directly
if (navigatorAlias.constructor === window.Navigator &&
typeof navigatorAlias.javaEnabled !== 'unknown' &&
!isUndefined(navigatorAlias.javaEnabled) &&
navigatorAlias.javaEnabled()) {
features.java = '1';
}
// Firefox
if (isFunction(windowAlias.GearsFactory)) {
features.gears = '1';
}
// Other browser features
features.res = screenAlias.width + 'x' + screenAlias.height;
features.cd = screenAlias.colorDepth;
if (useCookies) {
features.cookie = object.hasCookies(testCookieName);
}
return features;
};
}());

View file

@ -0,0 +1,436 @@
/*
* JavaScript tracker for Snowplow: Snowplow.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
;(function () {
var
isString = require('lodash/isString'),
isUndefined = require('lodash/isUndefined'),
isObject = require('lodash/isObject'),
map = require('lodash/map'),
cookie = require('browser-cookie-lite'),
object = typeof exports !== 'undefined' ? exports : this; // For eventual node.js environment support
/**
* Cleans up the page title
*/
object.fixupTitle = function (title) {
if (!isString(title)) {
title = title.text || '';
var tmp = document.getElementsByTagName('title');
if (tmp && !isUndefined(tmp[0])) {
title = tmp[0].text;
}
}
return title;
};
/**
* Extract hostname from URL
*/
object.getHostName = function (url) {
// scheme : // [username [: password] @] hostname [: port] [/ [path] [? query] [# fragment]]
var e = new RegExp('^(?:(?:https?|ftp):)/*(?:[^@]+@)?([^:/#]+)'),
matches = e.exec(url);
return matches ? matches[1] : url;
};
/**
* Fix-up domain
*/
object.fixupDomain = function (domain) {
var dl = domain.length;
// remove trailing '.'
if (domain.charAt(--dl) === '.') {
domain = domain.slice(0, dl);
}
// remove leading '*'
if (domain.slice(0, 2) === '*.') {
domain = domain.slice(1);
}
return domain;
};
/**
* Get page referrer. In the case of a single-page app,
* if the URL changes without the page reloading, pass
* in the old URL. It will be returned unless overriden
* by a "refer(r)er" parameter in the querystring.
*
* @param string oldLocation Optional.
* @return string The referrer
*/
object.getReferrer = function (oldLocation) {
var referrer = '';
var fromQs = object.fromQuerystring('referrer', window.location.href) ||
object.fromQuerystring('referer', window.location.href);
// Short-circuit
if (fromQs) {
return fromQs;
}
// In the case of a single-page app, return the old URL
if (oldLocation) {
return oldLocation;
}
try {
referrer = window.top.document.referrer;
} catch (e) {
if (window.parent) {
try {
referrer = window.parent.document.referrer;
} catch (e2) {
referrer = '';
}
}
}
if (referrer === '') {
referrer = document.referrer;
}
return referrer;
};
/**
* Cross-browser helper function to add event handler
*/
object.addEventListener = function (element, eventType, eventHandler, useCapture) {
if (element.addEventListener) {
element.addEventListener(eventType, eventHandler, useCapture);
return true;
}
if (element.attachEvent) {
return element.attachEvent('on' + eventType, eventHandler);
}
element['on' + eventType] = eventHandler;
};
/**
* Return value from name-value pair in querystring
*/
object.fromQuerystring = function (field, url) {
var match = new RegExp('^[^#]*[?&]' + field + '=([^&#]*)').exec(url);
if (!match) {
return null;
}
return decodeURIComponent(match[1].replace(/\+/g, ' '));
};
/*
* Find dynamic context generating functions and merge their results into the static contexts
* Combine an array of unchanging contexts with the result of a context-creating function
*
* @param {(object|function(...*): ?object)[]} dynamicOrStaticContexts Array of custom context Objects or custom context generating functions
* @param {...*} Parameters to pass to dynamic callbacks
*/
object.resolveDynamicContexts = function (dynamicOrStaticContexts) {
let params = Array.prototype.slice.call(arguments, 1);
return map(dynamicOrStaticContexts, function(context) {
if (typeof context === 'function') {
try {
return context.apply(null, params);
} catch (e) {
//TODO: provide warning
}
} else {
return context;
}
});
};
/**
* Only log deprecation warnings if they won't cause an error
*/
object.warn = function(message) {
if (typeof console !== 'undefined') {
console.warn('Snowplow: ' + message);
}
};
/**
* List the classes of a DOM element without using elt.classList (for compatibility with IE 9)
*/
object.getCssClasses = function (elt) {
return elt.className.match(/\S+/g) || [];
};
/**
* Check whether an element has at least one class from a given list
*/
function checkClass(elt, classList) {
var classes = object.getCssClasses(elt),
i;
for (i = 0; i < classes.length; i++) {
if (classList[classes[i]]) {
return true;
}
}
return false;
}
/**
* Convert a criterion object to a filter function
*
* @param object criterion Either {whitelist: [array of allowable strings]}
* or {blacklist: [array of allowable strings]}
* or {filter: function (elt) {return whether to track the element}
* @param boolean byClass Whether to whitelist/blacklist based on an element's classes (for forms)
* or name attribute (for fields)
*/
object.getFilter = function (criterion, byClass) {
// If the criterion argument is not an object, add listeners to all elements
if (Array.isArray(criterion) || !isObject(criterion)) {
return function () {
return true;
};
}
if (criterion.hasOwnProperty('filter')) {
return criterion.filter;
} else {
var inclusive = criterion.hasOwnProperty('whitelist');
var specifiedClasses = criterion.whitelist || criterion.blacklist;
if (!Array.isArray(specifiedClasses)) {
specifiedClasses = [specifiedClasses];
}
// Convert the array of classes to an object of the form {class1: true, class2: true, ...}
var specifiedClassesSet = {};
for (var i=0; i<specifiedClasses.length; i++) {
specifiedClassesSet[specifiedClasses[i]] = true;
}
if (byClass) {
return function (elt) {
return checkClass(elt, specifiedClassesSet) === inclusive;
};
} else {
return function (elt) {
return elt.name in specifiedClassesSet === inclusive;
};
}
}
};
/**
* Convert a criterion object to a transform function
*
* @param object criterion {transform: function (elt) {return the result of transform function applied to element}
*/
object.getTransform = function (criterion) {
if (!isObject(criterion)) {
return function(x) { return x };
}
if (criterion.hasOwnProperty('transform')) {
return criterion.transform;
} else {
return function(x) { return x };
}
return function(x) { return x };
};
/**
* Add a name-value pair to the querystring of a URL
*
* @param string url URL to decorate
* @param string name Name of the querystring pair
* @param string value Value of the querystring pair
*/
object.decorateQuerystring = function (url, name, value) {
var initialQsParams = name + '=' + value;
var hashSplit = url.split('#');
var qsSplit = hashSplit[0].split('?');
var beforeQuerystring = qsSplit.shift();
// Necessary because a querystring may contain multiple question marks
var querystring = qsSplit.join('?');
if (!querystring) {
querystring = initialQsParams;
} else {
// Whether this is the first time the link has been decorated
var initialDecoration = true;
var qsFields = querystring.split('&');
for (var i=0; i<qsFields.length; i++) {
if (qsFields[i].substr(0, name.length + 1) === name + '=') {
initialDecoration = false;
qsFields[i] = initialQsParams;
querystring = qsFields.join('&');
break;
}
}
if (initialDecoration) {
querystring = initialQsParams + '&' + querystring;
}
}
hashSplit[0] = beforeQuerystring + '?' + querystring;
return hashSplit.join('#');
};
/**
* Attempt to get a value from localStorage
*
* @param string key
* @return string The value obtained from localStorage, or
* undefined if localStorage is inaccessible
*/
object.attemptGetLocalStorage = function (key) {
try {
return localStorage.getItem(key);
} catch(e) {}
};
/**
* Attempt to write a value to localStorage
*
* @param string key
* @param string value
* @return boolean Whether the operation succeeded
*/
object.attemptWriteLocalStorage = function (key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch(e) {
return false;
}
};
/**
* Finds the root domain
*/
object.findRootDomain = function () {
var cookiePrefix = '_sp_root_domain_test_';
var cookieName = cookiePrefix + new Date().getTime();
var cookieValue = '_test_value_' + new Date().getTime();
var split = window.location.hostname.split('.');
var position = split.length - 1;
while (position >= 0) {
var currentDomain = split.slice(position, split.length).join('.');
cookie.cookie(cookieName, cookieValue, 0, '/', currentDomain);
if (cookie.cookie(cookieName) === cookieValue) {
// Clean up created cookie(s)
object.deleteCookie(cookieName, currentDomain);
var cookieNames = object.getCookiesWithPrefix(cookiePrefix);
for (var i = 0; i < cookieNames.length; i++) {
object.deleteCookie(cookieNames[i], currentDomain);
}
return currentDomain;
}
position -= 1;
}
// Cookies cannot be read
return window.location.hostname;
};
/**
* Checks whether a value is present within an array
*
* @param val The value to check for
* @param array The array to check within
* @return boolean Whether it exists
*/
object.isValueInArray = function (val, array) {
for (var i = 0; i < array.length; i++) {
if (array[i] === val) {
return true;
}
}
return false;
};
/**
* Deletes an arbitrary cookie by setting the expiration date to the past
*
* @param cookieName The name of the cookie to delete
* @param domainName The domain the cookie is in
*/
object.deleteCookie = function (cookieName, domainName) {
cookie.cookie(cookieName, '', -1, '/', domainName);
};
/**
* Fetches the name of all cookies beginning with a certain prefix
*
* @param cookiePrefix The prefix to check for
* @return array The cookies that begin with the prefix
*/
object.getCookiesWithPrefix = function (cookiePrefix) {
var cookies = document.cookie.split("; ");
var cookieNames = [];
for (var i = 0; i < cookies.length; i++) {
if (cookies[i].substring(0, cookiePrefix.length) === cookiePrefix) {
cookieNames.push(cookies[i]);
}
}
return cookieNames;
};
/**
* Parses an object and returns either the
* integer or undefined.
*
* @param obj The object to parse
* @return the result of the parse operation
*/
object.parseInt = function (obj) {
var result = parseInt(obj);
return isNaN(result) ? undefined : result;
};
/**
* Parses an object and returns either the
* number or undefined.
*
* @param obj The object to parse
* @return the result of the parse operation
*/
object.parseFloat = function (obj) {
var result = parseFloat(obj);
return isNaN(result) ? undefined : result;
}
}());

View file

@ -0,0 +1,101 @@
/*
* JavaScript tracker for Snowplow: proxies.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
;(function(){
var
helpers = require('./helpers'),
object = typeof exports !== 'undefined' ? exports : this;
/*
* Test whether a string is an IP address
*/
function isIpAddress(string) {
var IPRegExp = new RegExp('^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$');
return IPRegExp.test(string);
}
/*
* If the hostname is an IP address, look for text indicating
* that the page is cached by Yahoo
*/
function isYahooCachedPage(hostName) {
var
initialDivText,
cachedIndicator;
if (isIpAddress(hostName)) {
try {
initialDivText = document.body.children[0].children[0].children[0].children[0].children[0].children[0].innerHTML;
cachedIndicator = 'You have reached the cached page for';
return initialDivText.slice(0, cachedIndicator.length) === cachedIndicator;
} catch (e) {
return false;
}
}
}
/*
* Extract parameter from URL
*/
function getParameter(url, name) {
// scheme : // [username [: password] @] hostname [: port] [/ [path] [? query] [# fragment]]
var e = new RegExp('^(?:https?|ftp)(?::/*(?:[^?]+))([?][^#]+)'),
matches = e.exec(url),
result = helpers.fromQuerystring(name, matches[1]);
return result;
}
/*
* Fix-up URL when page rendered from search engine cache or translated page.
* TODO: it would be nice to generalise this and/or move into the ETL phase.
*/
object.fixupUrl = function (hostName, href, referrer) {
if (hostName === 'translate.googleusercontent.com') { // Google
if (referrer === '') {
referrer = href;
}
href = getParameter(href, 'u');
hostName = helpers.getHostName(href);
} else if (hostName === 'cc.bingj.com' || // Bing
hostName === 'webcache.googleusercontent.com' || // Google
isYahooCachedPage(hostName)) { // Yahoo (via Inktomi 74.6.0.0/16)
href = document.links[0].href;
hostName = helpers.getHostName(href);
}
return [hostName, href, referrer];
};
}());

View file

@ -0,0 +1,181 @@
/*
* JavaScript tracker for Snowplow: links.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var isUndefined = require('lodash/isUndefined'),
helpers = require('./lib/helpers'),
object = typeof exports !== 'undefined' ? exports : this;
/**
* Object for handling automatic link tracking
*
* @param object core The tracker core
* @param string trackerId Unique identifier for the tracker instance, used to mark tracked links
* @param function contextAdder Function to add common contexts like PerformanceTiming to all events
* @return object linkTrackingManager instance
*/
object.getLinkTrackingManager = function (core, trackerId, contextAdder) {
// Filter function used to determine whether clicks on a link should be tracked
var linkTrackingFilter,
// Whether pseudo clicks are tracked
linkTrackingPseudoClicks,
// Whether to track the innerHTML of clicked links
linkTrackingContent,
// The context attached to link click events
linkTrackingContext,
// Internal state of the pseudo click handler
lastButton,
lastTarget;
/*
* Process clicks
*/
function processClick(sourceElement, context) {
var parentElement,
tag,
elementId,
elementClasses,
elementTarget,
elementContent;
while ((parentElement = sourceElement.parentNode) !== null &&
!isUndefined(parentElement) && // buggy IE5.5
((tag = sourceElement.tagName.toUpperCase()) !== 'A' && tag !== 'AREA')) {
sourceElement = parentElement;
}
if (!isUndefined(sourceElement.href)) {
// browsers, such as Safari, don't downcase hostname and href
var originalSourceHostName = sourceElement.hostname || helpers.getHostName(sourceElement.href),
sourceHostName = originalSourceHostName.toLowerCase(),
sourceHref = sourceElement.href.replace(originalSourceHostName, sourceHostName),
scriptProtocol = new RegExp('^(javascript|vbscript|jscript|mocha|livescript|ecmascript|mailto):', 'i');
// Ignore script pseudo-protocol links
if (!scriptProtocol.test(sourceHref)) {
elementId = sourceElement.id;
elementClasses = helpers.getCssClasses(sourceElement);
elementTarget = sourceElement.target;
elementContent = linkTrackingContent ? sourceElement.innerHTML : null;
// decodeUrl %xx
sourceHref = unescape(sourceHref);
core.trackLinkClick(sourceHref, elementId, elementClasses, elementTarget, elementContent, contextAdder(helpers.resolveDynamicContexts(context, sourceElement)));
}
}
}
/*
* Return function to handle click event
*/
function getClickHandler(context) {
return function (evt) {
var button,
target;
evt = evt || window.event;
button = evt.which || evt.button;
target = evt.target || evt.srcElement;
// Using evt.type (added in IE4), we avoid defining separate handlers for mouseup and mousedown.
if (evt.type === 'click') {
if (target) {
processClick(target, context);
}
} else if (evt.type === 'mousedown') {
if ((button === 1 || button === 2) && target) {
lastButton = button;
lastTarget = target;
} else {
lastButton = lastTarget = null;
}
} else if (evt.type === 'mouseup') {
if (button === lastButton && target === lastTarget) {
processClick(target, context);
}
lastButton = lastTarget = null;
}
};
}
/*
* Add click listener to a DOM element
*/
function addClickListener(element) {
if (linkTrackingPseudoClicks) {
// for simplicity and performance, we ignore drag events
helpers.addEventListener(element, 'mouseup', getClickHandler(linkTrackingContext), false);
helpers.addEventListener(element, 'mousedown', getClickHandler(linkTrackingContext), false);
} else {
helpers.addEventListener(element, 'click', getClickHandler(linkTrackingContext), false);
}
}
return {
/*
* Configures link click tracking: how to filter which links will be tracked,
* whether to use pseudo click tracking, and what context to attach to link_click events
*/
configureLinkClickTracking: function (criterion, pseudoClicks, trackContent, context) {
linkTrackingContent = trackContent;
linkTrackingContext = context;
linkTrackingPseudoClicks = pseudoClicks;
linkTrackingFilter = helpers.getFilter(criterion, true);
},
/*
* Add click handlers to anchor and AREA elements, except those to be ignored
*/
addClickListeners: function () {
var linkElements = document.links,
i;
for (i = 0; i < linkElements.length; i++) {
// Add a listener to link elements which pass the filter and aren't already tracked
if (linkTrackingFilter(linkElements[i]) && !linkElements[i][trackerId]) {
addClickListener(linkElements[i]);
linkElements[i][trackerId] = true;
}
}
}
};
};

View file

@ -0,0 +1,369 @@
/*
* JavaScript tracker for Snowplow: out_queue.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
;(function() {
var
mapValues = require('lodash/mapValues'),
isString = require('lodash/isString'),
map = require('lodash/map'),
localStorageAccessible = require('./lib/detectors').localStorageAccessible,
helpers = require('./lib/helpers'),
object = typeof exports !== 'undefined' ? exports : this; // For eventual node.js environment support
/**
* Object handling sending events to a collector.
* Instantiated once per tracker instance.
*
* @param string functionName The Snowplow function name (used to generate the localStorage key)
* @param string namespace The tracker instance's namespace (used to generate the localStorage key)
* @param object mutSnowplowState Gives the pageUnloadGuard a reference to the outbound queue
* so it can unload the page when all queues are empty
* @param boolean useLocalStorage Whether to use localStorage at all
* @param string eventMethod if null will use 'beacon' otherwise can be set to 'post', 'get', or 'beacon' to force.
* @param int bufferSize How many events to batch in localStorage before sending them all.
* Only applies when sending POST requests and when localStorage is available.
* @param int maxPostBytes Maximum combined size in bytes of the event JSONs in a POST request
* @param string postPath The path where events are to be posted
* @return object OutQueueManager instance
*/
object.OutQueueManager = function (functionName, namespace, mutSnowplowState, useLocalStorage, eventMethod, postPath, bufferSize, maxPostBytes) {
var queueName,
executingQueue = false,
configCollectorUrl,
outQueue;
//Force to lower case if its a string
eventMethod = eventMethod.toLowerCase ? eventMethod.toLowerCase() : eventMethod;
// Use the Beacon API if eventMethod is set null, true, or 'beacon'.
var isBeaconRequested = (eventMethod === null) || (eventMethod === true) || (eventMethod === "beacon") || (eventMethod === "true");
// Fall back to POST or GET for browsers which don't support Beacon API
var isBeaconAvailable = Boolean(isBeaconRequested && navigator && navigator.sendBeacon);
var useBeacon = (isBeaconAvailable && isBeaconRequested);
// Use GET if specified
var isGetRequested = (eventMethod === "get");
// Use POST if specified
var isPostRequested = (eventMethod === "post");
// usePost acts like a catch all for POST methods - Beacon or XHR
var usePost = (isPostRequested || useBeacon) && !isGetRequested;
// Don't use POST for browsers which don't support CORS XMLHttpRequests (e.g. IE <= 9)
usePost = usePost && Boolean(window.XMLHttpRequest && ('withCredentials' in new XMLHttpRequest()));
// Resolve all options and capabilities and decide path
var path = usePost ? postPath : '/i';
bufferSize = (localStorageAccessible() && useLocalStorage && usePost && bufferSize) || 1;
// Different queue names for GET and POST since they are stored differently
queueName = ['snowplowOutQueue', functionName, namespace, usePost ? 'post2' : 'get'].join('_');
if (useLocalStorage) {
// Catch any JSON parse errors or localStorage that might be thrown
try {
// TODO: backward compatibility with the old version of the queue for POST requests
outQueue = JSON.parse(localStorage.getItem(queueName));
}
catch(e) {}
}
// Initialize to and empty array if we didn't get anything out of localStorage
if (!Array.isArray(outQueue)) {
outQueue = [];
}
// Used by pageUnloadGuard
mutSnowplowState.outQueues.push(outQueue);
if (usePost && bufferSize > 1) {
mutSnowplowState.bufferFlushers.push(function () {
if (!executingQueue) {
executeQueue();
}
});
}
/*
* Convert a dictionary to a querystring
* The context field is the last in the querystring
*/
function getQuerystring(request) {
var querystring = '?',
lowPriorityKeys = {'co': true, 'cx': true},
firstPair = true;
for (var key in request) {
if (request.hasOwnProperty(key) && !(lowPriorityKeys.hasOwnProperty(key))) {
if (!firstPair) {
querystring += '&';
} else {
firstPair = false;
}
querystring += encodeURIComponent(key) + '=' + encodeURIComponent(request[key]);
}
}
for (var contextKey in lowPriorityKeys) {
if (request.hasOwnProperty(contextKey) && lowPriorityKeys.hasOwnProperty(contextKey)) {
querystring += '&' + contextKey + '=' + encodeURIComponent(request[contextKey]);
}
}
return querystring;
}
/*
* Convert numeric fields to strings to match payload_data schema
*/
function getBody(request) {
var cleanedRequest = mapValues(request, function (v) {
return v.toString();
});
return {
evt: cleanedRequest,
bytes: getUTF8Length(JSON.stringify(cleanedRequest))
};
}
/**
* Count the number of bytes a string will occupy when UTF-8 encoded
* Taken from http://stackoverflow.com/questions/2848462/count-bytes-in-textarea-using-javascript/
*
* @param string s
* @return number Length of s in bytes when UTF-8 encoded
*/
function getUTF8Length(s) {
var len = 0;
for (var i = 0; i < s.length; i++) {
var code = s.charCodeAt(i);
if (code <= 0x7f) {
len += 1;
} else if (code <= 0x7ff) {
len += 2;
} else if (code >= 0xd800 && code <= 0xdfff) {
// Surrogate pair: These take 4 bytes in UTF-8 and 2 chars in UCS-2
// (Assume next char is the other [valid] half and just skip it)
len += 4; i++;
} else if (code < 0xffff) {
len += 3;
} else {
len += 4;
}
}
return len;
}
/*
* Queue an image beacon for submission to the collector.
* If we're not processing the queue, we'll start.
*/
function enqueueRequest (request, url) {
configCollectorUrl = url + path;
if (usePost) {
var body = getBody(request);
if (body.bytes >= maxPostBytes) {
helpers.warn("Event of size " + body.bytes + " is too long - the maximum size is " + maxPostBytes);
var xhr = initializeXMLHttpRequest(configCollectorUrl);
xhr.send(encloseInPayloadDataEnvelope(attachStmToEvent([body.evt])));
return;
} else {
outQueue.push(body);
}
} else {
outQueue.push(getQuerystring(request));
}
var savedToLocalStorage = false;
if (useLocalStorage) {
savedToLocalStorage = helpers.attemptWriteLocalStorage(queueName, JSON.stringify(outQueue));
}
if (!executingQueue && (!savedToLocalStorage || outQueue.length >= bufferSize)) {
executeQueue();
}
}
/*
* Run through the queue of image beacons, sending them one at a time.
* Stops processing when we run out of queued requests, or we get an error.
*/
function executeQueue () {
// Failsafe in case there is some way for a bad value like "null" to end up in the outQueue
while (outQueue.length && typeof outQueue[0] !== 'string' && typeof outQueue[0] !== 'object') {
outQueue.shift();
}
if (outQueue.length < 1) {
executingQueue = false;
return;
}
// Let's check that we have a Url to ping
if (!isString(configCollectorUrl)) {
throw "No Snowplow collector configured, cannot track";
}
executingQueue = true;
var nextRequest = outQueue[0];
if (usePost) {
var xhr = initializeXMLHttpRequest(configCollectorUrl);
// Time out POST requests after 5 seconds
var xhrTimeout = setTimeout(function () {
xhr.abort();
executingQueue = false;
}, 5000);
function chooseHowManyToExecute(q) {
var numberToSend = 0;
var byteCount = 0;
while (numberToSend < q.length) {
byteCount += q[numberToSend].bytes;
if (byteCount >= maxPostBytes) {
break;
} else {
numberToSend += 1;
}
}
return numberToSend;
}
// Keep track of number of events to delete from queue
var numberToSend = chooseHowManyToExecute(outQueue);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 400) {
for (var deleteCount = 0; deleteCount < numberToSend; deleteCount++) {
outQueue.shift();
}
if (useLocalStorage) {
helpers.attemptWriteLocalStorage(queueName, JSON.stringify(outQueue));
}
clearTimeout(xhrTimeout);
executeQueue();
} else if (xhr.readyState === 4 && xhr.status >= 400) {
clearTimeout(xhrTimeout);
executingQueue = false;
}
};
var batch = map(outQueue.slice(0, numberToSend), function (x) {
return x.evt;
});
if (batch.length > 0) {
var beaconStatus;
if (useBeacon) {
const headers = { type: 'application/json' };
const blob = new Blob([encloseInPayloadDataEnvelope(attachStmToEvent(batch))], headers);
beaconStatus = navigator.sendBeacon(configCollectorUrl, blob);
}
if (!useBeacon || !beaconStatus) {
xhr.send(encloseInPayloadDataEnvelope(attachStmToEvent(batch)));
}
}
} else {
var image = new Image(1, 1);
image.onload = function () {
outQueue.shift();
if (useLocalStorage) {
helpers.attemptWriteLocalStorage(queueName, JSON.stringify(outQueue));
}
executeQueue();
};
image.onerror = function () {
executingQueue = false;
};
image.src = configCollectorUrl + nextRequest.replace('?', '?stm=' + new Date().getTime() + '&');
}
}
/**
* Open an XMLHttpRequest for a given endpoint with the correct credentials and header
*
* @param string url The destination URL
* @return object The XMLHttpRequest
*/
function initializeXMLHttpRequest(url) {
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
return xhr;
}
/**
* Enclose an array of events in a self-describing payload_data JSON string
*
* @param array events Batch of events
* @return string payload_data self-describing JSON
*/
function encloseInPayloadDataEnvelope(events) {
return JSON.stringify({
schema: 'iglu:com.snowplowanalytics.snowplow/payload_data/jsonschema/1-0-4',
data: events
});
}
/**
* Attaches the STM field to outbound POST events.
*
* @param events the events to attach the STM to
*/
function attachStmToEvent(events) {
var stm = new Date().getTime().toString();
for (var i = 0; i < events.length; i++) {
events[i]['stm'] = stm;
}
return events
}
return {
enqueueRequest: enqueueRequest,
executeQueue: executeQueue
};
};
}());

View file

@ -0,0 +1,268 @@
/*
* JavaScript tracker for Snowplow: snowplow.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*jslint browser:true, plusplus:true, vars:true, nomen:true, evil:true */
/*global window */
/*global unescape */
/*global ActiveXObject */
/*global _snaq:true */
/*members encodeURIComponent, decodeURIComponent, getElementsByTagName,
shift, unshift,
addEventListener, attachEvent, removeEventListener, detachEvent,
cookie, domain, readyState, documentElement, doScroll, title, text,
location, top, document, referrer, parent, links, href, protocol, GearsFactory,
event, which, button, srcElement, type, target,
parentNode, tagName, hostname, className,
userAgent, cookieEnabled, platform, mimeTypes, enabledPlugin, javaEnabled,
XDomainRequest, XMLHttpRequest, ActiveXObject, open, setRequestHeader, onreadystatechange, setRequestHeader, send, readyState, status,
getTime, getTimeAlias, setTime, toGMTString, getHours, getMinutes, getSeconds,
toLowerCase, charAt, indexOf, lastIndexOf, split, slice, toUpperCase,
onload, src,
round, random,
exec,
res, width, height,
pdf, qt, realp, wma, dir, fla, java, gears, ag,
hook, getHook,
setCollectorCf, setCollectorUrl, setAppId,
setDownloadExtensions, addDownloadExtensions,
setDomains, setIgnoreClasses, setRequestMethod,
setReferrerUrl, setCustomUrl, setDocumentTitle,
setDownloadClasses, setLinkClasses,
discardHashTag,
setCookieNamePrefix, setCookieDomain, setCookiePath, setVisitorIdCookie,
setVisitorCookieTimeout, setSessionCookieTimeout, setReferralCookieTimeout,
doNotTrack, respectDoNotTrack, msDoNotTrack, getTimestamp, getCookieValue,
detectTimezone, detectViewport,
addListener, enableLinkTracking, enableActivityTracking, setLinkTrackingTimer,
enableDarkSocialTracking,
killFrame, redirectFile, setCountPreRendered,
trackLink, trackPageView, trackImpression,
addPlugin, getAsyncTracker
*/
;(function() {
// Load all our modules (at least until we fully modularize & remove grunt-concat)
var
uuid = require('uuid'),
forEach = require('lodash/forEach'),
filter = require('lodash/filter'),
helpers = require('./lib/helpers'),
queue = require('./in_queue'),
tracker = require('./tracker'),
object = typeof exports !== 'undefined' ? exports : this; // For eventual node.js environment support
object.Snowplow = function(asynchronousQueue, functionName) {
var
documentAlias = document,
windowAlias = window,
/* Tracker identifier with version */
version = 'js-' + '<%= pkg.version %>', // Update banner.js too
/* Contains four variables that are shared with tracker.js and must be passed by reference */
mutSnowplowState = {
/* List of request queues - one per Tracker instance */
outQueues: [],
bufferFlushers: [],
/* Time at which to stop blocking excecution */
expireDateTime: null,
/* DOM Ready */
hasLoaded: false,
registeredOnLoadHandlers: [],
/* pageViewId, which can changed by other trackers on page;
* initialized by tracker sent first event */
pageViewId: null
};
/************************************************************
* Private methods
************************************************************/
/*
* Handle beforeunload event
*
* Subject to Safari's "Runaway JavaScript Timer" and
* Chrome V8 extension that terminates JS that exhibits
* "slow unload", i.e., calling getTime() > 1000 times
*/
function beforeUnloadHandler() {
var now;
// Flush all POST queues
forEach(mutSnowplowState.bufferFlushers, function (flusher) {
flusher();
});
/*
* Delay/pause (blocks UI)
*/
if (mutSnowplowState.expireDateTime) {
// the things we do for backwards compatibility...
// in ECMA-262 5th ed., we could simply use:
// while (Date.now() < mutSnowplowState.expireDateTime) { }
do {
now = new Date();
if (filter(mutSnowplowState.outQueues, function (queue) {
return queue.length > 0;
}).length === 0) {
break;
}
} while (now.getTime() < mutSnowplowState.expireDateTime);
}
}
/*
* Handler for onload event
*/
function loadHandler() {
var i;
if (!mutSnowplowState.hasLoaded) {
mutSnowplowState.hasLoaded = true;
for (i = 0; i < mutSnowplowState.registeredOnLoadHandlers.length; i++) {
mutSnowplowState.registeredOnLoadHandlers[i]();
}
}
return true;
}
/*
* Add onload or DOM ready handler
*/
function addReadyListener() {
var _timer;
if (documentAlias.addEventListener) {
helpers.addEventListener(documentAlias, 'DOMContentLoaded', function ready() {
documentAlias.removeEventListener('DOMContentLoaded', ready, false);
loadHandler();
});
} else if (documentAlias.attachEvent) {
documentAlias.attachEvent('onreadystatechange', function ready() {
if (documentAlias.readyState === 'complete') {
documentAlias.detachEvent('onreadystatechange', ready);
loadHandler();
}
});
if (documentAlias.documentElement.doScroll && windowAlias === windowAlias.top) {
(function ready() {
if (!mutSnowplowState.hasLoaded) {
try {
documentAlias.documentElement.doScroll('left');
} catch (error) {
setTimeout(ready, 0);
return;
}
loadHandler();
}
}());
}
}
// sniff for older WebKit versions
if ((new RegExp('WebKit')).test(navigator.userAgent)) {
_timer = setInterval(function () {
if (mutSnowplowState.hasLoaded || /loaded|complete/.test(documentAlias.readyState)) {
clearInterval(_timer);
loadHandler();
}
}, 10);
}
// fallback
helpers.addEventListener(windowAlias, 'load', loadHandler, false);
}
/************************************************************
* Public data and methods
************************************************************/
windowAlias.Snowplow = {
/**
* Returns a Tracker object, configured with a
* CloudFront collector.
*
* @param string distSubdomain The subdomain on your CloudFront collector's distribution
*/
getTrackerCf: function (distSubdomain) {
var t = new tracker.Tracker(functionName, '', version, mutSnowplowState, {});
t.setCollectorCf(distSubdomain);
return t;
},
/**
* Returns a Tracker object, configured with the
* URL to the collector to use.
*
* @param string rawUrl The collector URL minus protocol and /i
*/
getTrackerUrl: function (rawUrl) {
var t = new tracker.Tracker(functionName, '', version, mutSnowplowState, {});
t.setCollectorUrl(rawUrl);
return t;
},
/**
* Get internal asynchronous tracker object
*
* @return Tracker
*/
getAsyncTracker: function () {
return new tracker.Tracker(functionName, '', version, mutSnowplowState, {});
}
};
/************************************************************
* Constructor
************************************************************/
// initialize the Snowplow singleton
helpers.addEventListener(windowAlias, 'beforeunload', beforeUnloadHandler, false);
addReadyListener();
// Now replace initialization array with queue manager object
return new queue.InQueueManager(tracker.Tracker, version, mutSnowplowState, asynchronousQueue, functionName);
};
}());

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,81 @@
/*
* JavaScript tracker for Snowplow: tag.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Use this function to load Snowplow
*
* @param object p The window
* @param object l The document
* @param string o "script", the tag name of script elements
* @param string w The source of the Snowplow script. Make sure you get the latest version.
* @param string i The Snowplow namespace. The Snowplow user should set this.
* @param undefined n The new script (to be created inside the function)
* @param undefined g The first script on the page (to be found inside the function)
*/
;(function(p,l,o,w,i,n,g) {
"p:nomunge, l:nomunge, o:nomunge, w:nomunge, i:nomunge, n:nomunge, g:nomunge";
// Stop if the Snowplow namespace i already exists
if (!p[i]) {
// Initialise the 'GlobalSnowplowNamespace' array
p['GlobalSnowplowNamespace'] = p['GlobalSnowplowNamespace'] || [];
// Add the new Snowplow namespace to the global array so sp.js can find it
p['GlobalSnowplowNamespace'].push(i);
// Create the Snowplow function
p[i] = function() {
(p[i].q = p[i].q || []).push(arguments);
};
// Initialise the asynchronous queue
p[i].q = p[i].q || [];
// Create a new script element
n = l.createElement(o);
// Get the first script on the page
g = l.getElementsByTagName(o)[0];
// The new script should load asynchronously
n.async = 1;
// Load Snowplow
n.src = w;
// Insert the Snowplow script before every other script so it executes as soon as possible
g.parentNode.insertBefore(n,g);
}
} (window, document, 'script', '//d1fc8wv8zag5ca.cloudfront.net/2/sp.js', 'new_name_here'));

View file

@ -0,0 +1,201 @@
/*
* JavaScript tracker for Snowplow: tests/functional/detectors.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
define([
'intern!object',
'intern/chai!assert'
], function(registerSuite, assert) {
var
// Expected viewport dimensions vary based on browser
expectedViewportWidths = [
996,
1008, // Windows 8, Windows 8.1
1016, // Linux, Firefox 27
1014, // Windows 7, Chrome 32
1022,
1024, // Windows 7, IE 9, Firefox 27; Mac OS, Safari
1020 // Windows 8, Windows 8.1
],
expectedViewportHeights = [
660, // Firefox 27.0, Linux
632, // Firefox 27.0, Linux
658, // Firefox 27.0, Windows 7
666,
670, // Safari 6/7
687, // Windows 7, IE 9
694, // Chrome 32, Windows 7 - 707
695, // Windows 7, IE 9
686, // Chrome 32, Linux - 686
705, // Windows 8/8.1
717 // Windows 8/8.1
],
// User fingerprint varies based on browser features
// TODO: try to hash this off the useragent -
// i.e. formal 1:1 relationship between viewport or signature and an individual browser
expectedSignatures = [
1587753772, // IE9, Windows 7
1101697779, // IE10, Windows 8
645783373, // IE11, Windows 8
580945094, // Firefox 27.0, Windows 7
1382842778, // Firefox 27.0, Linux
1727588738, // Chrome 32.0, Windows 7
3978691790, // Chrome 32.0, Linux
3552180943, // Safari 7, OS X 10.9
812921052 // Safari 6.2.7 Mac OS X 10.8
];
registerSuite({
name: 'Detectors test',
// These tests don't work as intended.
// I tend to blame SauceLabs on this, because it can fail on one of two subsequent runs
// on equal environments, while these functions are fully deterministic.
//
// 'Get viewport dimensions': function() {
//
// return this.remote
// .get(require.toUrl('tests/pages/detectors.html'))
// .setFindTimeout(5000)
// .setWindowSize(1024, 768)
// .findByCssSelector('body.loaded')
// .findById('detectViewport')
// .getVisibleText()
// .then(function (text) {
// var dimensions = text.split('x');
// assert.include(expectedViewportWidths, parseInt(dimensions[0]), 'Viewport width is valid');
// assert.include(expectedViewportHeights, parseInt(dimensions[1]), 'Viewport height is valid');
// });
// },
//
// 'Detect document size': function () {
// return this.remote
// .get(require.toUrl('tests/pages/detectors.html'))
// .setFindTimeout(5000)
// .setWindowSize(1024, 768)
// .findByCssSelector('body.loaded')
// .findById('detectDocumentDimensions')
// .getVisibleText()
// .then(function (text) {
// var dimensions = text.split('x');
// assert.include(expectedViewportWidths, parseInt(dimensions[0]), 'Document width is valid');
// assert.include(expectedViewportHeights, parseInt(dimensions[1]), 'Document height is valid');
// });
// },
//
// 'User fingerprinting': function() {
//
// return this.remote
// .get(require.toUrl('tests/pages/detectors.html'))
// .setFindTimeout(5000)
// .setWindowSize(1024, 768)
// .findByCssSelector('body.loaded')
// .findById('detectSignature')
// .getVisibleText()
// .then(function (text) {
// assert.include(expectedSignatures, parseInt(text), 'Create a user fingerprint based on browser features');
// });
// },
'Check localStorage availability': function() {
return this.remote
.get(require.toUrl('tests/pages/detectors.html'))
.setFindTimeout(5000)
.findByCssSelector('body.loaded')
.findById('localStorageAccessible')
.getVisibleText()
.then(function (text) {
assert.strictEqual(text, 'true', 'Detect localStorage accessibility');
});
},
'Check sessionStorage availability': function() {
return this.remote
.get(require.toUrl('tests/pages/detectors.html'))
.setFindTimeout(5000)
.findByCssSelector('body.loaded')
.findById('hasSessionStorage')
.getVisibleText()
.then(function (text) {
assert.strictEqual(text, 'true', 'Detect sessionStorage');
});
},
'Check whether cookies are enabled': function() {
return this.remote
.get(require.toUrl('tests/pages/detectors.html'))
.setFindTimeout(5000)
.findByCssSelector('body.loaded')
.findById('hasCookies')
.getVisibleText()
.then(function (text) {
assert.equal(text, '1', 'Detect whether cookies can be set');
});
},
'Detect timezone': function() {
return this.remote
.get(require.toUrl('tests/pages/detectors.html'))
.setFindTimeout(5000)
.findByCssSelector('body.loaded')
.findById('detectTimezone')
.getVisibleText()
.then(function (text) {
assert.include(['UTC', 'America/Los_Angeles'], text, 'Detect the timezone');
});
},
'Browser features': function() {
return this.remote
.get(require.toUrl('tests/pages/detectors.html'))
.setFindTimeout(5000)
.findByCssSelector('body.loaded')
.findById('detectBrowserFeatures')
.getVisibleText()
.then(function (text) {
var features = JSON.parse(text);
// The only features which are the same for all tested browsers
assert.equal('1', features.java, 'Detect Java');
assert.equal(24, features.cd, 'Detect color depth');
});
}
});
});

View file

@ -0,0 +1,98 @@
/*
* JavaScript tracker for Snowplow: tests/functional/helpers.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
define([
'intern!object',
'intern/chai!assert',
'intern/dojo/node!../../src/js/lib/helpers'
], function(registerSuite, assert, helpers) {
registerSuite({
name: 'Helpers test',
'Get page title': function() {
return this.remote
.get(require.toUrl('tests/pages/helpers.html'))
.setFindTimeout(5000)
.findByCssSelector('body.loaded')
.findById('title')
.getVisibleText()
.then(function (text) {
assert.strictEqual(text, 'Helpers test page', 'Get the page title' );
});
},
'Get host name': function() {
return this.remote
.get(require.toUrl('tests/pages/helpers.html'))
.setFindTimeout(5000)
.findByCssSelector('body.loaded')
.findById('hostname')
.getVisibleText()
.then(function (text){
assert.strictEqual(text, 'localhost', 'Get the host name');
});
},
'Get referrer from querystring': function() {
return this.remote
.get(require.toUrl('tests/pages/helpers.html') + '?name=value&referrer=previous#fragment')
.setFindTimeout(5000)
.findByCssSelector('body.loaded')
.findById('referrer')
.getVisibleText()
.then(function (text){
assert.strictEqual(text, 'previous', 'Get the referrer from the querystring');
});
},
'Add event listener': function() {
return this.remote
.get(require.toUrl('tests/pages/helpers.html'))
.setFindTimeout(5000)
.findByCssSelector('body.loaded')
.findById('click')
.click()
.getVisibleText()
.then(function (text){
assert.strictEqual(text, 'clicked', 'Add a click event listener');
});
}
});
});

View file

@ -0,0 +1,320 @@
/*
* JavaScript tracker for Snowplow: tests/functional/helpers.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2016 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
define([
'intern!object',
'intern/chai!assert',
'intern/dojo/node!lodash',
'intern/dojo/node!http',
'intern/dojo/node!url',
"intern/dojo/node!js-base64"
], function(registerSuite, assert, lodash, http, url, jsBase64) {
var decodeBase64 = jsBase64.Base64.fromBase64;
/**
* Expected amount of request for each browser
* This must be increased when new tracking call added to
* pages/integration-template.html
*/
var log = [];
function pageViewsHaveDifferentIds () {
var pageViews = lodash.filter(log, function (logLine) {
return logLine.e === 'pv';
});
var contexts = lodash.map(pageViews, function (logLine) {
var data = JSON.parse(decodeBase64(logLine.cx)).data;
return lodash.find(data, function (context) {
return context.schema === 'iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0';
});
});
var ids = lodash.map(contexts, function (wpContext) {
return wpContext.data.id;
});
return lodash.uniq(ids).length >= 2;
}
/**
* Check if expected payload exists in `log`
*/
function checkExistenceOfExpectedQuerystring(expected) {
function compare(e, other) { // e === expected
var result = lodash.map(e, function (v, k) {
if (lodash.isFunction(v)) { return v(other[k]); }
else { return lodash.isEqual(v, other[k]); }
});
return lodash.every(result);
}
function strip(logLine) {
var expectedKeys = lodash.keys(expected);
var stripped = lodash.pickBy(logLine, function (v, k) { return lodash.includes(expectedKeys, k); });
if (lodash.keys(stripped).length !== expectedKeys.length) { return null; }
else { return stripped; }
}
return lodash.some(log, function (logLine) {
var stripped = strip(logLine);
if (stripped == null) { return false; }
else { return lodash.isEqualWith(expected, stripped, compare); }
});
}
function someTestsFailed(suite) {
return lodash.some(suite.tests, function (test) { return test.error !== null; });
}
// Ngrok must be running to forward requests to localhost
http.createServer(function (request, response) {
response.writeHead(200, {'Content-Type': 'image/gif'});
if (request.method === 'GET') {
var payload = url.parse(request.url, true).query;
log.push(payload);
}
var img = new Buffer('47494638396101000100800000dbdfef00000021f90401000000002c00000000010001000002024401003b', 'hex');
response.end(img, 'binary');
}).listen(8500, function () { console.log("Collector mock running...\n"); });
registerSuite({
teardown: function () {
if (someTestsFailed(this)) {
console.log("Tests failed with following log:");
lodash.forEach(log, function (l) { console.log(l); });
}
console.log("Cleaning log");
log = [];
},
name: 'Test that request_recorder logs meet expectations',
'Check existence of page view in log': function () {
assert.isTrue(checkExistenceOfExpectedQuerystring({
e: 'pv',
p: 'mob',
aid: 'CFe23a',
uid: 'Malcolm',
page: 'My Title',
cx: function (cx) {
var contexts = JSON.parse(decodeBase64(cx)).data;
return lodash.some(contexts,
lodash.matches({
schema:"iglu:com.example_company/user/jsonschema/2-0-0",
data:{
userType:'tester'
}
})
);
}
}), 'A page view should be detected');
},
'Check nonexistence of nonexistent event types in log': function () {
assert.isFalse(checkExistenceOfExpectedQuerystring({
e: 'ad'
}), 'No nonexistent event type should be detected');
},
'Check a structured event was sent': function () {
assert.isTrue(checkExistenceOfExpectedQuerystring({
e: 'se',
se_ca: 'Mixes',
se_ac: 'Play',
se_la: 'MRC/fabric-0503-mix',
se_va: '0.0'
}), 'A structured event should be detected');
},
'Check an unstructured event with true timestamp was sent': function () {
assert.isTrue(checkExistenceOfExpectedQuerystring({
e: 'ue',
ue_px: 'eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvdy91bnN0cnVjdF9ldmVudC9qc29uc2NoZW1hLzEtMC0wIiwiZGF0YSI6eyJzY2hlbWEiOiJpZ2x1OmNvbS5hY21lX2NvbXBhbnkvdmlld2VkX3Byb2R1Y3QvanNvbnNjaGVtYS81LTAtMCIsImRhdGEiOnsicHJvZHVjdElkIjoiQVNPMDEwNDMifX19',
ttm: '1477401868'
}), 'An unstructured event should be detected');
},
'Check a transaction event was sent': function () {
assert.isTrue(checkExistenceOfExpectedQuerystring({
e: 'tr',
tr_id: 'order-123',
tr_af: 'acme',
tr_tt: '8000',
tr_tx: '100',
tr_ci: 'phoenix',
tr_st: 'arizona',
tr_co: 'USA',
tr_cu: 'JPY'
}), 'A transaction event should be detected');
},
'Check a transaction item event was sent': function () {
assert.isTrue(checkExistenceOfExpectedQuerystring({
e: 'ti',
ti_id: 'order-123',
ti_sk: '1001',
ti_nm: 'Blue t-shirt',
ti_ca: 'clothing',
ti_pr: '2000',
ti_qu: '2',
ti_cu: 'JPY'
}), 'A transaction item event should be detected');
},
'Check an unhandled exception was sent': function () {
assert.isTrue(checkExistenceOfExpectedQuerystring({
ue_px: function (ue) {
var event = JSON.parse(decodeBase64(ue)).data;
// We cannot test more because implementations vary much in old browsers (FF27,IE9)
return (event.schema === 'iglu:com.snowplowanalytics.snowplow/application_error/jsonschema/1-0-1') &&
(event.data.programmingLanguage === 'JAVASCRIPT') &&
(event.data.message != null);
}
}));
},
'Check pageViewId is regenerated for each trackPageView': function () {
assert.isTrue(pageViewsHaveDifferentIds());
},
'Check global contexts are for structured events': function () {
assert.isTrue(checkExistenceOfExpectedQuerystring({
e: 'se',
cx: function (cx) {
var contexts = JSON.parse(decodeBase64(cx)).data;
return 2 === lodash.size(
lodash.filter(contexts,
lodash.overSome(
lodash.matches({
schema: "iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1",
data: {
osType: 'ubuntu'
}
}),
lodash.matches({
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
data: {
'latitude': 40.0,
'longitude': 55.1
}
})
)
)
);
}
}));
},
'Check an unstructured event with global context from accept ruleset': function () {
assert.isTrue(checkExistenceOfExpectedQuerystring({
e: 'ue',
ue_px: function (ue_px) {
var event = JSON.parse(decodeBase64(ue_px)).data;
return lodash.isMatch(event,
{
schema:"iglu:com.acme_company/viewed_product/jsonschema/5-0-0",
data:{
productId: 'ASO01042'
}
}
);
},
cx: function (cx) {
var contexts = JSON.parse(decodeBase64(cx)).data;
return 2 === lodash.size(
lodash.filter(contexts,
lodash.overSome(
lodash.matches({
schema: "iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1",
data: {
osType: 'ubuntu'
}
}),
lodash.matches({
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
data: {
'latitude': 40.0,
'longitude': 55.1
}
})
)
)
);
}
}), 'An unstructured event with global contexts should be detected');
},
'Check an unstructured event missing global context from reject ruleset': function () {
assert.isTrue(checkExistenceOfExpectedQuerystring({
e: 'ue',
ue_px: function (ue_px) {
var event = JSON.parse(decodeBase64(ue_px)).data;
return lodash.isMatch(event,
{
schema:"iglu:com.acme_company/viewed_product/jsonschema/5-0-0",
data:{
productId: 'ASO01041'
}
}
);
},
cx: function (cx) {
var contexts = JSON.parse(decodeBase64(cx)).data;
return 0 === lodash.size(
lodash.filter(contexts,
lodash.overSome(
lodash.matches({
schema: "iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1",
data: {
osType: 'ubuntu'
}
}),
lodash.matches({
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
data: {
'latitude': 40.0,
'longitude': 55.1
}
})
)
)
);
}
}), 'An unstructured event without global contexts should be detected');
}
});
});

View file

@ -0,0 +1,52 @@
/*
* JavaScript tracker for Snowplow: tests/functional/detectors.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
define([
'intern!object'
], function(registerSuite) {
registerSuite({
name: 'Integration setup',
'Set up the page and send some events to request_recorder': function() {
return this.remote
.get(require.toUrl('tests/pages/integration.html'))
.setFindTimeout(5000)
.findByCssSelector('body.loaded')
.sleep(15000); // Time for requests to get written
}
});
});

View file

@ -0,0 +1,61 @@
/*
* JavaScript tracker for Snowplow: tests/intern.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
define({
capabilities: {
'selenium-version': '2.48.0'
},
environments: [
{ browserName: 'internet explorer', version: '11', platform: 'Windows 8.1' },
{ browserName: 'internet explorer', version: '10', platform: 'Windows 8' },
{ browserName: 'internet explorer', version: '9', platform: 'Windows 7' },
{ browserName: 'firefox', version: '27', platform: [ 'Windows 7', 'Linux' ] },
{ browserName: 'chrome', version: '32', platform: [ 'Windows 7', 'Linux' ] },
{ browserName: 'safari', version: '8', platform: 'OS X 10.10' },
{ browserName: 'safari', version: '10', platform: 'OS X 10.11' }
],
maxConcurrency: 1,
tunnel: 'SauceLabsTunnel',
tunnelOptions: {
logFile: process.cwd() + '/SauceLabsTunnel.log'
},
// A regular expression matching URLs to files that should not be included in code coverage analysis
excludeInstrumentation: /^(?:tests|node_modules)\//
});

View file

@ -0,0 +1,71 @@
import os
import sys
from http.server import HTTPServer, BaseHTTPRequestHandler
from http.client import NO_CONTENT
from pprint import pprint
gif_string = b'GIF87a\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\xff\xff\xff,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;'
file_path = os.path.dirname(__file__)
def get_serve(file):
return os.path.join(file_path, 'serve', file)
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
def do_HEAD(self):
self.send_response(200)
if (self.path == '/integration.html'):
self.send_header("Content-type", "text/html")
self.end_headers()
else:
self.send_header("Content-type", "image/gif")
self.end_headers()
def do_GET(self):
"""Respond to a GET request."""
self.send_response(200)
if (self.path == '/integration.html'):
self.send_header("Content-type", "text/html")
self.end_headers()
f = open(get_serve('integration.html'), "rb")
body = f.read()
f.close()
self.wfile.write(body)
elif (self.path == '/snowplow.js'):
self.send_header("Content-type", "text/html")
self.end_headers()
f = open(get_serve('snowplow.js'), "rb")
body = f.read()
f.close()
self.wfile.write(body)
else:
self.send_header("Content-type", "image/gif")
self.end_headers()
self.wfile.write(gif_string)
def do_POST(self):
self.send_response(200)
self.send_header("Content-type", "image/gif")
self.end_headers()
self.wfile.write(''.encode())
content_len = int(self.headers.get('content-length', 0))
post_body = self.rfile.read(content_len)
pprint(post_body)
def do_OPTIONS(self):
self.send_response(200)
self.send_header('Access-Control-Allow-Origin', 'null')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header("Access-Control-Allow-Headers", "X-Requested-With")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.end_headers()
httpd = HTTPServer(('127.0.0.1', 8000), SimpleHTTPRequestHandler)
try:
httpd.serve_forever()
except KeyboardInterrupt:
print('User closed server with Ctrl-c')
sys.exit(0)

View file

@ -0,0 +1,156 @@
/*
* JavaScript tracker for Snowplow: tests/nonfunctional/helpers.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
define([
"intern!object",
"intern/chai!assert",
"intern/dojo/node!../../src/js/lib/helpers"
], function (registerSuite, assert, helpers) {
var decorateQuerystring = helpers.decorateQuerystring;
var resolveDynamicContexts = helpers.resolveDynamicContexts;
registerSuite({
name: "decorateQuerystring test",
"Decorate a URL with no querystring or fragment": function () {
var url = 'http://www.example.com';
var expected = 'http://www.example.com?_sp=a.b';
var actual = decorateQuerystring(url, '_sp', 'a.b');
assert.equal(actual, expected);
},
"Decorate a URL with a fragment but no querystring": function () {
var url = 'http://www.example.com#fragment';
var expected = 'http://www.example.com?_sp=a.b#fragment';
var actual = decorateQuerystring(url, '_sp', 'a.b');
assert.equal(actual, expected);
},
"Decorate a URL with an empty querystring": function () {
var url = 'http://www.example.com?';
var expected = 'http://www.example.com?_sp=a.b';
var actual = decorateQuerystring(url, '_sp', 'a.b');
assert.equal(actual, expected);
},
"Decorate a URL with a nonempty querystring": function () {
var url = 'http://www.example.com?name=value';
var expected = 'http://www.example.com?_sp=a.b&name=value';
var actual = decorateQuerystring(url, '_sp', 'a.b');
assert.equal(actual, expected);
},
"Override an existing field": function () {
var url = 'http://www.example.com?_sp=outdated';
var expected = 'http://www.example.com?_sp=a.b';
var actual = decorateQuerystring(url, '_sp', 'a.b');
assert.equal(actual, expected);
},
"Decorate a URL whose querystring contains multiple question marks": function () {
var url = 'http://www.example.com?test=working?&name=value';
var expected = 'http://www.example.com?_sp=a.b&test=working?&name=value';
var actual = decorateQuerystring(url, '_sp', 'a.b');
assert.equal(actual, expected);
},
"Override a field in a querystring containing a question mark": function () {
var url = 'http://www.example.com?test=working?&_sp=outdated';
var expected = 'http://www.example.com?test=working?&_sp=a.b';
var actual = decorateQuerystring(url, '_sp', 'a.b');
assert.equal(actual, expected);
},
"Decorate a querystring with multiple ?s and #s": function () {
var url = 'http://www.example.com?test=working?&_sp=outdated?&?name=value#fragment?#?#';
var expected = 'http://www.example.com?test=working?&_sp=a.b&?name=value#fragment?#?#';
var actual = decorateQuerystring(url, '_sp', 'a.b');
assert.equal(actual, expected);
},
});
registerSuite({
name: "getCssClasses test",
"Tokenize a DOM element's className field": function () {
var element = {
className: ' the quick brown_fox-jumps/over\nthe\t\tlazy dog '
};
var expected = ['the', 'quick', 'brown_fox-jumps/over', 'the', 'lazy', 'dog'];
var actual = helpers.getCssClasses(element);
assert.deepEqual(actual, expected);
},
});
registerSuite({
name: "resolveDynamicContexts tests",
"Resolves context generators and static contexts": function () {
var contextGenerator = function () {
return {
'schema': 'iglu:com.acme.marketing/some_event/jsonschema/1-0-0',
'data': {'test': 1}
}
};
var staticContext = {
'schema': 'iglu:com.acme.marketing/some_event/jsonschema/1-0-0',
'data': {'test': 1}
};
var expected = [contextGenerator(), staticContext];
var actual = resolveDynamicContexts([contextGenerator, staticContext]);
assert.deepEqual(actual, expected);
},
"Resolves context generators with arguments": function () {
var contextGenerator = function (argOne, argTwo) {
return {
'schema': 'iglu:com.acme.marketing/some_event/jsonschema/1-0-0',
'data': {
'firstVal': argOne,
'secondVal': argTwo
}
}
};
var expected = [
{
'schema': 'iglu:com.acme.marketing/some_event/jsonschema/1-0-0',
'data': {
'firstVal': 1,
'secondVal': 2
}
}
];
var actual = resolveDynamicContexts([contextGenerator], 1, 2);
assert.deepEqual(actual, expected);
},
});
});

View file

@ -0,0 +1,104 @@
/*
* JavaScript tracker for Snowplow: tests/nonfunctional/in_queue.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
define([
"intern!object",
"intern/chai!assert",
"intern/dojo/node!../../src/js/in_queue"
], function(registerSuite, assert, in_queue) {
var output = 0;
function mockTrackerConstructor (functionName, namespace, version, mutSnowplowState, argmap) {
var configCollectorUrl,
attribute = 10;
return {
setCollectorUrl: function(rawUrl) {
configCollectorUrl = "http://" + rawUrl + "/i";
},
increaseAttribute: function(n) {
attribute += n;
},
setAttribute: function(p) {
attribute = p;
},
setOutputToAttribute: function() {
output = attribute;
},
addAttributeToOutput: function() {
output += attribute;
}
};
}
var asyncQueue = [
["newTracker", "firstTracker", "firstEndpoint"],
["increaseAttribute", 5],
["setOutputToAttribute"]
];
asyncQueue = new in_queue.InQueueManager(mockTrackerConstructor, 0, {}, asyncQueue, 'snowplow');
registerSuite({
name: "InQueueManager test",
"Make a proxy": function() {
assert.equal(output, 15, "Function originally stored in asyncQueue is executed when asyncQueue becomes an AsyncQueueProxy");
},
"Add to asyncQueue after conversion": function() {
asyncQueue.push(["setAttribute", 7]);
asyncQueue.push(["setOutputToAttribute"]);
assert.equal(output, 7, "Function added to asyncQueue after it becomes an AsyncQueueProxy is executed");
},
"Backward compatibility: Create a tracker using the legacy setCollectorUrl method": function() {
asyncQueue.push(["setCollectorUrl", "secondEndpoint"]);
asyncQueue.push(["addAttributeToOutput"]);
assert.equal(output, 24, "A second tracker is created and both trackers' attributes are added to output");
},
"Use 'function:tracker1;tracker2' syntax to control which trackers execute which functions": function() {
asyncQueue.push(["setAttribute:firstTracker", 2]);
asyncQueue.push(["setAttribute:sp", 3]);
asyncQueue.push(["addAttributeToOutput:firstTracker;sp"]);
assert.equal(output, 29, "Set the attributes of the two trackers individually, then add both to output");
},
"Execute a user-defined custom callback": function () {
var callbackExecuted = false;
asyncQueue.push([function () { callbackExecuted = true; }]);
assert.isTrue(callbackExecuted);
}
});
});

View file

@ -0,0 +1,141 @@
/*
* JavaScript tracker for Snowplow: tests/nonfunctional/proxies.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
define([
"intern!object",
"intern/chai!assert",
"intern/dojo/node!../../src/js/lib/proxies"
], function(registerSuite, assert, proxies) {
this.document = {
links: [{href: "http://www.example.com/"}],
body: {
children: [{
children: [{
children: [{
children: [{
children: [{
children: [{
innerHTML: 'You have reached the cached page for'
}]
}]
}]
}]
}]
}]
}
};
registerSuite({
"name": "Proxies test",
"Host name is not a special case": function() {
var
initialLocationArray = ["normalhostname", "href", "http://referrer.com"],
fixedupLocationArray = proxies.fixupUrl.apply(null, initialLocationArray),
expectedLocationArray = fixedupLocationArray,
i;
for (i = 0; i < 3; i++) {
assert.strictEqual(fixedupLocationArray[i], expectedLocationArray[i], 'Except in special cases, fixupUrl changes nothing');
}
},
"Host name = 'translate.googleusercontent.com'": function() {
var
initialLocationArray = [
"translate.googleusercontent.com",
"http://translate.googleusercontent.com/translate?hl=en&sl=fr&u=http:www.francais.fr/path",
""],
fixedupLocationArray = proxies.fixupUrl.apply(null, initialLocationArray),
expectedLocationArray = [
"www.francais.fr",
"http:www.francais.fr/path",
"http://translate.googleusercontent.com/translate?hl=en&sl=fr&u=http:www.francais.fr/path"],
i;
for (i = 0; i < 3; i++) {
assert.strictEqual(fixedupLocationArray[i], expectedLocationArray[i], 'Get the URL for the untranslated page from the querystring and make the translated page the referrer');
}
},
"Host name = 'ccj.bingj.com'": function() {
var
initialLocationArray = [
"cc.bingj.com",
"http://cc.bingj.com/cache.aspx?q=example.com&d=4870936571937837&mkt=en-GB&setlang=en-GB&w=QyOPD1fo3C2nC9sXMLmUUs81Jt78MYIp",
"http://referrer.com"],
fixedupLocationArray = proxies.fixupUrl.apply(null, initialLocationArray),
expectedLocationArray = [ "www.example.com", "http://www.example.com/", "http://referrer.com" ],
i;
for (i = 0; i < 3; i++) {
assert.strictEqual(fixedupLocationArray[i], expectedLocationArray[i], 'On a page cached by Bing, get the original URL from the first link');
}
},
"Host name = 'webcache.googleusercontent.com'": function() {
var
initialLocationArray = [
"webcache.googleusercontent.com",
"http://webcache.googleusercontent.com/search?q=cache:http://example.com/#fragment",
"http://referrer.com"],
fixedupLocationArray = proxies.fixupUrl.apply(null, initialLocationArray),
expectedLocationArray = [ "www.example.com", "http://www.example.com/", "http://referrer.com" ],
i;
for (i = 0; i < 3; i++) {
assert.strictEqual(fixedupLocationArray[i], expectedLocationArray[i], 'On a page cached by Google, get the original URL from the first link');
}
},
"Host name is an IP address": function() {
var
initialLocationArray = [
"98.139.21.31",
"http://98.139.21.31/search/srpcache",
"http://referrer.com"],
fixedupLocationArray = proxies.fixupUrl.apply(null, initialLocationArray),
expectedLocationArray = [ "www.example.com", "http://www.example.com/", "http://referrer.com" ],
i;
for (i = 0; i < 3; i++) {
assert.strictEqual(fixedupLocationArray[i], expectedLocationArray[i], 'On a page cached by Yahoo, get the original URL from the first link');
}
}
})
});

View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>Detectors test page</title>
</head>
<body>
<p id="detectViewport">Viewport dimensions here</p>
<p id="detectDocumentDimensions">Document dimensions here</p>
<p id="localStorageAccessible">localStorage accessibility here</p>
<p id="hasSessionStorage">sessionStorage here</p>
<p id="hasCookies">hasCookies here</p>
<p id="detectTimezone">Timezone here</p>
<p id="detectSignature">User fingerprint here</p>
<p id="detectBrowserFeatures">User fingerprint here</p>
<script src="./detectors.js"></script>
<script>
document.body.className += ' loaded';
</script>
</body>
</html>

View file

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>Helpers test page</title>
</head>
<body>
<p id="title">Title here</p>
<p id="hostname">Hostname here</p>
<p id="referrer">Referrer here</p>
<p id="click">Click here</p>
<script src="./helpers.js"></script>
<script>
document.body.className += ' loaded';
</script>
</body>
</html>

View file

@ -0,0 +1,157 @@
<!DOCTYPE html>
<html>
<head>
<title>Integration test page</title>
</head>
<body>
<p id="title">Page for sending requests to request_recorder</p>
<script>
document.body.className += ' loaded';
</script>
<script>
;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","./snowplow.js","snowplow"));
// Taken from a random environment variable
var collector_endpoint = '<%= subdomain %>' + '.ngrok.io';
window.snowplow('newTracker', 'cf', collector_endpoint, {
encodeBase64: true,
appId: 'CFe23a',
platform: 'mob',
contexts: {
webPage: true
}
});
// Add a global context
var geolocationContext = {
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
data: {
'latitude': 40.0,
'longitude' : 55.1
}
};
function eventTypeContextGenerator(args) {
var context = {};
context['schema'] = 'iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1';
context['data'] = {};
context['data']['osType'] = 'ubuntu';
context['data']['osVersion'] = '2018.04';
context['data']['deviceManufacturer'] = 'ASUS';
context['data']['deviceModel'] = String(args['eventType']);
return context;
}
// A filter that will only attach contexts to structured events
function structuredEventFilter(args) {
return args['eventType'] === 'se';
}
function erroneousContextGenerator(args) {
return {};
}
window.snowplow('addGlobalContexts', [erroneousContextGenerator]);
window.snowplow('addGlobalContexts', [[structuredEventFilter, [eventTypeContextGenerator, geolocationContext]]]);
window.snowplow('setUserId', 'Malcolm');
window.snowplow('trackPageView', 'My Title', [ // Auto-set page title; add page context
{
schema: "iglu:com.example_company/user/jsonschema/2-0-0",
data: {
userType: 'tester'
}
}
]);
// This should have different pageViewId in web_page context
window.snowplow('trackPageView');
window.snowplow('trackStructEvent', 'Mixes', 'Play', 'MRC/fabric-0503-mix', '', '0.0');
window.snowplow('trackUnstructEvent', {
schema: 'iglu:com.acme_company/viewed_product/jsonschema/5-0-0',
data: {
productId: 'ASO01043'
}
}, [], {type: 'ttm', value: 1477401868});
var orderId = 'order-123';
window.snowplow('addTrans',
orderId,
'acme',
'8000',
'100',
'50',
'phoenix',
'arizona',
'USA',
'JPY'
);
// addItem might be called for each item in the shopping cart,
// or not at all.
window.snowplow('addItem',
orderId,
'1001',
'Blue t-shirt',
'clothing',
'2000',
'2',
'JPY'
);
// trackTrans sends the transaction to Snowplow tracking servers.
// Must be called last to commit the transaction.
window.snowplow('trackTrans');
var testAcceptRuleSet = {
accept: ['iglu:com.acme_company/*/jsonschema/*-*-*']
};
var testRejectRuleSet = {
reject: ['iglu:com.acme_company/*/jsonschema/*-*-*']
};
// test context rulesets
window.snowplow('addGlobalContexts', [[testAcceptRuleSet, [eventTypeContextGenerator, geolocationContext]]]);
window.snowplow('trackUnstructEvent', {
schema: 'iglu:com.acme_company/viewed_product/jsonschema/5-0-0',
data: {
productId: 'ASO01042'
}
}, [], {type: 'ttm', value: 1477401869});
window.snowplow('removeGlobalContexts', [[testAcceptRuleSet, [eventTypeContextGenerator, geolocationContext]]]);
window.snowplow('addGlobalContexts', [[testRejectRuleSet, [eventTypeContextGenerator, geolocationContext]]]);
window.snowplow('trackUnstructEvent', {
schema: 'iglu:com.acme_company/viewed_product/jsonschema/5-0-0',
data: {
productId: 'ASO01041'
}
}, [], {type: 'ttm', value: 1477401868});
// track unhandled exception
window.snowplow("enableErrorTracking");
// Test for exception handling
function raiseException() {
notExistentObject.notExistentProperty();
}
setTimeout(raiseException, 2500);
</script>
</body>
</html>

View file

@ -0,0 +1,44 @@
/*
* JavaScript tracker for Snowplow: tests/scripts/detectors.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var detectors = require('../../src/js/lib/detectors.js');
document.getElementById('detectViewport').innerHTML = detectors.detectViewport();
document.getElementById('detectDocumentDimensions').innerHTML = detectors.detectDocumentSize();
document.getElementById('localStorageAccessible').innerHTML = detectors.localStorageAccessible();
document.getElementById('hasSessionStorage').innerHTML = detectors.hasSessionStorage();
document.getElementById('hasCookies').innerHTML = detectors.hasCookies();
document.getElementById('detectTimezone').innerHTML = detectors.detectTimezone();
document.getElementById('detectSignature').innerHTML = detectors.detectSignature();
document.getElementById('detectBrowserFeatures').innerHTML = JSON.stringify(detectors.detectBrowserFeatures(), undefined, 2);

View file

@ -0,0 +1,42 @@
/*
* JavaScript tracker for Snowplow: tests/scripts/helpers.js
*
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
* 2012-2014 Snowplow Analytics Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
* names of their contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var helpers = require('../../src/js/lib/helpers.js');
document.getElementById('title').innerHTML = helpers.fixupTitle(0);
document.getElementById('hostname').innerHTML = helpers.getHostName(location.href);
document.getElementById('referrer').innerHTML = helpers.getReferrer();
helpers.addEventListener(document.getElementById('click'), 'click', function(){
document.getElementById('click').innerHTML = 'clicked';
});

View file

@ -0,0 +1,3 @@
.peru
oss-playbooks
ansible

View file

@ -0,0 +1,2 @@
[vagrant]
127.0.0.1:2222

View file

@ -0,0 +1,14 @@
imports:
ansible: ansible
ansible_playbooks: oss-playbooks
curl module ansible:
# Equivalent of git cloning tags/v1.6.6 but much, much faster
url: https://codeload.github.com/ansible/ansible/zip/69d85c22c7475ccf8169b6ec9dee3ee28c92a314
unpack: zip
export: ansible-69d85c22c7475ccf8169b6ec9dee3ee28c92a314
git module ansible_playbooks:
url: https://github.com/snowplow/ansible-playbooks.git
# Comment out to fetch a specific rev instead of master:
# rev: xxx

View file

@ -0,0 +1,50 @@
#!/bin/bash
set -e
vagrant_dir=/vagrant/vagrant
bashrc=/home/vagrant/.bashrc
echo "========================================"
echo "INSTALLING PERU AND ANSIBLE DEPENDENCIES"
echo "----------------------------------------"
apt-get update
apt-get install -y language-pack-en git unzip libyaml-dev python3-pip python-yaml python-paramiko python-jinja2
echo "==============="
echo "INSTALLING PERU"
echo "---------------"
sudo pip3 install peru
echo "======================================="
echo "CLONING ANSIBLE AND PLAYBOOKS WITH PERU"
echo "---------------------------------------"
cd ${vagrant_dir} && peru sync -v
echo "... done"
env_setup=${vagrant_dir}/ansible/hacking/env-setup
hosts=${vagrant_dir}/ansible.hosts
echo "==================="
echo "CONFIGURING ANSIBLE"
echo "-------------------"
touch ${bashrc}
echo "source ${env_setup}" >> ${bashrc}
echo "export ANSIBLE_HOSTS=${hosts}" >> ${bashrc}
echo "... done"
echo "=========================================="
echo "RUNNING PLAYBOOKS WITH ANSIBLE*"
echo "* no output while each playbook is running"
echo "------------------------------------------"
while read pb; do
su - -c "source ${env_setup} && ${vagrant_dir}/ansible/bin/ansible-playbook ${vagrant_dir}/${pb} --connection=local --inventory-file=${hosts}" vagrant
done <${vagrant_dir}/up.playbooks
guidance=${vagrant_dir}/up.guidance
if [ -f ${guidance} ]; then
echo "==========="
echo "PLEASE READ"
echo "-----------"
cat $guidance
fi

View file

@ -0,0 +1,6 @@
To get started:
vagrant ssh
cd /vagrant
sudo npm install
cd core
sudo npm install

View file

@ -0,0 +1,3 @@
oss-playbooks/java10.yml
oss-playbooks/nodejs.yml
oss-playbooks/grunt.yml