Module: Mapkick::Helper

Defined in:
lib/mapkick/helper.rb

Instance Method Summary collapse

Instance Method Details

#js_map(data_source, **options) ⇒ Object

don’t break out options since need to merge with default options



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/mapkick/helper.rb', line 4

def js_map(data_source, **options)
  options = Mapkick::Utils.deep_merge(Mapkick.options, options)

  @mapkick_map_id ||= 0
  element_id = options.delete(:id) || "map-#{@mapkick_map_id += 1}"

  height = (options.delete(:height) || "500px").to_s
  width = (options.delete(:width) || "100%").to_s

  nonce = options.fetch(:nonce, true)
  options.delete(:nonce)
  if nonce == true
    # Secure Headers also defines content_security_policy_nonce but it takes an argument
    # Rails 5.2 overrides this method, but earlier versions do not
    if respond_to?(:content_security_policy_nonce) && (content_security_policy_nonce rescue nil)
      # Rails 5.2+
      nonce = content_security_policy_nonce
    elsif respond_to?(:content_security_policy_script_nonce)
      # Secure Headers
      nonce = content_security_policy_script_nonce
    else
      nonce = nil
    end
  end
  nonce_html = nonce ? " nonce=\"#{ERB::Util.html_escape(nonce)}\"" : nil

  # html vars
  html_vars = {
    id: element_id,
    height: height,
    width: width,
    # don't delete loading option since it needs to be passed to JS
    loading: options[:loading] || "Loading..."
  }

  [:height, :width].each do |k|
    # limit to alphanumeric and % for simplicity
    # this prevents things like calc() but safety is the priority
    # dot does not need escaped in square brackets
    raise ArgumentError, "Invalid #{k}" unless html_vars[k] =~ /\A[a-zA-Z0-9%.]*\z/
  end

  html_vars.each_key do |k|
    # escape all variables
    # we already limit height and width above, but escape for safety as fail-safe
    # to prevent XSS injection in worse-case scenario
    html_vars[k] = ERB::Util.html_escape(html_vars[k])
  end

  html = %(<div id="%{id}" style="height: %{height}; width: %{width};"><div style="height: %{height}; text-align: center; color: #999; line-height: %{height}; font-size: 14px; font-family: 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif;">%{loading}</div></div>) % html_vars

  # access token
  access_token = options.delete(:access_token) || options.delete(:accessToken) || ENV["MAPBOX_ACCESS_TOKEN"]
  if access_token
    # can bypass with string keys
    # but should help prevent common errors
    if access_token.start_with?("sk.")
      raise Mapkick::Error, "Expected public access token"
    elsif !access_token.start_with?("pk.")
      raise Mapkick::Error, "Invalid access token"
    end
    options[:accessToken] = access_token
  end

  # js vars
  js_vars = {
    id: element_id,
    data: data_source,
    options: options
  }
  js_vars.each_key do |k|
    js_vars[k] = Mapkick::Utils.json_escape(js_vars[k].to_json)
  end
  createjs = "new Mapkick.Map(%{id}, %{data}, %{options});" % js_vars

  # don't rerun JS on preview
  js = "    <script\#{nonce_html}>\n      (function() {\n        if (document.documentElement.hasAttribute(\"data-turbolinks-preview\")) return;\n        if (document.documentElement.hasAttribute(\"data-turbo-preview\")) return;\n\n        var createMap = function() { \#{createjs} };\n        if (\"Mapkick\" in window) {\n          createMap();\n        } else {\n          window.addEventListener(\"mapkick:load\", createMap, true);\n        }\n      })();\n    </script>\n  JS\n\n  html += \"\\n\#{js}\"\n\n  html.respond_to?(:html_safe) ? html.html_safe : html\nend\n"