mixpanel 2.2.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://secure.travis-ci.org/zevarito/mixpanel.png)](http://travis-ci.org/zevarito/mixpanel)
1
+ [![Build Status](https://secure.travis-ci.org/zevarito/mixpanel.png?branch=master)](http://travis-ci.org/zevarito/mixpanel)
2
2
 
3
3
  ## Table of Contents
4
4
 
@@ -7,7 +7,11 @@
7
7
  - [Install] (#install)
8
8
  - [Rack Middleware] (#rack-middleware)
9
9
  - [Usage] (#usage)
10
- - [Initialize Mixpanel class] (#initialize-mixpanel-class)
10
+ - [Initialize Mixpanel] (#initialize-mixpanel)
11
+ - [Track Events Directly](#track-events-directly)
12
+ - [Import Events](#import-events)
13
+ - [Set Person Attributes Directly](#set-person-attributes-directly)
14
+ - [Increment Person Attributes Directly](#increment-person-attributes-directly)
11
15
  - [Examples] (#examples)
12
16
  - [How to use it from Rails controllers] (#how-to-use-it-from-rails-controllers)
13
17
  - [How to track events using Resque and Rails] (#how-to-track-events-using-resque-and-rails)
@@ -22,8 +26,10 @@ http://mixpanel.com
22
26
 
23
27
  ## What does this Gem do?
24
28
 
25
- - Track events with properties directly from your backend.
26
- - Track events with properties through javascript using a rack middleware.
29
+ - Track events with properties directly from your backend
30
+ - Track events with properties through JavaScript using a Rack Middleware
31
+ - Set / increment user attributes directly from your backend
32
+ - Set / increment user attributes through JavaScript using a Rack Middleware
27
33
 
28
34
  ## Install
29
35
 
@@ -33,112 +39,232 @@ http://mixpanel.com
33
39
 
34
40
  ### Rack Middleware
35
41
 
36
- *Only need if you want to track events from Javascript.*
42
+ *Only needed if you want to track events via Javascript.* This setup will allow your backend to have the client browser process the actual
43
+ requests over JavaScript rather than sending the request yourself.
37
44
 
38
45
  If you are using Rails you can add this to your specific environment configuration file (located in config/environments/) or create a new
39
46
  initializer for it:
40
47
 
41
48
  ```ruby
42
- config.middleware.use "Mixpanel::Tracker::Middleware", "YOUR_MIXPANEL_API_TOKEN", options
49
+ config.middleware.use "Mixpanel::Middleware", "YOUR_MIXPANEL_API_TOKEN", options
43
50
  ```
44
51
 
45
- Where **options** is a Hash that accepts the following keys:
52
+ Where **options** is a hash that accepts the following keys:
46
53
 
47
- * **insert_js_last** : true | false
54
+ * **insert_js_last** : boolean
48
55
 
49
- *Default: false*.
50
- By default the scripts are inserted into the head of the html response. If you'd prefer the scripts to run after
51
- all rendering has completed you can set the insert_js_last flag and they'll be added at the end of the body tag.
52
- This will work whether or not you opt for the aynchronous version of the API. However, when inserting js into an
53
- ajax response it will have no effect.
56
+ *Default: false*
54
57
 
55
- * **persist** : true | false
58
+ By default the scripts are inserted into the head of the HTML response. If you'd prefer the scripts to run after
59
+ all rendering has completed, set the insert_js_last flag to true and they'll be added at the end of the body tag.
60
+ This will work whether or not you opt for the aynchronous version of the API. However, this will have no effect
61
+ when inserting JS into an AJAX response.
62
+
63
+ * **persist** : boolean
64
+
65
+ *Default: false*
56
66
 
57
- *Default: false*.
58
67
  If you would like, the Mixpanel gem may be configured to store its queue in a Rack session. This allows events
59
- to be stored through redirects, helpful if you sign in and redirect but want to associate an event with that
60
- action. The mixpanel gem will also remove duplicate events from your queue for information that should only be
61
- trasmitted to the API once, such as `mixpanel.identify`, `mixpanel.name_tag`, `mixpanel.people.set`, and
68
+ to be stored through redirects, which can be helpful if you sign in and redirect but want to associate an event with that
69
+ action. The Mixpanel gem will also remove duplicate events from your queue for information that should only be
70
+ transmitted to the API once, such as `mixpanel.identify`, `mixpanel.name_tag`, `mixpanel.people.set`, and
62
71
  `mixpanel.register`.
63
- This allows you to use a before filter to set these variables, redirect, and still have them only transmitted
72
+
73
+ This allows you to use a before_filter to set these variables, redirect, and still have them only transmitted
64
74
  once.
65
- *To enable persistence*, you must set it in both places, Middleware and when you initialize Mixpanel class.
66
75
 
67
- * **config** : a Hash
76
+ *To enable persistence*, you must set the flag twice: here when instantiating Middleware and again when you initialize
77
+ the Mixpanel class.
78
+
79
+ * **config** : hash
68
80
 
69
- *Default: {}*.
81
+ *Default: {}*
70
82
 
71
- You can also pass Mixpanel configuration details as seen here
72
- (https://mixpanel.com/docs/integration-libraries/javascript-full-api#set_config)
83
+ You can also pass additional [Mixpanel configuration details](https://mixpanel.com/docs/integration-libraries/javascript-full-api#set_config).
73
84
 
74
85
  ## Usage
75
86
 
76
- ### Initialize Mixpanel class
87
+ ### Initialize Mixpanel
77
88
 
78
89
  ```ruby
79
- @mixpanel = Mixpanel::Tracker.new("YOUR_MIXPANEL_API_TOKEN", request.env, options)
90
+ @mixpanel = Mixpanel::Tracker.new YOUR_MIXPANEL_API_TOKEN, options
80
91
  ```
81
- Where **options** is a Hash that accepts the following keys:
92
+ Where **options** is a hash that accepts the following keys:
82
93
 
83
- * **async** : true | false
94
+ * **async** : boolean
84
95
 
85
- *Default: false*.
86
- Built in async feature. Events are sent to a subprocess via a pipe and the sub process which asynchronously send events to Mixpanel.
87
- This process uses a single thread to upload events, and may start dropping events if your application generates
88
- them at a very high rate.
89
- If you like for a more robust async behavior take a look at Resque example.
90
-
91
- * **url** : String
92
-
93
- *Default: http://api.mixpanel.com*.
94
- If you are proxying Mixpanel API requests then you can set a custom url and additionally stop the token from
95
- being sent by marking it as false if you're going to let the proxy add it.
96
- Example: { url: "http://localhost:8000/mixpanelproxy" }.
97
-
98
- * **persist** : true | false
99
-
100
- *Default: false*.
96
+ *Default: false*
97
+
98
+ Built in async feature. Events are sent to a subprocess via a pipe and the sub process asynchronously send events to Mixpanel.
99
+ This value can be overwritten on subsequent method calls. I.e., this setting represents the default for your Mixpanel object,
100
+ but each call can overwrite this default setting.
101
+
102
+ This process uses a single thread to upload events, and may start dropping events if your application generates
103
+ them at a very high rate. While this is a simple way to have asynchronous interaction with Mixpanel, more robust solutions are
104
+ available. Specifically, see the [Resque example](#how-to-track-events-using-resque-and-rails) below.
105
+
106
+ * **persist** : boolean
107
+
108
+ *Default: false*
109
+
110
+ This is used in connection with the [Rack Middleware section](#rack-middleware) above. If you are not going to use Middleware
111
+ to send requests to Mixpanel through JavaScript, you don't need to worry about this option.
112
+
101
113
  If you would like, the Mixpanel gem may be configured to store its queue in a Rack session. This allows events
102
- to be stored through redirects, helpful if you sign in and redirect but want to associate an event with that
103
- action. The mixpanel gem will also remove duplicate events from your queue for information that should only be
104
- trasmitted to the API once, such as `mixpanel.identify`, `mixpanel.name_tag`, `mixpanel.people.set`, and
114
+ to be stored through redirects, which can be helpful if you sign in and redirect but want to associate an event with that
115
+ action. The Mixpanel gem will also remove duplicate events from your queue for information that should only be
116
+ transmitted to the API once, such as `mixpanel.identify`, `mixpanel.name_tag`, `mixpanel.people.set`, and
105
117
  `mixpanel.register`.
106
- This allows you to use a before filter to set these variables, redirect, and still have them only transmitted
118
+
119
+ This allows you to use a before_filter to set these variables, redirect, and still have them only transmitted
107
120
  once.
108
- *To enable persistence*, you must set it in both places, Middleware and when you initialize Mixpanel class.
109
121
 
110
- *To enable import mode* you must set both :import => true and :api_key => YOUR_KEY (not to be confused with the project token.)
111
- You can get more information about import mode here
112
- (https://mixpanel.com/docs/api-documentation/importing-events-older-than-31-days)
122
+ *To enable persistence*, you must set the flag twice: here when instantiating Middleware and again when you initialize
123
+ the Mixpanel class.
124
+
125
+ * **api_key** : string
126
+
127
+ *Default: nil*
128
+
129
+ When using the [import functionality](#import-events), you must set an API key to go along with your token. If not set when the
130
+ class is instantiated, you will be required to send the api key in the options hash of the import method.
131
+
132
+ * **env** : hash
133
+
134
+ *Default: {}*
135
+
136
+ This is used by the gem to append information from your request environment to your Mixpanel request. If you are calling this
137
+ directly from a controller, simply passing in `request.env` will be sufficient. However, as explained in the Resque example,
138
+ your environment might choke if it tries to convert that hash to JSON (not to mention how large that hash can be). You can just pass
139
+ in a subset of the full environment:
140
+
141
+ ```ruby
142
+ env = {
143
+ 'REMOTE_ADDR' => request.env['REMOTE_ADDR'],
144
+ 'HTTP_X_FORWARDED_FOR' => request.env['HTTP_X_FORWARDED_FOR'],
145
+ 'rack.session' => request.env['rack.session'],
146
+ 'mixpanel_events' => request.env['mixpanel_events']
147
+ }
148
+ @mixpanel = Mixpanel::Tracker.new MIXPANEL_TOKEN, { :env => env }
149
+ ```
150
+
151
+ Basically, this information is being used to: set the default IP address associated with the request, and grab any session variables
152
+ needed to run the Middleware stuff.
153
+
154
+ Additional information contained in your environment (e.g., http_referer) can simply be sent in as attributes where appropriate
155
+ for your use case.
156
+
157
+ ### Track Events Directly
158
+
159
+ ```ruby
160
+ @mixpanel.track event_name, properties, options
161
+ ```
162
+
163
+ **event_name** is a string denoting how you want this event to appear in your Mixpanel dashboard.
164
+
165
+ **properties** is a hash of properties to be associated with the event. The keys in the properties can either be strings
166
+ or symbols. If you send in a key that matches a [special property](https://mixpanel.com/docs/properties-or-segments/special-or-reserved-properties),
167
+ it will automatically be converted to the correct form (e.g., `{ :os => 'Mac' }` will be converted to `{ :$os => 'Mac' }`).
168
+
169
+ **options** is a hash that accepts the following keys:
170
+
171
+ * **async** : boolean
172
+
173
+ *Default: the async value from when the class was instantiated*
174
+
175
+ * **api_key**: string
176
+
177
+ *Default: the api_key value from when the class was instantiated*
178
+
179
+ * **url**: string
180
+
181
+ *Default: `http://api.mixpanel.com/track/`*
182
+
183
+ This can be used to proxy Mixpanel API requests.
184
+
185
+ Example:
186
+
187
+ ```ruby
188
+ @mixpanel.track 'Purchased credits', { :number => 5, 'First Time Buyer' => true }
189
+ ```
190
+
191
+ ### Import Events
192
+
193
+ ```ruby
194
+ @mixpanel.import event_name, properties, options
195
+ ```
196
+
197
+ All of these options have the same meaning and same defaults as the [track method](#track-events-directly), except that the
198
+ default url is `http://api.mixpanel.com/import/`
199
+
200
+ Example:
201
+
202
+ ```ruby
203
+ @mixpanel.import 'Purchased credits', { :number => 4, :time => 5.weeks.ago }, { :api_key => MY_API_KEY}
204
+ ```
205
+
206
+ ### Set Person Attributes Directly
207
+
208
+ ```ruby
209
+ @mixpanel.set distinct_id, properties, options
210
+ ```
211
+
212
+ **distinct_id** is whatever is used to identify the user to Mixpanel.
213
+
214
+ **properties** is a hash of properties to be set. The keys in the properties can either be strings
215
+ or symbols. If you send in a key that matches a [special property](https://mixpanel.com/docs/people-analytics/special-properties),
216
+ it will automatically be converted to the correct form (e.g., `{ :first_name => 'Chris' }` will be converted to `{ :$first_name => 'Chris' }`).
217
+
218
+ **options** is a hash that accepts the following keys:
219
+
220
+ * **async**: boolean
113
221
 
114
- ### Track events directly.
222
+ *Default: the async value from when the class was instantiated*
223
+
224
+ * **url**: string
225
+
226
+ *Default: `http://api.mixpanel.com/engage/`*
227
+
228
+ This can be used to proxy Mixpanel API requests
229
+
230
+ Example:
231
+
232
+ ```ruby
233
+ @mixpanel.set 'john-doe', { :age => 31, :email => 'john@doe.com' }
234
+ ```
235
+
236
+ ### Increment Person Attributes Directly
115
237
 
116
238
  ```ruby
117
- @mixpanel.track_event("Sign in", {:some => "property"})
239
+ @mixpanel.increment distinct_id, properties, options
118
240
  ```
119
241
 
120
- ### Interface with People management directly
242
+ All of these options have the same meaning and same defaults as the [set method](#set-person-attributes-directly). Note that according to Mixpanel's
243
+ docs, you cannot combine set and increment requests, and that is why they are split here.
244
+
245
+ Example:
121
246
 
122
247
  ```ruby
123
- @mixpanel.engage_set(@user.id, {:username => @user.username, :email => @user.email})
124
- @mixpanel.engage_add(@user.id, {:monkeys_punched => 12})
248
+ @mixpanel.increment 'john-doe', { :tokens => 5, :coins => -4 }
125
249
  ```
126
250
 
127
- ### Append events to be tracked with Javascript.
251
+ ### Append Events To Be Tracked With Javascript
128
252
 
129
253
  *Note*: You should setup the [Rack Middleware](#rack-middleware).
130
254
 
131
255
  ```ruby
132
- @mixpanel.append_event("Sign in", {:some => "property"})
256
+ @mixpanel.append_track event_name, properties
133
257
  ```
134
258
 
259
+ **event_name** and **properties** take the same form as [tracking the event directly](#track-events-directly).
260
+
135
261
  ### Execute Javascript API call
136
262
 
137
263
  *Note*: You should setup the [Rack Middleware](#rack-middleware).
138
264
 
139
265
  ```ruby
140
- @mixpanel.append_api("register", {:some => "property"})
141
- @mixpanel.append_api("identify", "Unique Identifier")
266
+ @mixpanel.append("register", {:some => "property"})
267
+ @mixpanel.append("identify", "Unique Identifier")
142
268
  ```
143
269
 
144
270
  ### Prevent middleware from inserting code
@@ -159,49 +285,64 @@ $.ajax("/path/to/api/endpoint", {
159
285
 
160
286
  ### How to use it from Rails controllers?
161
287
 
162
- In your ApplicationController class add a method to instantiate mixpanel.
288
+ In your ApplicationController class add a method to keep track of a Mixpanel instance.
163
289
 
164
290
  ```ruby
165
- before_filter :initialize_mixpanel
291
+ protected
292
+ def mixpanel
293
+ @mixpanel ||= Mixpanel::Tracker.new YOUR_MIXPANEL_API_TOKEN, { :env => request.env }
294
+ end
295
+ ```
166
296
 
167
- def initialize_mixpanel
168
- @mixpanel = Mixpanel::Tracker.new("YOUR_MIXPANEL_API_TOKEN", request.env, options)
169
- end
297
+ Then you can call against this method where it makes sense in your controller. For example, in the users#create method:
298
+
299
+ ```ruby
300
+ def create
301
+ @user = User.create( :name => 'Jane Doe', :gender => 'female', :mixpanel_identifer => 'asdf' )
302
+ mixpanel.track 'User Created', { :gender => @user.gender, :distinct_id => @user.mixpanel_identifier, :time => @user.created_at } # Note that passing the time key overwrites the default value of Time.now
303
+ mixpanel.set @user.mixpanel_identifer, { :gender => @user.gender, :created => @user.created_at, :name => @user.name }
304
+ end
170
305
  ```
171
- ## How to track events using Resque and Rails
172
306
 
173
- If you don't want to use the built in Mixpanel Gem async feature bellow there is an example about how to make
174
- async calls using Resque.
307
+ ## How to track events using Resque and Rails
175
308
 
176
- [Resque is a Redis-backed Ruby library for creating background jobs](https://github.com/defunkt/resque)
309
+ While there is built-in async functionality, other options are more robust (e.g., using a dedicated queue manager). Below is an example of how this
310
+ might be done with [Resque](https://github.com/defunkt/resque), but the same concepts would apply no matter what queue manager you use.
177
311
 
178
312
  ```ruby
179
- class MixpanelTrackEventJob
180
- @queue = :slow
313
+ class MixpanelTrackEventJob
314
+ @queue = :slow
181
315
 
182
- def mixpanel(request_env)
183
- Mixpanel::Tracker.new(MIXPANEL_TOKEN, request_env)
184
- end
316
+ def self.mixpanel env
317
+ Mixpanel::Tracker.new MIXPANEL_TOKEN, { :env => env }
318
+ end
185
319
 
186
- def perform(name, params, request_env)
187
- mixpanel(request_env).track_event(name, params)
188
- end
189
- end
320
+ def self.perform name, properties, env
321
+ mixpanel(env).track name, params
322
+ end
323
+ end
190
324
  ```
191
325
 
192
326
  ```ruby
193
- class UsersController < ApplicationController
194
- def create
195
- @user = User.new(params[:user])
196
-
197
- if @user.save
198
- MixpanelTrackEventJob.enqueue("Sign up", {:invited => params[:invited]}, request.env)
199
- redirect_to user_root_path
200
- else
201
- render :new
202
- end
203
- end
204
- end
327
+ class UsersController < ApplicationController
328
+ def create
329
+ @user = User.new(params[:user])
330
+
331
+ if @user.save
332
+ env = {
333
+ 'REMOTE_ADDR' => request.env['REMOTE_ADDR'],
334
+ 'HTTP_X_FORWARDED_FOR' => request.env['HTTP_X_FORWARDED_FOR'],
335
+ 'rack.session' => request.env['rack.session'],
336
+ 'mixpanel_events' => request.env['mixpanel_events']
337
+ } # Trying to pass request.env to Resque is going to fail (it chokes when trying to conver it to JSON, but no worries...)
338
+
339
+ Resque.enqueue MixpanelTrackEventJob, 'Sign up', { :invited => params[:invited] }, env
340
+ redirect_to user_root_path
341
+ else
342
+ render :new
343
+ end
344
+ end
345
+ end
205
346
  ```
206
347
 
207
348
  ## Supported Ruby Platforms
@@ -211,20 +352,6 @@ async calls using Resque.
211
352
  - 1.9.3
212
353
  - JRuby 1.8 Mode
213
354
 
214
- ## Deprecation Notes
215
-
216
- This way to initialize Mixpanel gem is not longer allowed.
217
-
218
- ```ruby
219
- Mixpanel.new
220
- ```
221
-
222
- Use this instead:
223
-
224
- ```ruby
225
- Mixpanel::Tracker.new
226
- ```
227
-
228
355
  ## Collaborators and Maintainers
229
356
 
230
357
  * [Alvaro Gil](https://github.com/zevarito) (Author)
@@ -240,3 +367,4 @@ Use this instead:
240
367
  * [Travis Pew](https://github.com/travisp)
241
368
  * [Sylvain Niles](https://github.com/sylvainsf)
242
369
  * [GBH](https://github.com/GBH)
370
+ * [Goalee](https://github.com/Goalee)
@@ -0,0 +1,31 @@
1
+ module Mixpanel::Async
2
+ WORKER_MUTEX = Mutex.new
3
+
4
+ def worker
5
+ WORKER_MUTEX.synchronize do
6
+ @worker || (@worker = IO.popen(self.cmd, 'w'))
7
+ end
8
+ end
9
+
10
+ def dispose_worker(w)
11
+ WORKER_MUTEX.synchronize do
12
+ if(@worker == w)
13
+ @worker = nil
14
+ w.close
15
+ end
16
+ end
17
+ end
18
+
19
+ protected
20
+
21
+ def cmd
22
+ @cmd || begin
23
+ require 'escape'
24
+ require 'rbconfig'
25
+ interpreter = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"]
26
+ subprocess = File.join(File.dirname(__FILE__), 'subprocess.rb')
27
+ @cmd = Escape.shell_command([interpreter, subprocess])
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,41 @@
1
+ module Mixpanel::Event
2
+ EVENT_PROPERTIES = %w{initial_referrer initial_referring_domain search_engine os browser referrer referring_domain}
3
+ TRACK_URL = 'http://api.mixpanel.com/track/'
4
+ IMPORT_URL = 'http://api.mixpanel.com/import/'
5
+
6
+ def track(event, properties={}, options={})
7
+ track_event event, properties, options, TRACK_URL
8
+ end
9
+
10
+ def import(event, properties={}, options={})
11
+ track_event event, properties, options, IMPORT_URL
12
+ end
13
+
14
+ def append_track(event, properties={})
15
+ append 'track', event, track_properties(properties, false)
16
+ end
17
+
18
+ protected
19
+
20
+ def track_event(event, properties, options, default_url)
21
+ options.reverse_merge! :url => default_url, :async => @async, :api_key => @api_key
22
+ data = build_event event, track_properties(properties)
23
+ url = "#{options[:url]}?data=#{encoded_data(data)}"
24
+ url += "&api_key=#{options[:api_key]}" if options[:api_key].present?
25
+ parse_response request(url, options[:async])
26
+ end
27
+
28
+ def ip
29
+ (@env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_ADDR'] || '').split(',').last
30
+ end
31
+
32
+ def track_properties(properties, include_token=true)
33
+ properties.reverse_merge! :time => Time.now, :ip => ip
34
+ properties.reverse_merge! :token => @token if include_token
35
+ properties_hash properties, EVENT_PROPERTIES
36
+ end
37
+
38
+ def build_event(event, properties)
39
+ { :event => event, :properties => properties_hash(properties, EVENT_PROPERTIES) }
40
+ end
41
+ end
@@ -0,0 +1,142 @@
1
+ require 'rack'
2
+ require 'json'
3
+
4
+ module Mixpanel
5
+ class Middleware
6
+ def initialize(app, mixpanel_token, options={})
7
+ @app = app
8
+ @token = mixpanel_token
9
+ @options = {
10
+ :insert_js_last => false,
11
+ :persist => false,
12
+ :config => {}
13
+ }.merge(options)
14
+ end
15
+
16
+ def call(env)
17
+ @env = env
18
+
19
+ @status, @headers, @response = @app.call(env)
20
+
21
+ if is_trackable_response?
22
+ merge_queue! if @options[:persist]
23
+ update_response!
24
+ update_content_length!
25
+ delete_event_queue!
26
+ end
27
+
28
+ [@status, @headers, @response]
29
+ end
30
+
31
+ private
32
+
33
+ def update_response!
34
+ @response.each do |part|
35
+ if is_regular_request? && is_html_response?
36
+ insert_at = part.index(@options[:insert_js_last] ? '</body' : '</head')
37
+ unless insert_at.nil?
38
+ part.insert(insert_at, render_event_tracking_scripts) unless queue.empty?
39
+ part.insert(insert_at, render_mixpanel_scripts) #This will insert the mixpanel initialization code before the queue of tracking events.
40
+ end
41
+ elsif is_ajax_request? && is_html_response?
42
+ part.insert(0, render_event_tracking_scripts) unless queue.empty?
43
+ elsif is_ajax_request? && is_javascript_response?
44
+ part.insert(0, render_event_tracking_scripts(false)) unless queue.empty?
45
+ end
46
+ end
47
+ end
48
+
49
+ def update_content_length!
50
+ new_size = 0
51
+ @response.each{|part| new_size += part.bytesize}
52
+ @headers.merge!("Content-Length" => new_size.to_s)
53
+ end
54
+
55
+ def is_regular_request?
56
+ !is_ajax_request?
57
+ end
58
+
59
+ def is_ajax_request?
60
+ @env.has_key?("HTTP_X_REQUESTED_WITH") && @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest"
61
+ end
62
+
63
+ def is_html_response?
64
+ @headers["Content-Type"].include?("text/html") if @headers.has_key?("Content-Type")
65
+ end
66
+
67
+ def is_javascript_response?
68
+ @headers["Content-Type"].include?("text/javascript") if @headers.has_key?("Content-Type")
69
+ end
70
+
71
+ def is_trackable_response?
72
+ return false if @status == 302
73
+ return false if @env.has_key?("HTTP_SKIP_MIXPANEL_MIDDLEWARE")
74
+ is_html_response? || is_javascript_response?
75
+ end
76
+
77
+ def render_mixpanel_scripts
78
+ <<-EOT
79
+ <!-- start Mixpanel -->
80
+ <script type="text/javascript">
81
+ (function(c,a){window.mixpanel=a;var b,d,h,e;b=c.createElement("script");
82
+ b.type="text/javascript";b.async=!0;b.src=("https:"===c.location.protocol?"https:":"http:")+
83
+ '//cdn.mxpnl.com/libs/mixpanel-2.1.min.js';d=c.getElementsByTagName("script")[0];
84
+ d.parentNode.insertBefore(b,d);a._i=[];a.init=function(b,c,f){function d(a,b){
85
+ var c=b.split(".");2==c.length&&(a=a[c[0]],b=c[1]);a[b]=function(){a.push([b].concat(
86
+ Array.prototype.slice.call(arguments,0)))}}var g=a;"undefined"!==typeof f?g=a[f]=[]:
87
+ f="mixpanel";g.people=g.people||[];h=['disable','track','track_pageview','track_links',
88
+ 'track_forms','register','register_once','unregister','identify','name_tag',
89
+ 'set_config','people.identify','people.set','people.increment'];for(e=0;e<h.length;e++)d(g,h[e]);
90
+ a._i.push([b,c,f])};a.__SV=1.1;})(document,window.mixpanel||[]);
91
+
92
+ mixpanel.init("#{@token}");
93
+ mixpanel.set_config(#{@options[:config].to_json});
94
+ </script>
95
+ <!-- end Mixpanel -->
96
+ EOT
97
+ end
98
+
99
+ def delete_event_queue!
100
+ if @options[:persist]
101
+ (@env['rack.session']).delete('mixpanel_events')
102
+ else
103
+ @env.delete('mixpanel_events')
104
+ end
105
+ end
106
+
107
+ def queue
108
+ if @options[:persist]
109
+ return [] if !(@env['rack.session']).has_key?('mixpanel_events') || @env['rack.session']['mixpanel_events'].empty?
110
+ @env['rack.session']['mixpanel_events']
111
+ else
112
+ return [] if !@env.has_key?('mixpanel_events') || @env['mixpanel_events'].empty?
113
+ @env['mixpanel_events']
114
+ end
115
+ end
116
+
117
+ def merge_queue!
118
+ present_hash = {}
119
+ special_events = ['identify', 'name_tag', 'people.set', 'register']
120
+ queue.uniq!
121
+
122
+ queue.reverse_each do |item|
123
+ is_special = special_events.include?(item[0])
124
+ if present_hash[item[0]] and is_special
125
+ queue.delete(item)
126
+ else
127
+ present_hash[item[0]] = true if is_special
128
+ end
129
+ end
130
+ end
131
+
132
+ def render_event_tracking_scripts(include_script_tag=true)
133
+ return "" if queue.empty?
134
+
135
+ output = queue.map {|type, arguments| %(mixpanel.#{type}(#{arguments.join(', ')});) }.join("\n")
136
+ output = "try {#{output}} catch(err) {}"
137
+
138
+ include_script_tag ? "<script type='text/javascript'>#{output}</script>" : output
139
+ end
140
+ end
141
+ end
142
+