mousereco 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/LICENSE.txt +22 -0
- data/README.md +34 -0
- data/Rakefile +34 -0
- data/app/assets/javascripts/mousereco/mousereco.js +15 -0
- data/app/assets/javascripts/mousereco/pageviews.js +244 -0
- data/app/assets/javascripts/mousereco/tracker.js.erb +312 -0
- data/app/assets/stylesheets/mousereco/bootstrap.min.css +9 -0
- data/app/assets/stylesheets/mousereco/mousereco.css +39 -0
- data/app/controllers/mousereco/api/v1/application_controller.rb +18 -0
- data/app/controllers/mousereco/api/v1/events_controller.rb +17 -0
- data/app/controllers/mousereco/api/v1/pageviews_controller.rb +21 -0
- data/app/controllers/mousereco/application_controller.rb +5 -0
- data/app/controllers/mousereco/pageviews_controller.rb +8 -0
- data/app/controllers/mousereco/trackers_controller.rb +6 -0
- data/app/controllers/mousereco/visitors_controller.rb +5 -0
- data/app/helpers/mousereco/application_helper.rb +4 -0
- data/app/helpers/mousereco/pageview_helper.rb +7 -0
- data/app/models/mousereco/click.rb +4 -0
- data/app/models/mousereco/event.rb +13 -0
- data/app/models/mousereco/mousemove.rb +4 -0
- data/app/models/mousereco/pageview.rb +15 -0
- data/app/models/mousereco/scroll.rb +4 -0
- data/app/models/mousereco/visitor.rb +13 -0
- data/app/services/mousereco/events_service.rb +21 -0
- data/app/services/mousereco/pageviews_service.rb +14 -0
- data/app/views/layouts/mousereco/application.html.haml +21 -0
- data/app/views/mousereco/pageviews/page_html.html.haml +1 -0
- data/app/views/mousereco/pageviews/show.html.haml +18 -0
- data/app/views/mousereco/visitors/index.html.haml +12 -0
- data/config/routes.rb +25 -0
- data/db/migrate/20140128001612_create_mousereco_events.rb +14 -0
- data/db/migrate/20140128001832_create_mousereco_pageviews.rb +17 -0
- data/db/migrate/20140128001950_create_mousereco_visitors.rb +9 -0
- data/db/migrate/20140130215132_add_limit_to_timestamps.rb +12 -0
- data/lib/generators/mousereco/install/install_generator.rb +22 -0
- data/lib/mousereco/engine.rb +9 -0
- data/lib/mousereco/version.rb +3 -0
- data/lib/mousereco.rb +4 -0
- data/lib/tasks/mousereco_tasks.rake +4 -0
- data/spec/api/mousereco/events_api_spec.rb +37 -0
- data/spec/api/mousereco/pageviews_api_spec.rb +58 -0
- data/spec/controllers/mousereco/pageviews_controller_spec.rb +12 -0
- data/spec/controllers/mousereco/visitors_controller_spec.rb +12 -0
- data/spec/dummy/Gemfile +31 -0
- data/spec/dummy/Gemfile.lock +174 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +14 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/home_controller.rb +4 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/home/index.html.haml +2 -0
- data/spec/dummy/app/views/layouts/application.html.haml +17 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +27 -0
- data/spec/dummy/config/boot.rb +4 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +7 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20140128221416_create_mousereco_events.mousereco.rb +15 -0
- data/spec/dummy/db/migrate/20140128221417_create_mousereco_pageviews.mousereco.rb +18 -0
- data/spec/dummy/db/migrate/20140128221418_create_mousereco_visitors.mousereco.rb +10 -0
- data/spec/dummy/db/migrate/20140130215835_add_limit_to_timestamps.mousereco.rb +13 -0
- data/spec/dummy/db/schema.rb +48 -0
- data/spec/dummy/db/seeds.rb +7 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +13721 -0
- data/spec/dummy/log/test.log +13232 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/robots.txt +5 -0
- data/spec/dummy/public/test.html +59 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/1510a9c9dc22b2be9eed26a8ed2dabe8 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/16d2919a6d361fd8c6e82b279d64067d +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/1d3e49d5614179160c0b571a9927dcc1 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/1de21ea3d9fadc1a1c3269614b19495b +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/2533caf6d0459a8624fd40d33cddde85 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/29cde272ab8d39abcb99bfc3cba51e1b +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/2fd3dabf7baee6c0702bc29f86d2a70c +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/2ffb0b37de65c4bc12c9759935bcfa4a +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/40091f436d9a77d1d9a9de7f3da46098 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/48139bef1023ab80e22b4891191c5c3a +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/4ea33e825e6e38bd22dccab488a0255b +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/54d5f50daadb633077b6cd9dd9fb72cc +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/5817965bb0355e7b0da40f9b36153ef6 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/5cbf24a4616e0252f26d7949679a42fe +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/5d1aff6b78ca0eab106290139d3410a3 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/6d498a2bfb1bc8566133e4eb5c0ccd35 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/727930fb0f32b87625179e6a77770d16 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/7be9086c960bb4384c9d23a52e489f29 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/7cd76d5e9ba9b8658678d1455acfab74 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/7d94acf804831ce74c7bf5455c1782f0 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/8f8936f164170e095c5ca2d0c2ae6127 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/912559948251ede2d7fcd0cbd58654da +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/9d48f1ca00d652a327f6e8f7c7fc0d40 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/a9cf04e553d11e0103cf1c870e363d7d +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/b02bb55c99ea1e7b80cf52548ee0d1c5 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/b9b341a8be874e4eee26dce66499bc80 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/c6793056549a28ef6b4d0a4a8929f7e3 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/cac5a12e0a520ce86636e0443f3da875 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/cb21171d87e8bb8b243a86594263deff +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/cc5195a4891bb336aeb52b296f4971e9 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/d44cfedd9e50cf9fd20463a31bad4ce3 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/e6c6c2db7ca06e62d0b8d81c9ee3fcf0 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/e73e1a39e822bb2763fbb90a3d41498f +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/eae4c7b940819b55676cea6e03251739 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/f6f1db9d7bdcc747aae3fe9f3ec43dab +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/fe8b16f9af7353e9609c7fe6259d6912 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/1d3e49d5614179160c0b571a9927dcc1 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/2154a1002f636a71bd688ee3b7a40295 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/2ffb0b37de65c4bc12c9759935bcfa4a +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/40091f436d9a77d1d9a9de7f3da46098 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/417edfca088d0fa2811b10666b3adea4 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/48139bef1023ab80e22b4891191c5c3a +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/4ea33e825e6e38bd22dccab488a0255b +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/54d5f50daadb633077b6cd9dd9fb72cc +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/5817965bb0355e7b0da40f9b36153ef6 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/5cbf24a4616e0252f26d7949679a42fe +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/6d498a2bfb1bc8566133e4eb5c0ccd35 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/7be9086c960bb4384c9d23a52e489f29 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/7d94acf804831ce74c7bf5455c1782f0 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/9fe452757d2a02edb37d0d7901709d9c +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/a9cf04e553d11e0103cf1c870e363d7d +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/b02bb55c99ea1e7b80cf52548ee0d1c5 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/b9b341a8be874e4eee26dce66499bc80 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/c7cac2711d4e453b8698081414f6bc73 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/cac5a12e0a520ce86636e0443f3da875 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/cb21171d87e8bb8b243a86594263deff +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/cc5195a4891bb336aeb52b296f4971e9 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/e6c6c2db7ca06e62d0b8d81c9ee3fcf0 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/f6f1db9d7bdcc747aae3fe9f3ec43dab +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/fe8b16f9af7353e9609c7fe6259d6912 +0 -0
- data/spec/dummy/tmp/pids/server.pid +1 -0
- data/spec/fabricators/pageview.rb +3 -0
- data/spec/fabricators/visitor.rb +3 -0
- data/spec/spec_helper.rb +20 -0
- metadata +393 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YTI3YjRjZmNhZWMwN2M3NGUwYTcyMmFlOWI2ZDg0ZDlhNjg1ZjhkNw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NDJjZjQ1NTJhMWI5ZGM1MGIxOWI2NWI1YzIzNDMzMGJjZWFjYWJlNg==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NWY4YWJhYjIyNjFhYTM4ZjNmYzVkNzI0YThmYjI2ZjQzMmQ5ZDc4M2U3OGZl
|
10
|
+
OWM2NmRlN2E2YjkyYmQzNzVlZDAzNDRkZGY1ZGI3NjA0MWVmYjI3MzM5MDY1
|
11
|
+
ZjVjZTRjZTUzZDIzZjljN2NmNDY5MjBmYTNlYTlmYTFhZmMyMjA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NTkzMWEzM2VlMTEyYTM1OWI0YmJlMmZlZTFkMDFhYjE4MmIzMTQ3ZGEwODU4
|
14
|
+
MWE0NmQyYjE3ZDI3N2VhNzc5YTAwOGZhZThkMjE1ODI5YzE1MTdiY2Y2YTNm
|
15
|
+
ZDY5YjQ1M2I1MDE3MTdjN2VmOTNmOTc0ODIzZDJlYmJkYTZkODQ=
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Paweł Nguyen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Mousereco
|
2
|
+
|
3
|
+
Mousereco is a mouse recording Rails engine with a web interface used to replay user visits
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'mousereco'
|
10
|
+
|
11
|
+
Execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
And then run install generator:
|
16
|
+
|
17
|
+
$ rails generate mousereco:install
|
18
|
+
|
19
|
+
It will:
|
20
|
+
- add a tracker js code to your application.js file
|
21
|
+
- mount Mousereco at ```/mousereco``` route
|
22
|
+
- install and run migrations needed for Mousereco
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
By default Mousereco engine will be mounted at ```/mousereco``` path in your application. You can replay your recordings there
|
27
|
+
|
28
|
+
## Contributing
|
29
|
+
|
30
|
+
1. Fork it
|
31
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
32
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
33
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
34
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Mousereco'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
Bundler::GemHelper.install_tasks
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'lib'
|
28
|
+
t.libs << 'test'
|
29
|
+
t.pattern = 'test/**/*_test.rb'
|
30
|
+
t.verbose = false
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
task default: :test
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require mousereco/pageviews
|
@@ -0,0 +1,244 @@
|
|
1
|
+
var mouseRecorder = mouseRecorder || {};
|
2
|
+
mouseRecorder.modules = mouseRecorder.modules || {};
|
3
|
+
mouseRecorder.views = mouseRecorder.views || {};
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
(function(namespace) {
|
8
|
+
|
9
|
+
var EVENTS = {
|
10
|
+
MOVE: 'mousemove',
|
11
|
+
SCROLL: 'scroll'
|
12
|
+
},
|
13
|
+
playDelay = 500,
|
14
|
+
duartionInterval = 100,
|
15
|
+
Player = function(events, $iframe){
|
16
|
+
this.mouseEvents = [];
|
17
|
+
this.scrollEvents = [];
|
18
|
+
this.currentMouseEvent = 0;
|
19
|
+
this.currentScrollEvent = 0;
|
20
|
+
|
21
|
+
this.currentMouseEventTimestamp = events[0].timestamp - playDelay;
|
22
|
+
this.mouseRealTimestamp = 0;
|
23
|
+
this.mousePauseTimeout = 0;
|
24
|
+
|
25
|
+
this.currentScrollEventTimestamp = events[0].timestamp - playDelay;
|
26
|
+
this.scrollRealTimestamp = 0;
|
27
|
+
this.scrollPauseTimeout = 0;
|
28
|
+
|
29
|
+
this.currentPlayPosition = 0;
|
30
|
+
|
31
|
+
this.$iframe = $iframe;
|
32
|
+
this.$mouse = null;
|
33
|
+
|
34
|
+
this.getDuration(events);
|
35
|
+
this.groupEvents(events);
|
36
|
+
this.createMouse();
|
37
|
+
};
|
38
|
+
Player.prototype = {
|
39
|
+
getDuration: function(events) {
|
40
|
+
if(events === undefined) {
|
41
|
+
return this.duration;
|
42
|
+
}
|
43
|
+
else {
|
44
|
+
this.duration = playDelay + (events[events.length - 1].timestamp - events[0].timestamp);
|
45
|
+
}
|
46
|
+
},
|
47
|
+
getPlayPosition: function() {
|
48
|
+
return Math.min(this.currentPlayPosition, this.duration);
|
49
|
+
},
|
50
|
+
groupEvents: function(events) {
|
51
|
+
for(var i in events) {
|
52
|
+
events[i].type = events[i].type.toLowerCase();
|
53
|
+
if(events[i].type == EVENTS.SCROLL) {
|
54
|
+
this.scrollEvents.push(events[i]);
|
55
|
+
}
|
56
|
+
else {
|
57
|
+
this.mouseEvents.push(events[i]);
|
58
|
+
}
|
59
|
+
}
|
60
|
+
},
|
61
|
+
createMouse: function() {
|
62
|
+
if(!this.$mouse) {
|
63
|
+
this.$iframe.append('<div id="mouse" style="max-width:50px;min-width:10px;position:absolute;top:' + 0 + 'px;left:' + 0 + 'px;color:blue;font-size:10px;z-index:99999;background-color:black;">Mouse</div>');
|
64
|
+
this.$mouse = this.$iframe.find('#mouse');
|
65
|
+
}
|
66
|
+
},
|
67
|
+
play: function() {
|
68
|
+
this.paused = false;
|
69
|
+
this.playScroll();
|
70
|
+
this.playMouse();
|
71
|
+
this.playDuration();
|
72
|
+
},
|
73
|
+
pause: function() {
|
74
|
+
this.paused = true;
|
75
|
+
clearTimeout(this.scrollTimeout);
|
76
|
+
clearTimeout(this.mouseTimeout);
|
77
|
+
clearInterval(this.durationTicker);
|
78
|
+
this.$mouse.stop();
|
79
|
+
this.mousePauseTimeout += (new Date()).getTime() - this.mouseRealTimestamp;
|
80
|
+
this.scrollPauseTimeout += (new Date()).getTime() - this.scrollRealTimestamp;
|
81
|
+
},
|
82
|
+
playDuration: function() {
|
83
|
+
var self = this,
|
84
|
+
$self = $(this);
|
85
|
+
this.durationTicker = setInterval(function() {
|
86
|
+
self.currentPlayPosition += duartionInterval;
|
87
|
+
$self.trigger('duration.update');
|
88
|
+
self.end();
|
89
|
+
}, duartionInterval);
|
90
|
+
},
|
91
|
+
playMouse: function() {
|
92
|
+
if(!this.paused && this.mouseEvents[this.currentMouseEvent]) {
|
93
|
+
var self = this;
|
94
|
+
if(this.currentMouseEvent === 0) {
|
95
|
+
var event = this.mouseEvents[this.currentMouseEvent],
|
96
|
+
timeout = event.timestamp - this.currentMouseEventTimestamp;
|
97
|
+
this.mouseRealTimestamp = (new Date()).getTime();
|
98
|
+
this.mouseTimeout = setTimeout(function() {
|
99
|
+
if(!self.paused) {
|
100
|
+
self.currentMouseEvent++;
|
101
|
+
self.currentMouseEventTimestamp = event.timestamp;
|
102
|
+
self.showEvent(event);
|
103
|
+
self.playMouse();
|
104
|
+
}
|
105
|
+
}, timeout);
|
106
|
+
}
|
107
|
+
else {
|
108
|
+
var currentEvent = this.mouseEvents[this.currentMouseEvent],
|
109
|
+
previousEvent = this.mouseEvents[this.currentMouseEvent - 1],
|
110
|
+
tmpPauseTimestamp = this.mousePauseTimeout;
|
111
|
+
this.mouseRealTimestamp = (new Date()).getTime();
|
112
|
+
this.$mouse.animate({
|
113
|
+
top: currentEvent.y,
|
114
|
+
left: currentEvent.x
|
115
|
+
}, currentEvent.timestamp - previousEvent.timestamp - tmpPauseTimestamp, function() {
|
116
|
+
if(!self.paused) {
|
117
|
+
self.mousePauseTimeout = 0;
|
118
|
+
self.currentMouseEvent++;
|
119
|
+
self.currentMouseEventTimestamp = currentEvent.timestamp;
|
120
|
+
self.showEvent(currentEvent);
|
121
|
+
self.playMouse();
|
122
|
+
}
|
123
|
+
});
|
124
|
+
}
|
125
|
+
}
|
126
|
+
},
|
127
|
+
playScroll: function() {
|
128
|
+
if(!this.paused && this.scrollEvents[this.currentScrollEvent]) {
|
129
|
+
var self = this,
|
130
|
+
event = this.scrollEvents[this.currentScrollEvent],
|
131
|
+
timeout = event.timestamp - this.currentScrollEventTimestamp - this.scrollPauseTimeout;
|
132
|
+
this.scrollRealTimestamp = (new Date()).getTime();
|
133
|
+
this.scrollTimeout = setTimeout(function() {
|
134
|
+
if(!self.paused) {
|
135
|
+
self.scrollPauseTimeout = 0;
|
136
|
+
self.currentScrollEvent++;
|
137
|
+
self.currentScrollEventTimestamp = event.timestamp;
|
138
|
+
self.showEvent(event);
|
139
|
+
self.playScroll();
|
140
|
+
}
|
141
|
+
}, timeout);
|
142
|
+
}
|
143
|
+
},
|
144
|
+
end: function() {
|
145
|
+
if(this.currentPlayPosition >= this.duration) {
|
146
|
+
this.pause();
|
147
|
+
// @TODO emit finished event
|
148
|
+
}
|
149
|
+
},
|
150
|
+
showEvent: function(event) {
|
151
|
+
switch(event.type) {
|
152
|
+
case EVENTS.SCROLL:
|
153
|
+
this.$iframe.scrollTop(event.y);
|
154
|
+
this.$iframe.scrollLeft(event.x);
|
155
|
+
break;
|
156
|
+
default:
|
157
|
+
this.$iframe.append('<div style="position:absolute;top:' + event.y + 'px;left:' + event.x + 'px;color:red;z-index:99998;">' + this.currentMouseEvent + '-' + event.type + '</div>');
|
158
|
+
break;
|
159
|
+
}
|
160
|
+
}
|
161
|
+
};
|
162
|
+
|
163
|
+
namespace.Player = Player;
|
164
|
+
})(mouseRecorder.modules);
|
165
|
+
|
166
|
+
|
167
|
+
(function(namespace) {
|
168
|
+
|
169
|
+
var EVENTS = 'data-events',
|
170
|
+
WIDTH = 'data-window-width',
|
171
|
+
HEIGHT = 'data-window-height',
|
172
|
+
URL = 'data-url',
|
173
|
+
Pageviews = function(events) {
|
174
|
+
this.$events = $(events);
|
175
|
+
this.$iframe = this.getIframe();
|
176
|
+
this.$progressbar = $('.progress-bar');
|
177
|
+
this.events = this.parseEvents();
|
178
|
+
this.init();
|
179
|
+
|
180
|
+
};
|
181
|
+
Pageviews.prototype = {
|
182
|
+
parseEvents: function() {
|
183
|
+
return $.parseJSON(this.$events.attr(EVENTS));
|
184
|
+
},
|
185
|
+
getIframe: function() {
|
186
|
+
var $iframe = $('#pageview_window');
|
187
|
+
$iframe.attr('src', this.$events.attr(URL));
|
188
|
+
$iframe.attr('width', this.$events.attr(WIDTH));
|
189
|
+
$iframe.attr('height', this.$events.attr(HEIGHT));
|
190
|
+
return $iframe;
|
191
|
+
},
|
192
|
+
formatTime: function(timeMs) {
|
193
|
+
var time = timeMs / 100,
|
194
|
+
h = Math.floor(time / (10 * 60 * 60)),
|
195
|
+
m = Math.floor(time / (10 * 60)) % 60,
|
196
|
+
s = Math.floor(time / 10) % 60,
|
197
|
+
ms = Math.floor(time % 10);
|
198
|
+
|
199
|
+
return this.padTime(m) + ':' + this.padTime(s) + ':' + ms;
|
200
|
+
},
|
201
|
+
padTime: function(time) {
|
202
|
+
return time < 10 ? '0' + time : time;
|
203
|
+
},
|
204
|
+
getTime: function(current, duration) {
|
205
|
+
return this.formatTime(current) + ' / ' + this.formatTime(duration);
|
206
|
+
},
|
207
|
+
init: function() {
|
208
|
+
var player,
|
209
|
+
self = this;
|
210
|
+
this.$iframe.on('load', $.proxy(function() {
|
211
|
+
this.player = player = new mouseRecorder.modules.Player(this.events, this.$iframe.contents().find('body'));
|
212
|
+
var duration = player.getDuration(),
|
213
|
+
$label = self.$progressbar.next();
|
214
|
+
$label.text(self.getTime(0, duration));
|
215
|
+
$(this.player).on('duration.update', function() {
|
216
|
+
self.$progressbar.width(Math.round((this.getPlayPosition() / this.getDuration() * 100 * 100))/100 + '%');
|
217
|
+
$label.text(self.getTime(this.getPlayPosition(), duration));
|
218
|
+
});
|
219
|
+
}, this));
|
220
|
+
$('button.play').on('click', function() {
|
221
|
+
if(player) {
|
222
|
+
var $el = $(this);
|
223
|
+
if($el.hasClass('play')) {
|
224
|
+
$el.toggleClass('play').text('pause');
|
225
|
+
player.play()
|
226
|
+
}
|
227
|
+
else {
|
228
|
+
$el.toggleClass('play').text('play');
|
229
|
+
player.pause();
|
230
|
+
}
|
231
|
+
}
|
232
|
+
});
|
233
|
+
}
|
234
|
+
};
|
235
|
+
|
236
|
+
namespace.Pageviews = Pageviews;
|
237
|
+
})(mouseRecorder.views);
|
238
|
+
|
239
|
+
|
240
|
+
$(document).on('ready', function() {
|
241
|
+
if($('body').hasClass('pageviews')) {
|
242
|
+
var pageviews = new mouseRecorder.views.Pageviews('#events');
|
243
|
+
}
|
244
|
+
});
|
@@ -0,0 +1,312 @@
|
|
1
|
+
(function(){
|
2
|
+
var MouseRecorder = {};
|
3
|
+
|
4
|
+
|
5
|
+
/**
|
6
|
+
* MouseRecorder.Events
|
7
|
+
*/
|
8
|
+
MouseRecorder.Events = {
|
9
|
+
CLICK: 'click',
|
10
|
+
MOVE: 'mousemove',
|
11
|
+
SCROLL: 'scroll',
|
12
|
+
OVER: 'mouseover'
|
13
|
+
},
|
14
|
+
|
15
|
+
|
16
|
+
/**
|
17
|
+
* MouseRecorder.Tracker
|
18
|
+
*/
|
19
|
+
(function(namespace){
|
20
|
+
|
21
|
+
var instance,
|
22
|
+
mousePositionTrackInterval = 100,
|
23
|
+
mouseScrollTrackInterval = 100,
|
24
|
+
mouseOverTrackInterval = 250,
|
25
|
+
body = document.getElementsByTagName('body')[0],
|
26
|
+
Tracker = function() {
|
27
|
+
this.pusher = new MouseRecorder.Pusher();
|
28
|
+
this.pusher.html();
|
29
|
+
|
30
|
+
this.coords = {};
|
31
|
+
this.initListeners();
|
32
|
+
};
|
33
|
+
Tracker.prototype = {
|
34
|
+
on: {},
|
35
|
+
initListeners: function() {
|
36
|
+
MouseRecorder.Util.addEventListener(window, MouseRecorder.Events.OVER, this.withInterval(MouseRecorder.Events.OVER, mouseOverTrackInterval, MouseRecorder.Events.MOVE), this)
|
37
|
+
MouseRecorder.Util.addEventListener(window, MouseRecorder.Events.SCROLL, this.withInterval(MouseRecorder.Events.SCROLL, mouseScrollTrackInterval), this);
|
38
|
+
MouseRecorder.Util.addEventListener(document, MouseRecorder.Events.MOVE, this.withInterval(MouseRecorder.Events.MOVE, mousePositionTrackInterval), this);
|
39
|
+
MouseRecorder.Util.addEventListener(document, MouseRecorder.Events.CLICK, this.pusher.receive, this.pusher);
|
40
|
+
},
|
41
|
+
withInterval: function(eventType, interval, asEvent) {
|
42
|
+
var self = this,
|
43
|
+
sourceEvent = eventType,
|
44
|
+
destEvent = (asEvent === undefined ? eventType : asEvent);
|
45
|
+
setInterval(function() {
|
46
|
+
if(self.coords[sourceEvent] && self.coords[sourceEvent].update === true) {
|
47
|
+
self.coords[sourceEvent].update = false;
|
48
|
+
self.pusher.receive(self.coords[sourceEvent], destEvent);
|
49
|
+
}
|
50
|
+
}, interval);
|
51
|
+
return function(event) {
|
52
|
+
var data = self.on[sourceEvent](event);
|
53
|
+
self.coords[sourceEvent] = self.coords[sourceEvent] || {};
|
54
|
+
self.coords[sourceEvent].update = true;
|
55
|
+
self.coords[sourceEvent].pageX = data[0];
|
56
|
+
self.coords[sourceEvent].pageY = data[1];
|
57
|
+
};
|
58
|
+
}
|
59
|
+
};
|
60
|
+
Tracker.prototype.on[MouseRecorder.Events.SCROLL] = function(event) {
|
61
|
+
return [body.scrollLeft, body.scrollTop];
|
62
|
+
};
|
63
|
+
Tracker.prototype.on[MouseRecorder.Events.MOVE] = function(event) {
|
64
|
+
return [event.pageX, event.pageY];
|
65
|
+
};
|
66
|
+
Tracker.prototype.on[MouseRecorder.Events.OVER] = function(event) {
|
67
|
+
return [event.pageX, event.pageY];
|
68
|
+
}
|
69
|
+
|
70
|
+
namespace.Tracker = {
|
71
|
+
instance: function() {
|
72
|
+
if(!instance) {
|
73
|
+
instance = new Tracker();
|
74
|
+
}
|
75
|
+
return instance;
|
76
|
+
}
|
77
|
+
};
|
78
|
+
})(MouseRecorder);
|
79
|
+
|
80
|
+
|
81
|
+
/**
|
82
|
+
* MouseRecorder.Pusher
|
83
|
+
*/
|
84
|
+
(function(namespace) {
|
85
|
+
|
86
|
+
<% url_helpers = Mousereco::Engine.routes.url_helpers %>
|
87
|
+
var eventsPath = '<%= url_helpers.api_v1_events_path %>',
|
88
|
+
htmlPath = '<%= url_helpers.api_v1_pageviews_path %>',
|
89
|
+
cookie = 'MouseRecorder_visitorKey',
|
90
|
+
pushTimeout = 2500,
|
91
|
+
Pusher = function(){
|
92
|
+
this.url = MouseRecorder.Util.getUrl();
|
93
|
+
this.visitorKey = this.getVisitorKey();
|
94
|
+
this.pageviewKey = MouseRecorder.Util.getUID();
|
95
|
+
this.events = [];
|
96
|
+
this.pushTimer = undefined;
|
97
|
+
};
|
98
|
+
Pusher.prototype = {
|
99
|
+
preflight: function() {
|
100
|
+
// @TODO this.push(this.basicData(), preflightUrl);
|
101
|
+
},
|
102
|
+
html: function() {
|
103
|
+
var data = this.basicData();
|
104
|
+
var window_size = MouseRecorder.Util.getWindowSize();
|
105
|
+
data.window_height = window_size.height;
|
106
|
+
data.window_width = window_size.width;
|
107
|
+
data.page_html = MouseRecorder.Util.getHTML();
|
108
|
+
this.push(data, htmlPath);
|
109
|
+
},
|
110
|
+
receive: function(event, type) {
|
111
|
+
var type = type || MouseRecorder.Events.CLICK,
|
112
|
+
push = (type == MouseRecorder.Events.CLICK);
|
113
|
+
this.events.push({
|
114
|
+
x: event.pageX,
|
115
|
+
y: event.pageY,
|
116
|
+
timestamp: (new Date()).getTime(),
|
117
|
+
type: type
|
118
|
+
});
|
119
|
+
if(push) {
|
120
|
+
this.prePush();
|
121
|
+
}
|
122
|
+
else {
|
123
|
+
var self = this;
|
124
|
+
if(!this.pushTimer) {
|
125
|
+
this.pushTimer = setTimeout(function() {
|
126
|
+
clearTimeout(self.pushTimer);
|
127
|
+
self.pushTimer = undefined;
|
128
|
+
self.prePush();
|
129
|
+
}, pushTimeout);
|
130
|
+
}
|
131
|
+
}
|
132
|
+
},
|
133
|
+
prePush: function() {
|
134
|
+
var data = this.basicData();
|
135
|
+
data.events = this.events;
|
136
|
+
this.events = [];
|
137
|
+
this.push(data, eventsPath);
|
138
|
+
},
|
139
|
+
push: function(data, url, callback) {
|
140
|
+
var request = false,
|
141
|
+
self = this;
|
142
|
+
if (window.ActiveXObject) {
|
143
|
+
var types = ['MSXML2.XmlHttp.5.0', 'MSXML2.XmlHttp.4.0', 'MSXML2.XmlHttp.3.0', 'MSXML2.XmlHttp.2.0', 'Microsoft.XmlHttp'];
|
144
|
+
for(var i in types) {
|
145
|
+
try {
|
146
|
+
request = new ActiveXObject(types[i]);
|
147
|
+
break;
|
148
|
+
} catch(e){}
|
149
|
+
}
|
150
|
+
}
|
151
|
+
else if (window.XMLHttpRequest) {
|
152
|
+
request = new XMLHttpRequest();
|
153
|
+
}
|
154
|
+
|
155
|
+
if(request) {
|
156
|
+
request.open('POST', url, true);
|
157
|
+
if(callback) {
|
158
|
+
request.onreadystatechange = function() {
|
159
|
+
if(this.status === 200 && this.readyState === 4) {
|
160
|
+
callback.apply(self, JSON.parse(this.responseText));
|
161
|
+
}
|
162
|
+
};
|
163
|
+
}
|
164
|
+
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
165
|
+
request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
166
|
+
if(request.readyState != 4) {
|
167
|
+
request.send(MouseRecorder.Util.toString(data));
|
168
|
+
}
|
169
|
+
}
|
170
|
+
},
|
171
|
+
basicData: function() {
|
172
|
+
return {
|
173
|
+
url: this.url,
|
174
|
+
visitor_key: this.visitorKey,
|
175
|
+
pageview_key: this.pageviewKey,
|
176
|
+
timestamp: (new Date()).getTime()
|
177
|
+
};
|
178
|
+
},
|
179
|
+
getVisitorKey: function() {
|
180
|
+
var visitorKeyCookie = MouseRecorder.CookieMonster.get(cookie);
|
181
|
+
if(visitorKeyCookie === null || visitorKeyCookie === undefined || visitorKeyCookie.length === 0) {
|
182
|
+
visitorKeyCookie = MouseRecorder.Util.getUID();
|
183
|
+
}
|
184
|
+
MouseRecorder.CookieMonster.set(cookie, visitorKeyCookie, 60 * 60, '/')
|
185
|
+
return visitorKeyCookie;
|
186
|
+
}
|
187
|
+
};
|
188
|
+
|
189
|
+
namespace.Pusher = Pusher;
|
190
|
+
})(MouseRecorder);
|
191
|
+
|
192
|
+
|
193
|
+
/**
|
194
|
+
* MouseRecorder.Util
|
195
|
+
*/
|
196
|
+
(function(namespace) {
|
197
|
+
namespace.Util = {
|
198
|
+
addEventListener: function(el, event, callback, scope) {
|
199
|
+
var _callback = function() {
|
200
|
+
callback.apply(scope, arguments);
|
201
|
+
};
|
202
|
+
if(el.addEventListener != undefined) {
|
203
|
+
el.addEventListener(event, _callback, false);
|
204
|
+
}
|
205
|
+
else if(el.attachEvent != undefined) {
|
206
|
+
el.attachEvent('on' + event, _callback);
|
207
|
+
}
|
208
|
+
else {
|
209
|
+
el['on' + event] = _callback;
|
210
|
+
}
|
211
|
+
},
|
212
|
+
getUID: function() {
|
213
|
+
return ((((1+Math.random())*0x10000)|0).toString(16) + (new Date().getTime()).toString(16)).substring(1);
|
214
|
+
},
|
215
|
+
getUrl: function() {
|
216
|
+
return document.URL;
|
217
|
+
},
|
218
|
+
toString: function(obj, index) {
|
219
|
+
var params = [];
|
220
|
+
for(var i in obj) {
|
221
|
+
var key = (index == undefined) ? i : index + '[' + i + ']';
|
222
|
+
if(Array.isArray(obj[i])) {
|
223
|
+
params.push(this.toString(obj[i], key));
|
224
|
+
}
|
225
|
+
else if(typeof obj[i] === 'object') {
|
226
|
+
params.push(this.toString(obj[i], key));
|
227
|
+
}
|
228
|
+
else {
|
229
|
+
params.push(key + '=' + obj[i]);
|
230
|
+
}
|
231
|
+
}
|
232
|
+
return params.join('&');
|
233
|
+
},
|
234
|
+
getHTML: function() {
|
235
|
+
var html = document.documentElement.outerHTML.replace(/\s+/g, ' ').replace(/\r?\n|\r/g, ' ').replace(
|
236
|
+
/<head>(.*)<\/head>/, '<base href="' + this.getUrlRoot() + '">' + document.getElementsByTagName('head')[0].innerHTML);
|
237
|
+
return encodeURIComponent(html.replace(/\s+/g, ' ').replace(/\r?\n|\r/g, ' '))
|
238
|
+
},
|
239
|
+
getUrlRoot: function() {
|
240
|
+
return this.getUrl().match(/(.*\/)/)[0];
|
241
|
+
},
|
242
|
+
getWindowSize: function() {
|
243
|
+
return {width: window.innerWidth, height: window.innerHeight};
|
244
|
+
}
|
245
|
+
};
|
246
|
+
})(MouseRecorder);
|
247
|
+
|
248
|
+
|
249
|
+
/**
|
250
|
+
* MouseRecorder.CookieMonster
|
251
|
+
*/
|
252
|
+
(function(namespace){
|
253
|
+
/*!
|
254
|
+
* cookie-monster - a simple cookie library
|
255
|
+
* v0.2.0
|
256
|
+
* https://github.com/jgallen23/cookie-monster
|
257
|
+
* copyright Greg Allen 2013
|
258
|
+
* MIT License
|
259
|
+
*/
|
260
|
+
var monster = {
|
261
|
+
set: function(name, value, seconds, path) {
|
262
|
+
var date = new Date(),
|
263
|
+
expires = '',
|
264
|
+
type = typeof(value),
|
265
|
+
valueToUse = '';
|
266
|
+
path = path || "/";
|
267
|
+
if (seconds) {
|
268
|
+
date.setTime(date.getTime() + seconds * 1000);
|
269
|
+
expires = "; expires=" + date.toUTCString();
|
270
|
+
}
|
271
|
+
if (type === "object" && type !== "undefined") {
|
272
|
+
if(!("JSON" in window)) throw "Bummer, your browser doesn't support JSON parsing.";
|
273
|
+
valueToUse = JSON.stringify({v:value});
|
274
|
+
} else {
|
275
|
+
valueToUse = encodeURIComponent(value);
|
276
|
+
}
|
277
|
+
|
278
|
+
document.cookie = name + "=" + valueToUse + expires + "; path=" + path;
|
279
|
+
},
|
280
|
+
get: function(name) {
|
281
|
+
var nameEQ = name + "=",
|
282
|
+
ca = document.cookie.split(';'),
|
283
|
+
value = '',
|
284
|
+
firstChar = '',
|
285
|
+
parsed={};
|
286
|
+
for (var i = 0; i < ca.length; i++) {
|
287
|
+
var c = ca[i];
|
288
|
+
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
|
289
|
+
if (c.indexOf(nameEQ) === 0) {
|
290
|
+
value = c.substring(nameEQ.length, c.length);
|
291
|
+
firstChar = value.substring(0, 1);
|
292
|
+
if(firstChar=="{"){
|
293
|
+
parsed = JSON.parse(value);
|
294
|
+
if("v" in parsed) return parsed.v;
|
295
|
+
}
|
296
|
+
if (value=="undefined") return undefined;
|
297
|
+
return decodeURIComponent(value);
|
298
|
+
}
|
299
|
+
}
|
300
|
+
return null;
|
301
|
+
},
|
302
|
+
remove: function(name) {
|
303
|
+
this.set(name, "", -1);
|
304
|
+
}
|
305
|
+
};
|
306
|
+
|
307
|
+
namespace.CookieMonster = monster;
|
308
|
+
})(MouseRecorder);
|
309
|
+
|
310
|
+
|
311
|
+
MouseRecorder.Tracker.instance();
|
312
|
+
})();
|