nano-pure-pkg 0.0.1

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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/ahoy_matey-5.5.0/CHANGELOG.md +403 -0
  3. data/ahoy_matey-5.5.0/CONTRIBUTING.md +42 -0
  4. data/ahoy_matey-5.5.0/LICENSE.txt +22 -0
  5. data/ahoy_matey-5.5.0/README.md +802 -0
  6. data/ahoy_matey-5.5.0/app/controllers/ahoy/base_controller.rb +44 -0
  7. data/ahoy_matey-5.5.0/app/controllers/ahoy/events_controller.rb +51 -0
  8. data/ahoy_matey-5.5.0/app/controllers/ahoy/visits_controller.rb +15 -0
  9. data/ahoy_matey-5.5.0/config/routes.rb +10 -0
  10. data/ahoy_matey-5.5.0/lib/ahoy/base_store.rb +104 -0
  11. data/ahoy_matey-5.5.0/lib/ahoy/controller.rb +56 -0
  12. data/ahoy_matey-5.5.0/lib/ahoy/database_store.rb +96 -0
  13. data/ahoy_matey-5.5.0/lib/ahoy/engine.rb +37 -0
  14. data/ahoy_matey-5.5.0/lib/ahoy/geocode_v2_job.rb +31 -0
  15. data/ahoy_matey-5.5.0/lib/ahoy/helper.rb +40 -0
  16. data/ahoy_matey-5.5.0/lib/ahoy/model.rb +15 -0
  17. data/ahoy_matey-5.5.0/lib/ahoy/query_methods.rb +88 -0
  18. data/ahoy_matey-5.5.0/lib/ahoy/tracker.rb +287 -0
  19. data/ahoy_matey-5.5.0/lib/ahoy/utils.rb +7 -0
  20. data/ahoy_matey-5.5.0/lib/ahoy/version.rb +3 -0
  21. data/ahoy_matey-5.5.0/lib/ahoy/visit_properties.rb +122 -0
  22. data/ahoy_matey-5.5.0/lib/ahoy/warden.rb +5 -0
  23. data/ahoy_matey-5.5.0/lib/ahoy.rb +167 -0
  24. data/ahoy_matey-5.5.0/lib/ahoy_matey.rb +1 -0
  25. data/ahoy_matey-5.5.0/lib/generators/ahoy/activerecord_generator.rb +59 -0
  26. data/ahoy_matey-5.5.0/lib/generators/ahoy/base_generator.rb +13 -0
  27. data/ahoy_matey-5.5.0/lib/generators/ahoy/install_generator.rb +44 -0
  28. data/ahoy_matey-5.5.0/lib/generators/ahoy/mongoid_generator.rb +16 -0
  29. data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/active_record_event_model.rb.tt +10 -0
  30. data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/active_record_migration.rb.tt +62 -0
  31. data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/active_record_visit_model.rb.tt +6 -0
  32. data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/base_store_initializer.rb.tt +25 -0
  33. data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/database_store_initializer.rb.tt +10 -0
  34. data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/mongoid_event_model.rb.tt +14 -0
  35. data/ahoy_matey-5.5.0/lib/generators/ahoy/templates/mongoid_visit_model.rb.tt +50 -0
  36. data/ahoy_matey-5.5.0/vendor/assets/javascripts/ahoy.js +544 -0
  37. data/nano-pure-pkg.gemspec +12 -0
  38. metadata +77 -0
@@ -0,0 +1,44 @@
1
+ module Ahoy
2
+ class BaseController < ApplicationController
3
+ filters = _process_action_callbacks.map(&:filter) - Ahoy.preserve_callbacks
4
+ skip_before_action(*filters, raise: false)
5
+ skip_after_action(*filters, raise: false)
6
+ skip_around_action(*filters, raise: false)
7
+
8
+ if respond_to?(:protect_from_forgery)
9
+ protect_from_forgery with: :null_session, if: -> { Ahoy.protect_from_forgery }
10
+ end
11
+
12
+ before_action :verify_request_size
13
+ before_action :check_params
14
+ before_action :renew_cookies
15
+
16
+ protected
17
+
18
+ def ahoy
19
+ @ahoy ||= Ahoy::Tracker.new(controller: self, api: true)
20
+ end
21
+
22
+ def check_params
23
+ if ahoy.send(:missing_params?)
24
+ logger.info "[ahoy] Missing required parameters"
25
+ render plain: "Missing required parameters\n", status: :bad_request
26
+ end
27
+ end
28
+
29
+ # set proper ttl if cookie generated from JavaScript
30
+ # approach is not perfect, as user must reload the page
31
+ # for new cookie settings to take effect
32
+ def renew_cookies
33
+ set_ahoy_cookies if params[:js] && !Ahoy.api_only
34
+ end
35
+
36
+ def verify_request_size
37
+ if request.content_length > Ahoy.max_content_length
38
+ logger.info "[ahoy] Payload too large"
39
+ status = Rack::RELEASE.to_f >= 3.1 ? :content_too_large : :payload_too_large
40
+ render plain: "Payload too large\n", status: status
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,51 @@
1
+ module Ahoy
2
+ class EventsController < Ahoy::BaseController
3
+ def create
4
+ events =
5
+ if params[:name]
6
+ # legacy API and AMP
7
+ [request.params]
8
+ elsif params[:events]
9
+ request.params[:events]
10
+ else
11
+ data =
12
+ if params[:events_json]
13
+ request.params[:events_json]
14
+ else
15
+ request.body.read
16
+ end
17
+ begin
18
+ ActiveSupport::JSON.decode(data)
19
+ rescue ActiveSupport::JSON.parse_error
20
+ # TODO change to nil in Ahoy 6
21
+ []
22
+ end
23
+ end
24
+
25
+ max_events_per_request = Ahoy.max_events_per_request
26
+
27
+ # check before creating any events
28
+ unless events.is_a?(Array) && events.first(max_events_per_request).all? { |v| v.is_a?(Hash) }
29
+ logger.info "[ahoy] Invalid parameters"
30
+ # :unprocessable_entity is probably more correct
31
+ # but keep consistent with missing parameters for now
32
+ render plain: "Invalid parameters\n", status: :bad_request
33
+ return
34
+ end
35
+
36
+ events.first(max_events_per_request).each do |event|
37
+ time = Time.zone.parse(event["time"]) rescue nil
38
+
39
+ # timestamp is deprecated
40
+ time ||= Time.zone.at(event["time"].to_f) rescue nil
41
+
42
+ options = {
43
+ id: event["id"],
44
+ time: time
45
+ }
46
+ ahoy.track event["name"], event["properties"], options
47
+ end
48
+ render json: {}
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,15 @@
1
+ module Ahoy
2
+ class VisitsController < BaseController
3
+ def create
4
+ ahoy.track_visit
5
+
6
+ render json: {
7
+ visit_token: ahoy.visit_token,
8
+ visitor_token: ahoy.visitor_token,
9
+ # legacy
10
+ visit_id: ahoy.visit_token,
11
+ visitor_id: ahoy.visitor_token
12
+ }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ Rails.application.routes.draw do
2
+ mount Ahoy::Engine => "/ahoy" if Ahoy.api
3
+ end
4
+
5
+ Ahoy::Engine.routes.draw do
6
+ scope module: "ahoy" do
7
+ resources :visits, only: [:create]
8
+ resources :events, only: [:create]
9
+ end
10
+ end
@@ -0,0 +1,104 @@
1
+ module Ahoy
2
+ class BaseStore
3
+ attr_writer :user
4
+
5
+ def initialize(options)
6
+ @user = options[:user]
7
+ @options = options
8
+ end
9
+
10
+ def track_visit(data)
11
+ end
12
+
13
+ def track_event(data)
14
+ end
15
+
16
+ def geocode(data)
17
+ end
18
+
19
+ def authenticate(data)
20
+ end
21
+
22
+ def visit
23
+ end
24
+
25
+ def user
26
+ @user ||= begin
27
+ if Ahoy.user_method.respond_to?(:call)
28
+ if Ahoy.user_method.arity == 1
29
+ Ahoy.user_method.call(controller)
30
+ else
31
+ Ahoy.user_method.call(controller, request)
32
+ end
33
+ else
34
+ controller.send(Ahoy.user_method) if controller.respond_to?(Ahoy.user_method, true)
35
+ end
36
+ end
37
+ end
38
+
39
+ def exclude?
40
+ (!Ahoy.track_bots && bot?) ||
41
+ exclude_by_method? ||
42
+ (defined?(Rails::HealthController) && controller.is_a?(Rails::HealthController))
43
+ end
44
+
45
+ def generate_id
46
+ Ahoy.token_generator.call
47
+ end
48
+
49
+ def visit_or_create
50
+ visit
51
+ end
52
+
53
+ protected
54
+
55
+ def bot?
56
+ unless defined?(@bot)
57
+ @bot = begin
58
+ if request
59
+ if Ahoy.user_agent_parser == :device_detector
60
+ detector = DeviceDetector.new(request.user_agent)
61
+ if Ahoy.bot_detection_version == 2
62
+ detector.bot? || (detector.device_type.nil? && detector.os_name.nil?)
63
+ else
64
+ detector.bot?
65
+ end
66
+ else
67
+ # no need to throw friendly error if browser isn't defined
68
+ # since will error in visit_properties
69
+ Browser.new(request.user_agent).bot?
70
+ end
71
+ else
72
+ false
73
+ end
74
+ end
75
+ end
76
+
77
+ @bot
78
+ end
79
+
80
+ def exclude_by_method?
81
+ if Ahoy.exclude_method
82
+ if Ahoy.exclude_method.arity == 1
83
+ Ahoy.exclude_method.call(controller)
84
+ else
85
+ Ahoy.exclude_method.call(controller, request)
86
+ end
87
+ else
88
+ false
89
+ end
90
+ end
91
+
92
+ def request
93
+ @request ||= @options[:request] || controller.try(:request)
94
+ end
95
+
96
+ def controller
97
+ @controller ||= @options[:controller]
98
+ end
99
+
100
+ def ahoy
101
+ @ahoy ||= @options[:ahoy]
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,56 @@
1
+ module Ahoy
2
+ module Controller
3
+ def self.included(base)
4
+ if base.respond_to?(:helper_method)
5
+ base.helper_method :current_visit
6
+ base.helper_method :ahoy
7
+ end
8
+ base.before_action :track_ahoy_visit, unless: -> { Ahoy.api_only }
9
+ base.around_action :set_ahoy_request_store, unless: -> { Ahoy.api_only }
10
+ end
11
+
12
+ def ahoy
13
+ @ahoy ||= Ahoy::Tracker.new(controller: self)
14
+ end
15
+
16
+ def current_visit
17
+ ahoy.visit
18
+ end
19
+
20
+ def set_ahoy_cookies
21
+ if Ahoy.cookies?
22
+ ahoy.set_visitor_cookie
23
+ ahoy.set_visit_cookie
24
+ else
25
+ # delete cookies if exist
26
+ ahoy.reset
27
+ end
28
+ end
29
+
30
+ def track_ahoy_visit
31
+ defer = Ahoy.server_side_visits != true
32
+
33
+ if defer && !Ahoy.cookies?
34
+ # avoid calling new_visit?, which triggers a database call
35
+ elsif !Ahoy.cookies? && ahoy.exclude?
36
+ # avoid calling new_visit?, which triggers a database call
37
+ # may or may not be a new visit
38
+ Ahoy.log("Request excluded")
39
+ elsif ahoy.new_visit?
40
+ ahoy.track_visit(defer: defer)
41
+ end
42
+
43
+ set_ahoy_cookies
44
+ end
45
+
46
+ def set_ahoy_request_store
47
+ previous_value = Ahoy.instance
48
+ begin
49
+ Ahoy.instance = ahoy
50
+ yield
51
+ ensure
52
+ Ahoy.instance = previous_value
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,96 @@
1
+ module Ahoy
2
+ class DatabaseStore < BaseStore
3
+ def track_visit(data)
4
+ @visit = visit_model.create!(slice_data(visit_model, data))
5
+ rescue => e
6
+ raise e unless unique_exception?(e)
7
+
8
+ # so next call to visit will try to fetch from DB
9
+ if defined?(@visit)
10
+ remove_instance_variable(:@visit)
11
+ end
12
+ end
13
+
14
+ def track_event(data)
15
+ visit = visit_or_create(started_at: data[:time])
16
+ if visit
17
+ event = event_model.new(slice_data(event_model, data))
18
+ event.visit = visit
19
+ event.time = visit.started_at if event.time < visit.started_at
20
+ begin
21
+ event.save!
22
+ rescue => e
23
+ raise e unless unique_exception?(e)
24
+ end
25
+ else
26
+ Ahoy.log "Event excluded since visit not created: #{data[:visit_token]}"
27
+ end
28
+ end
29
+
30
+ def geocode(data)
31
+ visit_token = data.delete(:visit_token)
32
+ data = slice_data(visit_model, data)
33
+ if defined?(Mongoid::Document) && visit_model < Mongoid::Document
34
+ # upsert since visit might not be found due to eventual consistency
35
+ visit_model.where(visit_token: visit_token).find_one_and_update({"$set": data}, {upsert: true})
36
+ elsif visit
37
+ visit.update!(data)
38
+ else
39
+ Ahoy.log "Visit for geocode not found: #{visit_token}"
40
+ end
41
+ end
42
+
43
+ def authenticate(_)
44
+ if visit && visit.respond_to?(:user) && !visit.user
45
+ begin
46
+ visit.user = user
47
+ visit.save!
48
+ rescue ActiveRecord::AssociationTypeMismatch
49
+ # do nothing
50
+ end
51
+ end
52
+ end
53
+
54
+ def visit
55
+ unless defined?(@visit)
56
+ if ahoy.send(:existing_visit_token) || ahoy.instance_variable_get(:@visit_token)
57
+ # find_by raises error by default with Mongoid when not found
58
+ @visit = visit_model.where(visit_token: ahoy.visit_token).take if ahoy.visit_token
59
+ elsif !Ahoy.cookies? && ahoy.visitor_token
60
+ @visit = visit_model.where(visitor_token: ahoy.visitor_token).where(started_at: Ahoy.visit_duration.ago..).order(started_at: :desc).first
61
+ else
62
+ @visit = nil
63
+ end
64
+ end
65
+ @visit
66
+ end
67
+
68
+ # if we don't have a visit, let's try to create one first
69
+ def visit_or_create(started_at: nil)
70
+ ahoy.track_visit(started_at: started_at) if !visit && Ahoy.server_side_visits
71
+ visit
72
+ end
73
+
74
+ protected
75
+
76
+ def visit_model
77
+ ::Ahoy::Visit
78
+ end
79
+
80
+ def event_model
81
+ ::Ahoy::Event
82
+ end
83
+
84
+ def slice_data(model, data)
85
+ column_names = model.try(:column_names) || model.attribute_names
86
+ data.slice(*column_names.map(&:to_sym)).select { |_, v| !v.nil? }
87
+ end
88
+
89
+ def unique_exception?(e)
90
+ return true if defined?(ActiveRecord::RecordNotUnique) && e.is_a?(ActiveRecord::RecordNotUnique)
91
+ return true if defined?(PG::UniqueViolation) && e.is_a?(PG::UniqueViolation)
92
+ return true if defined?(Mongo::Error::OperationFailure) && e.is_a?(Mongo::Error::OperationFailure) && e.message.include?("duplicate key error")
93
+ false
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,37 @@
1
+ module Ahoy
2
+ class Engine < ::Rails::Engine
3
+ initializer "ahoy", after: "sprockets.environment" do
4
+ Ahoy.logger ||= Rails.logger
5
+
6
+ # allow Devise to be loaded after Ahoy
7
+ require "ahoy/warden" if defined?(Warden)
8
+
9
+ next unless Ahoy.quiet
10
+
11
+ # Parse PATH_INFO by assets prefix
12
+ AHOY_PREFIX = "/ahoy/".freeze
13
+
14
+ # Just create an alias for call in middleware
15
+ Rails::Rack::Logger.class_eval do
16
+ def call_with_quiet_ahoy(env)
17
+ if env["PATH_INFO"].start_with?(AHOY_PREFIX) && logger.respond_to?(:silence)
18
+ logger.silence do
19
+ call_without_quiet_ahoy(env)
20
+ end
21
+ else
22
+ call_without_quiet_ahoy(env)
23
+ end
24
+ end
25
+ alias_method :call_without_quiet_ahoy, :call
26
+ alias_method :call, :call_with_quiet_ahoy
27
+ end
28
+ end
29
+
30
+ # for importmap
31
+ initializer "ahoy.importmap" do |app|
32
+ if app.config.respond_to?(:assets) && defined?(Importmap) && defined?(Sprockets)
33
+ app.config.assets.precompile << "ahoy.js"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ module Ahoy
2
+ class GeocodeV2Job < ActiveJob::Base
3
+ queue_as { Ahoy.job_queue }
4
+
5
+ def perform(visit_token, ip)
6
+ location =
7
+ begin
8
+ Geocoder.search(ip).first
9
+ rescue NameError
10
+ raise "Add the geocoder gem to your Gemfile to use geocoding"
11
+ rescue => e
12
+ Ahoy.log "Geocode error: #{e.class.name}: #{e.message}"
13
+ nil
14
+ end
15
+
16
+ if location && location.country.present?
17
+ data = {
18
+ country: location.country,
19
+ country_code: location.try(:country_code).presence,
20
+ region: location.try(:state).presence,
21
+ city: location.try(:city).presence,
22
+ postal_code: location.try(:postal_code).presence,
23
+ latitude: location.try(:latitude).presence,
24
+ longitude: location.try(:longitude).presence
25
+ }
26
+
27
+ Ahoy::Tracker.new(visit_token: visit_token).geocode(data)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,40 @@
1
+ module Ahoy
2
+ module Helper
3
+ def amp_event(name, properties = {})
4
+ url = Ahoy::Engine.routes.url_helpers.events_url(
5
+ url_options.slice(:host, :port, :protocol).merge(
6
+ name: name,
7
+ properties: properties,
8
+ screen_width: "SCREEN_WIDTH",
9
+ screen_height: "SCREEN_HEIGHT",
10
+ platform: "Web",
11
+ landing_page: "AMPDOC_URL",
12
+ referrer: "DOCUMENT_REFERRER",
13
+ random: "RANDOM"
14
+ )
15
+ )
16
+ url = "#{url}&visit_token=${clientId(ahoy_visit)}&visitor_token=${clientId(ahoy_visitor)}"
17
+
18
+ content_tag "amp-analytics" do
19
+ content_tag "script", type: "application/json" do
20
+ json_escape({
21
+ requests: {
22
+ pageview: url
23
+ },
24
+ triggers: {
25
+ trackPageview: {
26
+ on: "visible",
27
+ request: "pageview"
28
+ }
29
+ },
30
+ transport: {
31
+ beacon: true,
32
+ xhrpost: true,
33
+ image: false
34
+ }
35
+ }.to_json).html_safe
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,15 @@
1
+ module Ahoy
2
+ module Model
3
+ def visitable(name = :visit, **options)
4
+ class_eval do
5
+ belongs_to(name, class_name: "Ahoy::Visit", optional: true, **options)
6
+ before_create :set_ahoy_visit
7
+ end
8
+ class_eval %{
9
+ def set_ahoy_visit
10
+ self.#{name} ||= Ahoy.instance.try(:visit_or_create)
11
+ end
12
+ }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,88 @@
1
+ module Ahoy
2
+ module QueryMethods
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def where_event(name, properties = {})
7
+ where(name: name).where_props(properties)
8
+ end
9
+
10
+ def where_props(properties)
11
+ return all if properties.empty?
12
+
13
+ adapter_name = respond_to?(:connection_db_config) ? connection_db_config.adapter.to_s : "mongoid"
14
+ case adapter_name
15
+ when "mongoid"
16
+ where(properties.to_h { |k, v| ["properties.#{k}", v] })
17
+ when /mysql|trilogy/i
18
+ where("JSON_CONTAINS(properties, ?, '$') = 1", properties.to_json)
19
+ when /postg/i
20
+ case columns_hash["properties"].type
21
+ when :hstore
22
+ properties.inject(all) do |relation, (k, v)|
23
+ if v.nil?
24
+ relation.where("properties -> ? IS NULL", k.to_s)
25
+ else
26
+ relation.where("properties -> ? = ?", k.to_s, v.to_s)
27
+ end
28
+ end
29
+ when :jsonb
30
+ where("properties @> ?", properties.to_json)
31
+ else
32
+ where("properties::jsonb @> ?", properties.to_json)
33
+ end
34
+ when /sqlite/i
35
+ properties.inject(all) do |relation, (k, v)|
36
+ if v.nil?
37
+ relation.where("JSON_EXTRACT(properties, ?) IS NULL", "$.#{k}")
38
+ else
39
+ relation.where("JSON_EXTRACT(properties, ?) = ?", "$.#{k}", v.as_json)
40
+ end
41
+ end
42
+ else
43
+ raise "Adapter not supported: #{adapter_name}"
44
+ end
45
+ end
46
+ alias_method :where_properties, :where_props
47
+
48
+ def group_prop(*props)
49
+ # like with group
50
+ props.flatten!
51
+
52
+ relation = all
53
+ adapter_name = respond_to?(:connection_db_config) ? connection_db_config.adapter.to_s : "mongoid"
54
+ case adapter_name
55
+ when "mongoid"
56
+ raise "Adapter not supported: #{adapter_name}"
57
+ when /mysql|trilogy/i
58
+ props.each do |prop|
59
+ quoted_prop = connection_pool.with_connection { |c| c.quote("$.#{prop}") }
60
+ relation = relation.group("JSON_UNQUOTE(JSON_EXTRACT(properties, #{quoted_prop}))")
61
+ end
62
+ when /postg/i
63
+ # convert to jsonb to fix
64
+ # could not identify an equality operator for type json
65
+ # and for text columns
66
+ column_type = columns_hash["properties"].type
67
+ cast = [:jsonb, :hstore].include?(column_type) ? "" : "::jsonb"
68
+
69
+ props.each do |prop|
70
+ quoted_prop = connection_pool.with_connection { |c| c.quote(prop) }
71
+ relation = relation.group("properties#{cast} -> #{quoted_prop}")
72
+ end
73
+ when /sqlite/i
74
+ props.each do |prop|
75
+ quoted_prop = connection_pool.with_connection { |c| c.quote("$.#{prop}") }
76
+ relation = relation.group("JSON_EXTRACT(properties, #{quoted_prop})")
77
+ end
78
+ else
79
+ raise "Adapter not supported: #{adapter_name}"
80
+ end
81
+ relation
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ # backward compatibility
88
+ Ahoy::Properties = Ahoy::QueryMethods