referrer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 89e3a3f6f51b751a3c6aec837885203238dd5381
4
+ data.tar.gz: 9d1eca24fd9a34ffe027a8574280e8c10f30a383
5
+ SHA512:
6
+ metadata.gz: 79c6e178264169070eb6d227b59931c1b9ba1d97a081af2eba8a0f45fac85a60ae7ec235dcc7aa16b14df4513335dfd97fd19ecf7dceeeb1acce3f57a57026ea
7
+ data.tar.gz: 968e753ff946204eb34f6fce3495b389ca1639b88d58fd80af2f0de40055e64f94ab4ba27682c48345b74c07e49b7e5e75ab0ce89bd8a37d2052278c74e52dfa
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Sergey Sokolov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,281 @@
1
+ # Referrer
2
+
3
+ [![Build Status](https://api.travis-ci.org/salkar/referrer.svg?branch=master)](http://travis-ci.org/salkar/referrer)
4
+ [![Code Climate](https://codeclimate.com/github/salkar/referrer.svg)](https://codeclimate.com/github/salkar/referrer)
5
+
6
+ Referrer tracks sources with which users visit your site, computes priority of these sources and provides linking for sources with tracked model's records.
7
+
8
+ Sample questions which Referrer can help to answer:
9
+
10
+ - Where did this user come from?
11
+ - `{utm_source: 'google', utm_medium: 'organic', utm_campaign: '(none)', utm_content: '(none)', utm_term: 'user search query', kind: 'organic'}`
12
+ <br /><br />
13
+ - Where did users come from last month?
14
+ - `[{utm_source: 'google', utm_medium: 'organic', utm_campaign: '(none)', utm_content: '(none)', utm_term: 'user search query', kind: 'organic', count: 1}, {utm_source: 'google', utm_campaign: 'adv_campaign', utm_medium: 'cpc', utm_content: 'adv 1', utm_term: 'some text', kind: 'utm', count: 2}, etc...]`
15
+ <br /><br />
16
+ - Where did user who make purchase come from?
17
+ - `{utm_source: 'twitter.com', utm_medium: 'referral', utm_campaign: '(none)', utm_content: '/some_path', utm_term: '(none)', kind: 'referral'}`
18
+ <br /><br />
19
+ - Where did users who left some requests come from last week?
20
+ - `[{utm_source: '(direct)', utm_medium: '(none)', utm_campaign: '(none)', utm_content: '(none)', utm_term: '(none)', kind: 'direct', count: 3}, {utm_source: 'google', utm_campaign: 'adv_campaign', utm_medium: 'cpc', utm_content: 'adv 2', utm_term: 'another text', kind: 'utm', count: 5}, etc...]`
21
+
22
+ ## Installation
23
+
24
+ ### Basic setup
25
+
26
+ 1. Add Referrer to your Gemfile:
27
+ ```ruby
28
+ gem 'referrer'
29
+ ```
30
+
31
+ 2. Run the bundle command to install it:
32
+ ```bash
33
+ bundle install
34
+ ```
35
+
36
+ 3. Copy migrations from engine:
37
+ ```bash
38
+ rake referrer:install:migrations
39
+ ```
40
+
41
+ 4. Migrate your database:
42
+ ```bash
43
+ rake db:migrate
44
+ ```
45
+
46
+ 5. Mount engine's routes at `config/routes.rb`
47
+ ```ruby
48
+ mount Referrer::Engine => '/referrer'
49
+ ```
50
+
51
+ 6. Require engine's js in your `application.js` file:
52
+ ```ruby
53
+ //= require referrer/application
54
+ ```
55
+
56
+ 7. Add to your ApplicationController:
57
+ ```ruby
58
+ include Referrer::ControllerAdditions
59
+ ```
60
+
61
+ 8. If your `current_user` method has another name, add to `config/initializers/referrer.rb`:
62
+ ```ruby
63
+ Referrer.current_user_method_name = :your_method_name
64
+ ```
65
+
66
+ ### Setup your statistics owners
67
+
68
+ Add to your models which objects may be returned from `current_user` (or your same method, specified in `Referrer.current_user_method_name`) `include Referrer::OwnerModelAdditions`. For sample if your `current_user` return `User` objects:
69
+
70
+ ```ruby
71
+ class User < ActiveRecord::Base
72
+ include Referrer::OwnerModelAdditions
73
+ #...
74
+ end
75
+ ```
76
+
77
+ ### Setup your tracked models
78
+
79
+ 1. Add to models you want to track `include Referrer::TrackedModelAdditions`. For sample if you want to track where users who make orders come from (`Order` class):
80
+ ```ruby
81
+ class Order < ActiveRecord::Base
82
+ include Referrer::TrackedModelAdditions
83
+ #...
84
+ end
85
+ ```
86
+
87
+ 2. After you create tracked model record next time you can link record with current source. For sample (with `Order` object):
88
+ ```ruby
89
+ # OrdersControllers#create
90
+ #...
91
+ if @order.save
92
+ @order.referrer_link_with(referrer_user) # referrer_user defined in Referrer::TrackedModelAdditions
93
+ else
94
+ #...
95
+ ```
96
+
97
+ ## Settings
98
+
99
+ Referrer settings can be changed in initializers. For sample, you can create `config/initializers/referrer.rb` and add your custom settings to it.
100
+
101
+ ###Settings list
102
+
103
+ 1. **current_user_method_name** - method name in ApplicationController which return current logged in object.
104
+ ```ruby
105
+ :current_user
106
+ ```
107
+
108
+ 2. **js_settings** - options passes to js part of Referrer.
109
+ ```ruby
110
+ {}
111
+ ```
112
+ Available options:
113
+ * cookies
114
+
115
+ ```javascript
116
+ {prefix: 'referrer',
117
+ domain: null,
118
+ path: '/'}
119
+ ```
120
+
121
+ * object
122
+
123
+ ```javascript
124
+ {name: 'referrer'}
125
+ ```
126
+
127
+ * callback
128
+
129
+ ```javascript
130
+ null
131
+ ```
132
+
133
+ 3. **js_csrf_token** - js code to get CSRF token if it is used.
134
+ ```ruby
135
+ <<-JS
136
+ var tokenContainer = document.querySelector("meta[name=csrf-token]");
137
+ return tokenContainer ? tokenContainer.content : null;
138
+ JS
139
+ ```
140
+
141
+ 4. **markup_generator_settings** - options for Referrer::MarkupGenerator.
142
+ ```ruby
143
+ {}
144
+ ```
145
+ Available options:
146
+ * organics
147
+
148
+ ```ruby
149
+ [{host: 'search.daum.net', param: 'q'},
150
+ {host: 'search.naver.com', param: 'query'},
151
+ {host: 'search.yahoo.com', param: 'p'},
152
+ {host: /^(www\.)?google\.[a-z]+$/, param: 'q', display: 'google'},
153
+ {host: 'www.bing.com', param: 'q'},
154
+ {host: 'search.aol.com', params: 'q'},
155
+ {host: 'search.lycos.com', param: 'q'},
156
+ {host: 'edition.cnn.com', param: 'text'},
157
+ {host: 'index.about.com', param: 'q'},
158
+ {host: 'mamma.com', param: 'q'},
159
+ {host: 'ricerca.virgilio.it', param: 'qs'},
160
+ {host: 'www.baidu.com', param: 'wd'},
161
+ {host: /^(www\.)?yandex\.[a-z]+$/, param: 'text', display: 'yandex'},
162
+ {host: 'search.seznam.cz', param: 'oq'},
163
+ {host: 'www.search.com', param: 'q'},
164
+ {host: 'search.yam.com', param: 'k'},
165
+ {host: 'www.kvasir.no', param: 'q'},
166
+ {host: 'buscador.terra.com', param: 'query'},
167
+ {host: 'nova.rambler.ru', param: 'query'},
168
+ {host: 'go.mail.ru', param: 'q'},
169
+ {host: 'www.ask.com', param: 'q'},
170
+ {host: 'searches.globososo.com', param: 'q'},
171
+ {host: 'search.tut.by', param: 'query'}]
172
+ ```
173
+ * referrals
174
+
175
+ ```ruby
176
+ [{host: /^(www\.)?t\.co$/, display: 'twitter.com'},
177
+ {host: /^(www\.)?plus\.url\.google\.com$/, display: 'plus.google.com'}]
178
+ ```
179
+ * utm_synonyms
180
+
181
+ ```ruby
182
+ {'utm_source'=>[], 'utm_medium'=>[], 'utm_campaign'=>[], 'utm_content'=>[], 'utm_term'=>[]}
183
+ ```
184
+ * array_params_joiner
185
+
186
+ ```ruby
187
+ ', '
188
+ ```
189
+
190
+ 5. **session_duration** - after this duration left, new session will be created and its sources priorities will be computed without regard to past sessions sources.
191
+ ```ruby
192
+ 3.months
193
+ ```
194
+
195
+ 6. **sources_overwriting_schema** - source's kind priorities for priority source computation.
196
+ ```ruby
197
+ {direct: %w(direct),
198
+ referral: %w(direct referral organic utm),
199
+ organic: %w(direct referral organic utm),
200
+ utm: %w(direct referral organic utm)}
201
+ ```
202
+
203
+ ## Usage
204
+
205
+ ### Sources owner
206
+
207
+ #### referrer_markups
208
+
209
+ Get markups for application user. Returns hash where:
210
+ * **first** - user's first source
211
+ * **priority** - user's last priority source, computed using `sources_overwriting_schema`
212
+ * **last** - user's last source
213
+
214
+ ```ruby
215
+ user.referrer_markups
216
+ => {first: {utm_source: 'google', utm_medium: 'organic', utm_campaign: '(none)', utm_content: '(none)', utm_term: 'user search query', kind: 'organic'},
217
+ priority: {utm_source: 'google', utm_campaign: 'adv_campaign', utm_medium: 'cpc', utm_content: 'adv 1', utm_term: 'some text', kind: 'utm'},
218
+ last: {utm_source: '(direct)', utm_medium: '(none)', utm_campaign: '(none)', utm_content: '(none)', utm_term: '(none)', kind: 'direct'}}
219
+ ```
220
+
221
+ ### Tracked model
222
+
223
+ #### referrer_markup
224
+
225
+ Get markup for tracked model's object. Returns markup hash:
226
+
227
+ ```ruby
228
+ user.referrer_markup
229
+ => {utm_source: 'test.com', utm_campaign: '(none)', utm_medium: 'referral', utm_content: '/',
230
+ utm_term: '(none)', kind: 'referral'}}
231
+ ```
232
+
233
+ #### referrer_link_with(r_user, linked_at: nil) [referrer_link_with!(r_user, linked_at: nil)]
234
+
235
+ Link tracked model's object to referrer's data. Parameters:
236
+
237
+ * `r_user` - `Referrer::User` object, in controller can be got by `referrer_user` (requires Referrer::ControllerAdditions be included).
238
+ * `linked_at` - custom linking `DateTime`.
239
+
240
+ ### Statistics
241
+
242
+ #### Referrer::Statistics.sources_markup(from, to)
243
+
244
+ Get markup of visits occurred in the specified period. Parameters:
245
+
246
+ * `from` - DateTime of period start
247
+ * `to` - DateTime of period end
248
+
249
+ Sample:
250
+
251
+ ```ruby
252
+ Referrer::Statistics.sources_markup(10.days.ago, 2.days.ago)
253
+ => [{utm_source: 'first', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
254
+ utm_term: '(none)', kind: 'utm', count: 2},
255
+ {utm_source: 'second', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
256
+ utm_term: '(none)', kind: 'utm', count: 1},
257
+ {utm_source: '(direct)', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
258
+ utm_term: '(none)', kind: 'direct', count: 1}]
259
+ ```
260
+
261
+ #### Referrer::Statistics.tracked_objects_markup(from, to, type: nil)
262
+
263
+ Get markup associated with tracked model objects, in the specified period. Parameters:
264
+
265
+ * `from` - DateTime of period start
266
+ * `to` - DateTime of period end
267
+ * `type` - tracked model name. If specified, response will contain markup associated only with this model objects.
268
+
269
+ Sample:
270
+
271
+ ```ruby
272
+ Referrer::Statistics.tracked_objects_markup(10.days.ago, 2.days.ago)
273
+ => [{utm_source: 'first', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
274
+ utm_term: '(none)', kind: 'utm', count: 2},
275
+ {utm_source: 'second', utm_campaign: '(none)', utm_medium: '(none)', utm_content: '(none)',
276
+ utm_term: '(none)', kind: 'utm', count: 1}]
277
+ ```
278
+
279
+ ## License
280
+
281
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,26 @@
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 = 'Referrer'
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("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
@@ -0,0 +1,13 @@
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 any plugin's vendor/assets/javascripts directory 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/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,364 @@
1
+ <% urls = Referrer::Engine.routes.url_helpers %>
2
+ (function() {
3
+ 'use strict';
4
+
5
+ window.Referrer = function (params) {
6
+ var _options = {
7
+ cookies: {
8
+ prefix: 'referrer',
9
+ domain: null,
10
+ path: '/'
11
+ },
12
+ user: {
13
+ methods: {create: {url: '<%= urls.users_path %>'}}
14
+ },
15
+ session: {
16
+ methods: {create: {url: '<%= urls.sessions_path %>'}}
17
+ },
18
+ source: {
19
+ methods: {massCreate: {url: '<%= urls.mass_create_sources_path %>'}}
20
+ },
21
+ object: {name: 'referrer'},
22
+ callback: null,
23
+ csrfTokenFunction: function () {<%= Referrer.js_csrf_token %>},
24
+ version: 1.0
25
+ };
26
+
27
+ var _storageNames, _urlSupport, _storageManager, _requestResolver;
28
+
29
+ var UrlSupport = function () {
30
+ this.getHostname = function (url) {
31
+ var tmp = document.createElement('a');
32
+ tmp.href = url;
33
+ return tmp.hostname;
34
+ };
35
+ this.referrerUrl = document.referrer;
36
+ this.referrerHostname = this.referrerUrl ? this.getHostname(this.referrerUrl) : null;
37
+ this.locationUrl = window.location.toString();
38
+ this.locationHostname = this.getHostname(this.locationUrl);
39
+ };
40
+
41
+ var StorageManager = function () {
42
+ var _domain = _options.cookies.domain ||
43
+ (_urlSupport.locationHostname == 'localhost' ? null : _urlSupport.locationHostname);
44
+
45
+ this.setCookie = function (name, value, permanent, seconds) {
46
+ var expires;
47
+ var cookie = name + "=" + value + ';path=' + _options.cookies.path;
48
+ if (_domain) {cookie = cookie + ';domain=' + _domain;}
49
+ if (permanent) {
50
+ expires = new Date();
51
+ expires.setFullYear(expires.getFullYear() + 20);
52
+ } else {
53
+ if (seconds && typeof seconds == 'number') {
54
+ expires = new Date();
55
+ expires.setTime(expires.getTime() + seconds*1000);
56
+ }
57
+ }
58
+ if (expires) {
59
+ cookie = cookie + ';expires=' + expires.toUTCString();
60
+ }
61
+ document.cookie = cookie;
62
+ };
63
+
64
+ this.getCookie = function (name) {
65
+ var pName = name + '=';
66
+ var arr = document.cookie.split(';');
67
+ for(var i=0; i<arr.length; i++) {
68
+ var c = arr[i];
69
+ while (c.charAt(0)==' ') c = c.substring(1);
70
+ if (c.indexOf(pName) == 0) return c.substring(pName.length,c.length);
71
+ }
72
+ return '';
73
+ };
74
+
75
+ this.isLocalStorageSupported = function () {
76
+ try {
77
+ return 'localStorage' in window && window['localStorage'] !== null;
78
+ } catch (e) {
79
+ return false;
80
+ }
81
+ };
82
+
83
+ this.setLocalStorageItem = function (name, value) {
84
+ return localStorage[name] = value;
85
+ };
86
+
87
+ this.getLocalStorageItem = function (name) {
88
+ return localStorage[name];
89
+ };
90
+ };
91
+
92
+ var RequestResolver = function () {
93
+ var isEmpty = function (obj) {
94
+ for (var prop in obj) {
95
+ if (obj.hasOwnProperty(prop))
96
+ return false;
97
+ }
98
+ return true;
99
+ };
100
+
101
+ this.post = function (url, data, callback, self) {
102
+ var formData, csrfToken;
103
+ var xhr = new XMLHttpRequest();
104
+ xhr.open('POST', url, true);
105
+
106
+ formData = new FormData();
107
+ csrfToken = _options.csrfTokenFunction();
108
+ if (csrfToken) {formData.append('authenticity_token', csrfToken);}
109
+
110
+ if (isEmpty(data) && !csrfToken) {
111
+ xhr.send();
112
+ } else {
113
+ for (var key in data) {
114
+ if (data.hasOwnProperty(key)) {formData.append(key, data[key])}
115
+ }
116
+ xhr.send(formData);
117
+ }
118
+
119
+ (function(obj) {
120
+ xhr.onreadystatechange = function() {
121
+ callback.call(this, obj);
122
+ }
123
+ })(self);
124
+ };
125
+ };
126
+
127
+ var UserManager = function () {
128
+ var object;
129
+ var errors = [];
130
+
131
+ this.object = function () {
132
+ var cookie;
133
+ if (!object) {
134
+ cookie = _storageManager.getCookie(_storageNames.user);
135
+ if (cookie) {
136
+ object = JSON.parse(decodeURIComponent(cookie));
137
+ }
138
+ }
139
+ return object;
140
+ };
141
+
142
+ this.createObject = function (callback) {
143
+ _requestResolver.post(_options.user.methods.create.url, {}, function (obj) {
144
+ if (this.readyState != 4) {return}
145
+ if (this.status == 200) {
146
+ object = JSON.parse(this.responseText);
147
+ _storageManager.setCookie(_storageNames.user,
148
+ encodeURIComponent(JSON.stringify({id: object.id, token: object.token})), true);
149
+ callback.call(obj);
150
+ } else {
151
+ errors.push({text: 'create object error', xhr: this});
152
+ }
153
+ }, this);
154
+ };
155
+
156
+ this.errors = function () {return errors;};
157
+ };
158
+
159
+ var SessionManager = function () {
160
+ var object;
161
+ var errors = [];
162
+
163
+ this.object = function () {
164
+ var cookie;
165
+ if (object) return object;
166
+ cookie = _storageManager.getCookie(_storageNames.sessionId);
167
+ return cookie ? {id: cookie} : null;
168
+ };
169
+
170
+ this.createObject = function (user, callback) {
171
+ _requestResolver.post(
172
+ _options.session.methods.create.url,
173
+ {'session[user_id]': user.id, 'session[user_token]': user.token},
174
+ function (obj) {
175
+ var result;
176
+ if (this.readyState != 4) {return;}
177
+ if (this.status == 200) {
178
+ result = JSON.parse(this.responseText);
179
+ if (result.errors) {
180
+ errors.push({text: 'create object error', errors: result.errors})
181
+ } else {
182
+ _storageManager.setCookie(
183
+ _storageNames.sessionId, result.id, false, result.active_seconds);
184
+ object = {id: result.id};
185
+ }
186
+ callback.call(obj);
187
+ } else {
188
+ errors.push({text: 'create object error', xhr: this});
189
+ }
190
+ }, this);
191
+ };
192
+
193
+ this.errors = function () {
194
+ return errors;
195
+ };
196
+ };
197
+
198
+ var SourcesManager = function (queue) {
199
+ var errors;
200
+ this.createObjects = function (user, session, callback) {
201
+ _requestResolver.post(
202
+ _options.source.methods.massCreate.url,
203
+ {'sources[user_id]': user.id, 'sources[user_token]': user.token,
204
+ 'sources[current_session_id]': session.id, 'sources[values]': JSON.stringify(queue.queue())},
205
+ function (obj) {
206
+ var result;
207
+ if (this.readyState != 4) {return;}
208
+ if (this.status == 200) {
209
+ result = JSON.parse(this.responseText);
210
+ if (result.errors) {
211
+ errors.push({text: 'create object error', errors: result.errors})
212
+ } else {
213
+ queue.clear();
214
+ }
215
+ callback.call(obj);
216
+ } else {
217
+ errors.push({text: 'create object error', xhr: this});
218
+ }
219
+ }, this);
220
+ }
221
+ };
222
+
223
+ var SourceQueue = function () {
224
+ var queue, lastId;
225
+
226
+ this.queue = function () {
227
+ if (arguments.length) {
228
+ queue = arguments[0];
229
+ this.save();
230
+ } else {
231
+ if (!queue) {
232
+ var data = _storageManager.getLocalStorageItem(_storageNames.sourceQueue);
233
+ queue = data ? JSON.parse(data) : [];
234
+ }
235
+ }
236
+ return queue;
237
+ };
238
+
239
+ this.save = function () {
240
+ _storageManager.setLocalStorageItem(_storageNames.sourceQueue, JSON.stringify(queue));
241
+ };
242
+
243
+ this.lastId = function () {
244
+ if (arguments.length) {
245
+ lastId = arguments[0];
246
+ _storageManager.setLocalStorageItem(_storageNames.lastSourceId, lastId);
247
+ } else {
248
+ if (!lastId) {
249
+ var cookie = _storageManager.getLocalStorageItem(_storageNames.lastSourceId);
250
+ lastId = cookie ? parseInt(cookie) : 0;
251
+ }
252
+ }
253
+ return lastId;
254
+ };
255
+
256
+ this.push = function (hash) {
257
+ var value = {};
258
+ for (var key in hash) {
259
+ if (hash.hasOwnProperty(key)) {
260
+ value[key] = hash[key];
261
+ }
262
+ }
263
+ var id = this.lastId();
264
+ this.lastId(id + 1);
265
+ value['client_duplicate_id'] = id;
266
+ queue = this.queue();
267
+ queue.push(value);
268
+ this.save();
269
+ return value;
270
+ };
271
+
272
+ this.clear = function () {
273
+ queue = [];
274
+ this.save();
275
+ return queue;
276
+ };
277
+ };
278
+
279
+ var MainManager = function () {
280
+ var _userManager = new UserManager();
281
+ var _sessionManager = new SessionManager();
282
+ var _sourceQueue = new SourceQueue();
283
+ var _sourcesManager = new SourcesManager(_sourceQueue);
284
+ var _rewriteSession = false;
285
+
286
+ var isNewSource = function () {
287
+ return _urlSupport.referrerHostname != _urlSupport.locationHostname;
288
+ };
289
+
290
+ var resolveUserAnd = function (callback) {
291
+ if (_userManager.object()) {
292
+ callback();
293
+ } else {
294
+ _rewriteSession = true;
295
+ _userManager.createObject(callback);
296
+ }
297
+ };
298
+
299
+ var resolveSessionAnd = function (callback) {
300
+ if (!_rewriteSession && _sessionManager.object()) {
301
+ callback();
302
+ } else {
303
+ _sessionManager.createObject(_userManager.object(), callback);
304
+ }
305
+ };
306
+
307
+ var resolveSourceAnd = function (callback) {
308
+ _sourcesManager.createObjects(_userManager.object(), _sessionManager.object(), callback);
309
+ };
310
+
311
+ this.resolve = function () {
312
+ if (isNewSource()) {
313
+ var source = {referrer: _urlSupport.referrerUrl, entry_point: _urlSupport.locationUrl};
314
+ if (_sessionManager.object()) {source['session_id'] = _sessionManager.object().id;}
315
+ _sourceQueue.push(source);
316
+ }
317
+ if (_sourceQueue.queue().length) {
318
+ resolveUserAnd(function(){
319
+ resolveSessionAnd(function () {
320
+ resolveSourceAnd(function () {
321
+ window[_options.object.name]['finished'] = true;
322
+ if (_options.callback) {
323
+ _options.callback();
324
+ }
325
+ });
326
+ });
327
+ });
328
+ }
329
+ };
330
+ };
331
+
332
+ (function() {
333
+ var extendOptions = function (def, opt) {
334
+ var result = def;
335
+ for (var key in opt) {
336
+ if (opt.hasOwnProperty(key)) {
337
+ if (typeof(opt[key]) === 'object' && typeof(result[key]) === 'object' && result[key] !== null) {
338
+ result[key] = extendOptions(result[key], opt[key]);
339
+ } else {
340
+ result[key] = opt[key];
341
+ }
342
+ }
343
+ }
344
+ return result;
345
+ };
346
+
347
+ _options = extendOptions(_options, params);
348
+ _storageNames = {
349
+ user: _options.cookies.prefix + '_user',
350
+ sessionId: _options.cookies.prefix + '_session_id',
351
+ sourceQueue: _options.cookies.prefix + '_sources_queue',
352
+ lastSourceId: _options.cookies.prefix + '_sources_last_id'
353
+ };
354
+ _urlSupport = new UrlSupport();
355
+ _storageManager = new StorageManager();
356
+ _requestResolver = new RequestResolver();
357
+ (new MainManager).resolve();
358
+ })();
359
+ };
360
+
361
+ document.addEventListener('DOMContentLoaded', function () {
362
+ window.referrer = {object: new window.Referrer(<%= Referrer.js_settings.to_json %>), finished: false};
363
+ });
364
+ })();