New upstream version 12.9.2+dfsg
This commit is contained in:
parent
b0a17338f5
commit
c5badf79b1
76 changed files with 18439 additions and 0 deletions
10
snowplow-javascript-tracker/.babelrc
Normal file
10
snowplow-javascript-tracker/.babelrc
Normal 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
34
snowplow-javascript-tracker/.gitignore
vendored
Normal 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
|
58
snowplow-javascript-tracker/.travis.yml
Normal file
58
snowplow-javascript-tracker/.travis.yml
Normal 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
|
174
snowplow-javascript-tracker/.travis/deploy.py
Executable file
174
snowplow-javascript-tracker/.travis/deploy.py
Executable 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()
|
15
snowplow-javascript-tracker/.travis/is_core_release_tag.sh
Executable file
15
snowplow-javascript-tracker/.travis/is_core_release_tag.sh
Executable 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
|
14
snowplow-javascript-tracker/.travis/is_tracker_release_tag.sh
Executable file
14
snowplow-javascript-tracker/.travis/is_tracker_release_tag.sh
Executable 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
|
548
snowplow-javascript-tracker/CHANGELOG
Normal file
548
snowplow-javascript-tracker/CHANGELOG
Normal 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
|
279
snowplow-javascript-tracker/Gruntfile.js
Normal file
279
snowplow-javascript-tracker/Gruntfile.js
Normal 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']);
|
||||
};
|
29
snowplow-javascript-tracker/LICENSE.txt
Normal file
29
snowplow-javascript-tracker/LICENSE.txt
Normal 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.
|
88
snowplow-javascript-tracker/README.md
Normal file
88
snowplow-javascript-tracker/README.md
Normal 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
29
snowplow-javascript-tracker/Vagrantfile
vendored
Normal 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
|
7
snowplow-javascript-tracker/aws.sample.json
Normal file
7
snowplow-javascript-tracker/aws.sample.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"key": "ADD HERE",
|
||||
"secret": "ADD HERE",
|
||||
"bucket": "ADD HERE",
|
||||
"region": "ADD HERE",
|
||||
"distribution": "ADD HERE"
|
||||
}
|
4
snowplow-javascript-tracker/core/.gitignore
vendored
Normal file
4
snowplow-javascript-tracker/core/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
index.js
|
||||
lib/*.js
|
||||
lib/*.js.map
|
||||
.tscache
|
2
snowplow-javascript-tracker/core/.npmignore
Normal file
2
snowplow-javascript-tracker/core/.npmignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
tests/
|
||||
Gruntfile.js
|
28
snowplow-javascript-tracker/core/CHANGELOG
Normal file
28
snowplow-javascript-tracker/core/CHANGELOG
Normal 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
|
58
snowplow-javascript-tracker/core/Gruntfile.js
Normal file
58
snowplow-javascript-tracker/core/Gruntfile.js
Normal 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']);
|
||||
};
|
132
snowplow-javascript-tracker/core/README.md
Normal file
132
snowplow-javascript-tracker/core/README.md
Normal 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
|
16
snowplow-javascript-tracker/core/index.ts
Normal file
16
snowplow-javascript-tracker/core/index.ts
Normal 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";
|
170
snowplow-javascript-tracker/core/lib/base64.ts
Normal file
170
snowplow-javascript-tracker/core/lib/base64.ts
Normal 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+$/, ''));
|
||||
}
|
||||
|
492
snowplow-javascript-tracker/core/lib/contexts.ts
Normal file
492
snowplow-javascript-tracker/core/lib/contexts.ts
Normal 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 [];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
1013
snowplow-javascript-tracker/core/lib/core.ts
Normal file
1013
snowplow-javascript-tracker/core/lib/core.ts
Normal file
File diff suppressed because it is too large
Load diff
112
snowplow-javascript-tracker/core/lib/payload.ts
Normal file
112
snowplow-javascript-tracker/core/lib/payload.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
202
snowplow-javascript-tracker/core/license.txt
Normal file
202
snowplow-javascript-tracker/core/license.txt
Normal 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.
|
37
snowplow-javascript-tracker/core/package.json
Normal file
37
snowplow-javascript-tracker/core/package.json
Normal 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"
|
||||
}
|
20
snowplow-javascript-tracker/core/tests/intern.js
Normal file
20
snowplow-javascript-tracker/core/tests/intern.js
Normal 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)\//
|
||||
|
||||
});
|
32
snowplow-javascript-tracker/core/tests/unit/base64.js
Normal file
32
snowplow-javascript-tracker/core/tests/unit/base64.js
Normal 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');
|
||||
}
|
||||
});
|
||||
});
|
327
snowplow-javascript-tracker/core/tests/unit/contexts.js
Normal file
327
snowplow-javascript-tracker/core/tests/unit/contexts.js
Normal 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');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
747
snowplow-javascript-tracker/core/tests/unit/core.js
Normal file
747
snowplow-javascript-tracker/core/tests/unit/core.js
Normal 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');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
121
snowplow-javascript-tracker/core/tests/unit/payload.js
Normal file
121
snowplow-javascript-tracker/core/tests/unit/payload.js
Normal 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');
|
||||
}
|
||||
});
|
||||
});
|
26
snowplow-javascript-tracker/core/tsconfig.json
Normal file
26
snowplow-javascript-tracker/core/tsconfig.json
Normal 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
|
||||
}
|
||||
}
|
5
snowplow-javascript-tracker/core/tslint.json
Normal file
5
snowplow-javascript-tracker/core/tslint.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"rules": {
|
||||
"indent": [true, "tabs"]
|
||||
}
|
||||
}
|
4
snowplow-javascript-tracker/dist/.gitignore
vendored
Normal file
4
snowplow-javascript-tracker/dist/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Assembled files
|
||||
bundle.js
|
||||
snowplow.js
|
||||
sp.js
|
164
snowplow-javascript-tracker/examples/ads/async.html
Normal file
164
snowplow-javascript-tracker/examples/ads/async.html
Normal 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>
|
BIN
snowplow-javascript-tracker/examples/ads/images/banner-1.png
Normal file
BIN
snowplow-javascript-tracker/examples/ads/images/banner-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
snowplow-javascript-tracker/examples/ads/images/banner-2.png
Normal file
BIN
snowplow-javascript-tracker/examples/ads/images/banner-2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
snowplow-javascript-tracker/examples/ads/images/banner-3.png
Normal file
BIN
snowplow-javascript-tracker/examples/ads/images/banner-3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
277
snowplow-javascript-tracker/examples/web/async-large.html
Normal file
277
snowplow-javascript-tracker/examples/web/async-large.html
Normal 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>
|
191
snowplow-javascript-tracker/examples/web/async-medium.html
Normal file
191
snowplow-javascript-tracker/examples/web/async-medium.html
Normal 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>
|
168
snowplow-javascript-tracker/examples/web/async-small.html
Normal file
168
snowplow-javascript-tracker/examples/web/async-small.html
Normal 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>
|
74
snowplow-javascript-tracker/examples/web/sync.html
Normal file
74
snowplow-javascript-tracker/examples/web/sync.html
Normal 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>
|
15
snowplow-javascript-tracker/local_test.sh
Executable file
15
snowplow-javascript-tracker/local_test.sh
Executable 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
|
6046
snowplow-javascript-tracker/npm-shrinkwrap.json
generated
Normal file
6046
snowplow-javascript-tracker/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load diff
49
snowplow-javascript-tracker/package.json
Normal file
49
snowplow-javascript-tracker/package.json
Normal 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
|
||||
}
|
127
snowplow-javascript-tracker/src/js/errors.js
Normal file
127
snowplow-javascript-tracker/src/js/errors.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
189
snowplow-javascript-tracker/src/js/forms.js
Executable file
189
snowplow-javascript-tracker/src/js/forms.js
Executable 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
60
snowplow-javascript-tracker/src/js/guard.js
Normal file
60
snowplow-javascript-tracker/src/js/guard.js
Normal 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;
|
||||
};
|
193
snowplow-javascript-tracker/src/js/in_queue.js
Normal file
193
snowplow-javascript-tracker/src/js/in_queue.js
Normal 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
|
||||
};
|
||||
};
|
||||
|
||||
}());
|
53
snowplow-javascript-tracker/src/js/init.js
Normal file
53
snowplow-javascript-tracker/src/js/init.js
Normal 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');
|
||||
}
|
254
snowplow-javascript-tracker/src/js/lib/detectors.js
Normal file
254
snowplow-javascript-tracker/src/js/lib/detectors.js
Normal 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;
|
||||
};
|
||||
|
||||
}());
|
436
snowplow-javascript-tracker/src/js/lib/helpers.js
Executable file
436
snowplow-javascript-tracker/src/js/lib/helpers.js
Executable 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;
|
||||
}
|
||||
|
||||
}());
|
101
snowplow-javascript-tracker/src/js/lib/proxies.js
Normal file
101
snowplow-javascript-tracker/src/js/lib/proxies.js
Normal 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];
|
||||
};
|
||||
|
||||
}());
|
181
snowplow-javascript-tracker/src/js/links.js
Executable file
181
snowplow-javascript-tracker/src/js/links.js
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
369
snowplow-javascript-tracker/src/js/out_queue.js
Normal file
369
snowplow-javascript-tracker/src/js/out_queue.js
Normal 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
|
||||
};
|
||||
};
|
||||
|
||||
}());
|
268
snowplow-javascript-tracker/src/js/snowplow.js
Normal file
268
snowplow-javascript-tracker/src/js/snowplow.js
Normal 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);
|
||||
};
|
||||
|
||||
}());
|
2739
snowplow-javascript-tracker/src/js/tracker.js
Executable file
2739
snowplow-javascript-tracker/src/js/tracker.js
Executable file
File diff suppressed because it is too large
Load diff
81
snowplow-javascript-tracker/tags/tag.js
Normal file
81
snowplow-javascript-tracker/tags/tag.js
Normal 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'));
|
201
snowplow-javascript-tracker/tests/functional/detectors.js
Normal file
201
snowplow-javascript-tracker/tests/functional/detectors.js
Normal 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');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
98
snowplow-javascript-tracker/tests/functional/helpers.js
Normal file
98
snowplow-javascript-tracker/tests/functional/helpers.js
Normal 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');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
320
snowplow-javascript-tracker/tests/integration/integration.js
Executable file
320
snowplow-javascript-tracker/tests/integration/integration.js
Executable 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');
|
||||
}
|
||||
});
|
||||
});
|
52
snowplow-javascript-tracker/tests/integration/setup.js
Normal file
52
snowplow-javascript-tracker/tests/integration/setup.js
Normal 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
|
||||
}
|
||||
});
|
||||
});
|
61
snowplow-javascript-tracker/tests/intern.js
Normal file
61
snowplow-javascript-tracker/tests/intern.js
Normal 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)\//
|
||||
|
||||
});
|
71
snowplow-javascript-tracker/tests/local/http-server.py
Normal file
71
snowplow-javascript-tracker/tests/local/http-server.py
Normal 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)
|
156
snowplow-javascript-tracker/tests/nonfunctional/helpers.js
Normal file
156
snowplow-javascript-tracker/tests/nonfunctional/helpers.js
Normal 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);
|
||||
},
|
||||
});
|
||||
});
|
104
snowplow-javascript-tracker/tests/nonfunctional/in_queue.js
Executable file
104
snowplow-javascript-tracker/tests/nonfunctional/in_queue.js
Executable 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);
|
||||
}
|
||||
});
|
||||
});
|
141
snowplow-javascript-tracker/tests/nonfunctional/proxies.js
Normal file
141
snowplow-javascript-tracker/tests/nonfunctional/proxies.js
Normal 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');
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
25
snowplow-javascript-tracker/tests/pages/detectors.html
Normal file
25
snowplow-javascript-tracker/tests/pages/detectors.html
Normal 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>
|
21
snowplow-javascript-tracker/tests/pages/helpers.html
Normal file
21
snowplow-javascript-tracker/tests/pages/helpers.html
Normal 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>
|
|
@ -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>
|
44
snowplow-javascript-tracker/tests/scripts/detectors.js
Normal file
44
snowplow-javascript-tracker/tests/scripts/detectors.js
Normal 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);
|
42
snowplow-javascript-tracker/tests/scripts/helpers.js
Normal file
42
snowplow-javascript-tracker/tests/scripts/helpers.js
Normal 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';
|
||||
});
|
3
snowplow-javascript-tracker/vagrant/.gitignore
vendored
Normal file
3
snowplow-javascript-tracker/vagrant/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.peru
|
||||
oss-playbooks
|
||||
ansible
|
2
snowplow-javascript-tracker/vagrant/ansible.hosts
Normal file
2
snowplow-javascript-tracker/vagrant/ansible.hosts
Normal file
|
@ -0,0 +1,2 @@
|
|||
[vagrant]
|
||||
127.0.0.1:2222
|
14
snowplow-javascript-tracker/vagrant/peru.yaml
Normal file
14
snowplow-javascript-tracker/vagrant/peru.yaml
Normal 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
|
50
snowplow-javascript-tracker/vagrant/up.bash
Executable file
50
snowplow-javascript-tracker/vagrant/up.bash
Executable 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
|
6
snowplow-javascript-tracker/vagrant/up.guidance
Normal file
6
snowplow-javascript-tracker/vagrant/up.guidance
Normal file
|
@ -0,0 +1,6 @@
|
|||
To get started:
|
||||
vagrant ssh
|
||||
cd /vagrant
|
||||
sudo npm install
|
||||
cd core
|
||||
sudo npm install
|
3
snowplow-javascript-tracker/vagrant/up.playbooks
Normal file
3
snowplow-javascript-tracker/vagrant/up.playbooks
Normal file
|
@ -0,0 +1,3 @@
|
|||
oss-playbooks/java10.yml
|
||||
oss-playbooks/nodejs.yml
|
||||
oss-playbooks/grunt.yml
|
Loading…
Reference in a new issue