referrer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +281 -0
- data/Rakefile +26 -0
- data/app/assets/javascripts/referrer/application.js +13 -0
- data/app/assets/javascripts/referrer/referrer.js.erb +364 -0
- data/app/controllers/referrer/application_controller.rb +5 -0
- data/app/controllers/referrer/sessions_controller.rb +22 -0
- data/app/controllers/referrer/sources_controller.rb +31 -0
- data/app/controllers/referrer/users_controller.rb +10 -0
- data/app/helpers/referrer/application_helper.rb +4 -0
- data/app/models/referrer/session.rb +27 -0
- data/app/models/referrer/source.rb +46 -0
- data/app/models/referrer/sources_tracked_object.rb +17 -0
- data/app/models/referrer/user.rb +32 -0
- data/app/models/referrer/users_main_app_user.rb +6 -0
- data/app/views/layouts/referrer/application.html.erb +14 -0
- data/config/routes.rb +9 -0
- data/db/migrate/20160620130847_create_referrer_init_structure.rb +58 -0
- data/lib/concerns/controllers/controller_additions.rb +25 -0
- data/lib/concerns/models/owner_model_additions.rb +35 -0
- data/lib/concerns/models/tracked_model_additions.rb +21 -0
- data/lib/modules/statistics.rb +25 -0
- data/lib/other/markup_generator.rb +112 -0
- data/lib/referrer.rb +25 -0
- data/lib/referrer/engine.rb +5 -0
- data/lib/referrer/version.rb +3 -0
- data/lib/tasks/referrer_tasks.rake +4 -0
- metadata +93 -0
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
|
+
})();
|