perimeter_x 1.3.0 → 2.2.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.
@@ -0,0 +1,26 @@
1
+ def validate_hash_schema(hash, schema)
2
+ hash.each do |key, value|
3
+ if schema.key?(key) && value != nil
4
+ # validate value types in hash are according to schema
5
+ if !schema[key][:types].include?(value.class)
6
+ raise PxConfigurationException.new("PerimeterX: Type of #{key} should be one of #{schema[key][:types]} but instead is #{value.class}")
7
+ end
8
+
9
+ # validate arrays elments types are according to schema
10
+ if value.class == Array
11
+ value.each do |element|
12
+ if !schema[key][:allowed_element_types].include?(element.class)
13
+ raise PxConfigurationException.new("PerimeterX: #{key} may only contain elements of the following types: #{schema[key][:allowed_element_types]} but includes element of type #{element.class}")
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ # validate required fields exist in hash
21
+ schema.each do |key, value|
22
+ if value[:required] && hash[key].nil?
23
+ raise PxConfigurationException.new("PerimeterX: #{key} configuration is missing")
24
+ end
25
+ end
26
+ end
@@ -18,47 +18,51 @@ module PxModule
18
18
 
19
19
  def verify(px_ctx)
20
20
  begin
21
- # Case no cookie
22
21
  if px_ctx.context[:px_cookie].empty?
23
- @logger.warn("PerimeterxCookieValidator:[verify]: cookie not found")
22
+ @logger.warn("PerimeterxCookieValidator:[verify]: no cookie")
24
23
  px_ctx.context[:s2s_call_reason] = PxModule::NO_COOKIE
25
24
  return false, px_ctx
26
25
  end
27
-
26
+
28
27
  # Deserialize cookie start
29
28
  cookie = PerimeterxPayload.px_cookie_factory(px_ctx, @px_config)
30
- if (!cookie.deserialize())
29
+ if !cookie.deserialize()
31
30
  @logger.warn("PerimeterxCookieValidator:[verify]: invalid cookie")
31
+ px_ctx.context[:px_orig_cookie] = px_ctx.get_px_cookie
32
32
  px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_DECRYPTION_FAILED
33
33
  return false, px_ctx
34
34
  end
35
35
  px_ctx.context[:decoded_cookie] = cookie.decoded_cookie
36
36
  px_ctx.context[:score] = cookie.cookie_score()
37
37
  px_ctx.context[:uuid] = cookie.decoded_cookie[:u]
38
- px_ctx.context[:vid] = cookie.decoded_cookie[:v]
39
38
  px_ctx.context[:block_action] = px_ctx.set_block_action_type(cookie.cookie_block_action())
40
39
  px_ctx.context[:cookie_hmac] = cookie.cookie_hmac()
41
40
 
42
- if (cookie.expired?)
41
+ vid = cookie.decoded_cookie[:v]
42
+ if vid.is_a?(String) && vid.match(PxModule::VID_REGEX)
43
+ px_ctx.context[:vid_source] = "risk_cookie"
44
+ px_ctx.context[:vid] = vid
45
+ end
46
+
47
+ if cookie.expired?
43
48
  @logger.warn("PerimeterxCookieValidator:[verify]: cookie expired")
44
49
  px_ctx.context[:s2s_call_reason] = PxModule::EXPIRED_COOKIE
45
50
  return false, px_ctx
46
51
  end
47
52
 
48
- if (cookie.high_score?)
53
+ if cookie.high_score?
49
54
  @logger.warn("PerimeterxCookieValidator:[verify]: cookie high score")
50
- px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_HIGH_SCORE
51
55
  px_ctx.context[:blocking_reason] = 'cookie_high_score'
52
56
  return true, px_ctx
53
57
  end
54
58
 
55
- if (!cookie.secured?)
59
+ if !cookie.secured?
56
60
  @logger.warn("PerimeterxCookieValidator:[verify]: cookie invalid hmac")
57
- px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_VALIDATION_FAILED
61
+ px_ctx.context[:s2s_call_reason] = PxModule:: COOKIE_VALIDATION_FAILED
58
62
  return false, px_ctx
59
63
  end
60
64
 
61
- if (px_ctx.context[:sensitive_route])
65
+ if px_ctx.context[:sensitive_route]
62
66
  @logger.info("PerimeterxCookieValidator:[verify]: cookie was verified but route is sensitive")
63
67
  px_ctx.context[:s2s_call_reason] = PxModule::SENSITIVE_ROUTE
64
68
  return false, px_ctx
@@ -66,10 +70,11 @@ module PxModule
66
70
 
67
71
  @logger.debug("PerimeterxCookieValidator:[verify]: cookie validation passed succesfully")
68
72
 
73
+ px_ctx.context[:pass_reason] = 'cookie'
69
74
  return true, px_ctx
70
75
  rescue Exception => e
71
76
  @logger.error("PerimeterxCookieValidator:[verify]: exception while verifying cookie => #{e.message}")
72
- px_ctx.context[:px_orig_cookie] = cookie.px_cookie
77
+ px_ctx.context[:px_orig_cookie] = px_ctx.context[:px_cookie]
73
78
  px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_DECRYPTION_FAILED
74
79
  return false, px_ctx
75
80
  end
@@ -26,12 +26,22 @@ module PxModule
26
26
  :additional => {
27
27
  :s2s_call_reason => px_ctx.context[:s2s_call_reason],
28
28
  :module_version => @px_config[:sdk_name],
29
- :cookie_origin => px_ctx.context[:cookie_origin],
30
29
  :http_method => px_ctx.context[:http_method],
31
30
  :http_version => px_ctx.context[:http_version],
32
31
  :risk_mode => risk_mode
33
32
  }
34
33
  }
34
+
35
+ #Check for cookie_origin
36
+ if !px_ctx.context[:px_cookie].empty?
37
+ request_body[:additional][:cookie_origin] = px_ctx.context[:cookie_origin]
38
+ end
39
+
40
+ #Override s2s_call_reason in case of mobile error
41
+ if !px_ctx.context[:mobile_error].nil?
42
+ request_body[:additional][:s2s_call_reason] = "mobile_error_#{px_ctx.context[:mobile_error]}"
43
+ end
44
+
35
45
  #Check for hmac
36
46
  @logger.debug("px_ctx cookie_hmac key = #{px_ctx.context.key?(:cookie_hmac)}, value is: #{px_ctx.context[:cookie_hmac]}")
37
47
  if px_ctx.context.key?(:cookie_hmac)
@@ -67,11 +77,19 @@ module PxModule
67
77
  };
68
78
 
69
79
  # Custom risk handler
80
+ risk_start = Time.now
70
81
  if (risk_mode == PxModule::ACTIVE_MODE && @px_config.key?(:custom_risk_handler))
71
- response = @px_config[:custom_risk_handler].call(PxModule::API_V2_RISK, request_body, headers, @px_config[:api_timeout], @px_config[:api_timeout_connection])
82
+ response = @px_config[:custom_risk_handler].call(PxModule::API_V3_RISK, request_body, headers, @px_config[:api_timeout], @px_config[:api_timeout_connection])
72
83
  else
73
- response = @http_client.post(PxModule::API_V2_RISK , request_body, headers, @px_config[:api_timeout], @px_config[:api_timeout_connection])
84
+ response = @http_client.post(PxModule::API_V3_RISK , request_body, headers, @px_config[:api_timeout], @px_config[:api_timeout_connection])
85
+ end
86
+
87
+ # Set risk_rtt
88
+ if(response)
89
+ risk_end = Time.now
90
+ px_ctx.context[:risk_rtt] = ((risk_end-risk_start)*1000).round
74
91
  end
92
+
75
93
  return response
76
94
  end
77
95
 
@@ -79,6 +97,7 @@ module PxModule
79
97
  @logger.debug("PerimeterxS2SValidator[verify]")
80
98
  response = send_risk_request(px_ctx)
81
99
  if (!response)
100
+ px_ctx.context[:pass_reason] = "s2s_timeout"
82
101
  return px_ctx
83
102
  end
84
103
  px_ctx.context[:made_s2s_risk_api_call] = true
@@ -86,7 +105,7 @@ module PxModule
86
105
  # From here response should be valid, if success or error
87
106
  response_body = eval(response.body);
88
107
  # When success
89
- if (response.code == 200 && response_body.key?(:score) && response_body.key?(:action))
108
+ if (response.code == 200 && response_body.key?(:score) && response_body.key?(:action) && response_body.key?(:status) && response_body[:status] == 0 )
90
109
  @logger.debug("PerimeterxS2SValidator[verify]: response ok")
91
110
  score = response_body[:score]
92
111
  px_ctx.context[:score] = score
@@ -97,13 +116,17 @@ module PxModule
97
116
  px_ctx.context[:blocking_reason] = 'challenge'
98
117
  elsif (score >= @px_config[:blocking_score])
99
118
  px_ctx.context[:blocking_reason] = 's2s_high_score'
119
+ else
120
+ px_ctx.context[:pass_reason] = 's2s'
100
121
  end #end challange or blocking score
101
122
  end #end success response
102
123
 
103
124
  # When error
104
- if(response.code != 200)
105
- @logger.warn("PerimeterxS2SValidator[verify]: bad response, return code #{response.code}")
106
- px_ctx.context[:uuid] = ""
125
+ risk_error_status = response_body && response_body.key?(:status) && response_body[:status] == -1
126
+ if(response.code != 200 || risk_error_status)
127
+ @logger.warn("PerimeterxS2SValidator[verify]: bad response, returned code #{response.code} #{risk_error_status ? "risk status: -1" : ""}")
128
+ px_ctx.context[:pass_reason] = 'request_failed'
129
+ px_ctx.context[:uuid] = (!response_body || response_body[:uuid].nil?)? "" : response_body[:uuid]
107
130
  px_ctx.context[:s2s_error_msg] = !response_body || response_body[:message].nil? ? 'unknown' : response_body[:message]
108
131
  end
109
132
 
@@ -10,8 +10,7 @@ module PxModule
10
10
 
11
11
  # Routes
12
12
  API_V1_S2S = '/api/v1/collector/s2s'
13
- API_V1_CAPTCHA = '/api/v1/risk/captcha'
14
- API_V2_RISK = '/api/v2/risk'
13
+ API_V3_RISK = '/api/v3/risk'
15
14
 
16
15
  # Activity Types
17
16
  BLOCK_ACTIVITY = 'block'
@@ -27,9 +26,9 @@ module PxModule
27
26
  SENSITIVE_ROUTE = 'sensitive_route'
28
27
 
29
28
  # Templates
30
- BLOCK_TEMPLATE = 'block'
31
- CAPTCHA_TEMPLATE = 'captcha'
29
+ CHALLENGE_TEMPLATE = 'block_template'
32
30
  TEMPLATE_EXT = '.mustache'
31
+ RATELIMIT_TEMPLATE = 'ratelimit'
33
32
 
34
33
 
35
34
  # Template Props
@@ -41,11 +40,24 @@ module PxModule
41
40
  PROP_CUSTOM_LOGO = :customLogo
42
41
  PROP_CSS_REF = :cssRef
43
42
  PROP_JS_REF = :jsRef
44
- HOST_URL = :hostUrl
43
+ PROP_BLOCK_SCRIPT = :blockScript
44
+ PROP_JS_CLIENT_SRC = :jsClientSrc
45
+ PROP_HOST_URL = :hostUrl
46
+ PROP_FIRST_PARTY_ENABLED = :firstPartyEnabled
47
+
48
+ # Hosts
49
+ CLIENT_HOST = 'client.perimeterx.net'
50
+ CAPTCHA_HOST = 'captcha.px-cdn.net'
45
51
 
46
52
  VISIBLE = 'visible'
47
53
  HIDDEN = 'hidden'
48
54
 
49
55
  # Mobile SDK
50
56
  TOKEN_HEADER = 'X-PX-AUTHORIZATION'
57
+ ORIGINAL_TOKEN_HEADER = 'X-PX-ORIGINAL-TOKEN'
58
+
59
+ # Regular Expressions
60
+ VID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
61
+ MOBILE_TOKEN_V3_REGEX = /\A3:(.*)\z/
62
+ MOBILE_ERROR_REGEX = /\A[0-9]\z/
51
63
  end
@@ -1,6 +1,7 @@
1
1
  require 'perimeterx/utils/px_logger'
2
2
  require 'typhoeus'
3
3
  require 'concurrent'
4
+ require 'net/http'
4
5
 
5
6
  module PxModule
6
7
  class PxHttpClient
@@ -12,7 +13,7 @@ module PxModule
12
13
  def initialize(px_config)
13
14
  @px_config = px_config
14
15
  @logger = px_config[:logger]
15
- @logger.debug("PxHttpClient[initialize]: HTTP client is being initilized with base_uri: #{px_config[:perimeterx_server_host]}")
16
+ @logger.debug("PxHttpClient[initialize]: HTTP client is being initilized with base_uri: #{px_config[:backend_url]}")
16
17
  end
17
18
 
18
19
  # Runs a POST command to Perimeter X servers
@@ -28,7 +29,7 @@ module PxModule
28
29
  begin
29
30
  @logger.debug("PxHttpClient[post]: posting to #{path} headers {#{headers.to_json()}} body: {#{body.to_json()}} ")
30
31
  response = Typhoeus.post(
31
- "#{px_config[:perimeterx_server_host]}#{path}",
32
+ "#{px_config[:backend_url]}#{path}",
32
33
  headers: headers,
33
34
  body: body.to_json,
34
35
  timeout: api_timeout,
@@ -45,5 +46,61 @@ module PxModule
45
46
  return response
46
47
  end
47
48
 
49
+
50
+ def post_xhr(url, body, headers)
51
+ s = Time.now
52
+ begin
53
+ @logger.debug("PxHttpClient[post]: sending xhr post request to #{url} with headers {#{headers.to_json()}}")
54
+
55
+ #set url
56
+ uri = URI(url)
57
+ req = Net::HTTP::Post.new(uri)
58
+
59
+ # set body
60
+ req.body=body
61
+
62
+ # set headers
63
+ headers.each do |key, value|
64
+ req[key] = value
65
+ end
66
+
67
+ # send request
68
+ response = Net::HTTP.start(uri.hostname, uri.port) {|http|
69
+ http.request(req)
70
+ }
71
+
72
+ ensure
73
+ e = Time.now
74
+ @logger.debug("PxHttpClient[get]: runtime: #{(e-s) * 1000.0}")
75
+ end
76
+ return response
77
+ end
78
+
79
+
80
+ def get(url, headers)
81
+ s = Time.now
82
+ begin
83
+ @logger.debug("PxHttpClient[get]: sending get request to #{url} with headers {#{headers.to_json()}}")
84
+
85
+ #set url
86
+ uri = URI(url)
87
+ req = Net::HTTP::Get.new(uri)
88
+
89
+ # set headers
90
+ headers.each do |key, value|
91
+ req[key] = value
92
+ end
93
+
94
+ # send request
95
+ response = Net::HTTP.start(uri.hostname, uri.port) {|http|
96
+ http.request(req)
97
+ }
98
+
99
+ ensure
100
+ e = Time.now
101
+ @logger.debug("PxHttpClient[get]: runtime: #{(e-s) * 1000.0}")
102
+ end
103
+ return response
104
+ end
48
105
  end
49
106
  end
@@ -3,23 +3,24 @@ require 'perimeterx/utils/px_constants'
3
3
  module PxModule
4
4
  module PxTemplateFactory
5
5
 
6
- def self.get_template(px_ctx, px_config)
6
+ def self.get_template(px_ctx, px_config, px_template_object)
7
7
  logger = px_config[:logger]
8
8
  if (px_config[:challenge_enabled] && px_ctx.context[:block_action] == 'challenge')
9
9
  logger.debug('PxTemplateFactory[get_template]: px challange triggered')
10
10
  return px_ctx.context[:block_action_data].html_safe
11
11
  end
12
12
 
13
- logger.debug('PxTemplateFactory[get_template]: rendering template')
14
- template_type = px_ctx.context[:block_action] == 'captcha' ? PxModule::CAPTCHA_TEMPLATE : BLOCK_TEMPLATE
13
+ view = Mustache.new
15
14
 
16
- template_postfix = ''
17
- if px_ctx.context[:cookie_origin] == 'header'
18
- template_postfix = '.mobile'
15
+ if (px_ctx.context[:block_action] == 'rate_limit')
16
+ logger.debug('PxTemplateFactory[get_template]: rendering ratelimit template')
17
+ template_type = RATELIMIT_TEMPLATE
18
+ else
19
+ logger.debug('PxTemplateFactory[get_template]: rendering template')
20
+ template_type = CHALLENGE_TEMPLATE
19
21
  end
20
22
 
21
- Mustache.template_file = "#{File.dirname(__FILE__) }/templates/#{template_type}#{template_postfix}#{PxModule::TEMPLATE_EXT}"
22
- view = Mustache.new
23
+ Mustache.template_file = "#{File.dirname(__FILE__) }/templates/#{template_type}#{PxModule::TEMPLATE_EXT}"
23
24
 
24
25
  view[PxModule::PROP_APP_ID] = px_config[:app_id]
25
26
  view[PxModule::PROP_REF_ID] = px_ctx.context[:uuid]
@@ -28,8 +29,11 @@ module PxModule
28
29
  view[PxModule::PROP_CUSTOM_LOGO] = px_config[:custom_logo]
29
30
  view[PxModule::PROP_CSS_REF] = px_config[:css_ref]
30
31
  view[PxModule::PROP_JS_REF] = px_config[:js_ref]
31
- view[PxModule::HOST_URL] = "https://collector-#{px_config[:app_id]}.perimeterx.net"
32
+ view[PxModule::PROP_HOST_URL] = px_template_object[:host_url]
32
33
  view[PxModule::PROP_LOGO_VISIBILITY] = px_config[:custom_logo] ? PxModule::VISIBLE : PxModule::HIDDEN
34
+ view[PxModule::PROP_BLOCK_SCRIPT] = px_template_object[:block_script]
35
+ view[PxModule::PROP_JS_CLIENT_SRC] = px_template_object[:js_client_src]
36
+ view[PxModule::PROP_FIRST_PARTY_ENABLED] = px_ctx.context[:first_party_enabled]
33
37
 
34
38
  return view.render.html_safe
35
39
  end
@@ -6,19 +6,19 @@
6
6
  <title>Access to this page has been denied.</title>
7
7
  <link href="https://fonts.googleapis.com/css?family=Open+Sans:300" rel="stylesheet">
8
8
  <style>
9
- html,body{
9
+ html, body {
10
10
  margin: 0;
11
11
  padding: 0;
12
12
  font-family: 'Open Sans', sans-serif;
13
13
  color: #000;
14
14
  }
15
15
 
16
- a{
16
+ a {
17
17
  color: #c5c5c5;
18
18
  text-decoration: none;
19
19
  }
20
20
 
21
- .container{
21
+ .container {
22
22
  align-items: center;
23
23
  display: flex;
24
24
  flex: 1;
@@ -30,7 +30,7 @@
30
30
  .container > div {
31
31
  width: 100%;
32
32
  display: flex;
33
- justify-content:center;
33
+ justify-content: center;
34
34
  }
35
35
 
36
36
  .container > div > div {
@@ -38,38 +38,40 @@
38
38
  width: 80%;
39
39
  }
40
40
 
41
- .customer-logo-wrapper{
41
+ .customer-logo-wrapper {
42
42
  padding-top: 2rem;
43
43
  flex-grow: 0;
44
44
  background-color: #fff;
45
45
  visibility: {{logoVisibility}};
46
46
  }
47
47
 
48
- .customer-logo{
48
+ .customer-logo {
49
49
  border-bottom: 1px solid #000;
50
50
  }
51
51
 
52
- .customer-logo > img{
52
+ .customer-logo > img {
53
53
  padding-bottom: 1rem;
54
54
  max-height: 50px;
55
- max-width: auto;
55
+ max-width: 100%;
56
56
  }
57
57
 
58
- .page-title-wrapper{
58
+ .page-title-wrapper {
59
59
  flex-grow: 2;
60
60
  }
61
+
61
62
  .page-title {
62
63
  flex-direction: column-reverse;
63
64
  }
64
65
 
65
- .content-wrapper{
66
+ .content-wrapper {
66
67
  flex-grow: 5;
67
68
  }
68
- .content{
69
+
70
+ .content {
69
71
  flex-direction: column;
70
72
  }
71
73
 
72
- .page-footer-wrapper{
74
+ .page-footer-wrapper {
73
75
  align-items: center;
74
76
  flex-grow: 0.2;
75
77
  background-color: #000;
@@ -77,17 +79,16 @@
77
79
  font-size: 70%;
78
80
  }
79
81
 
80
- @media (min-width:768px){
81
- html,body{
82
+ @media (min-width: 768px) {
83
+ html, body {
82
84
  height: 100%;
83
85
  }
84
86
  }
85
87
  </style>
86
88
  <!-- Custom CSS -->
87
89
  {{#cssRef}}
88
- <link rel="stylesheet" type="text/css" href="{{cssRef}}" />
90
+ <link rel="stylesheet" type="text/css" href="{{{cssRef}}}"/>
89
91
  {{/cssRef}}
90
- <script src="https://www.google.com/recaptcha/api.js" async defer></script>
91
92
  </head>
92
93
 
93
94
  <body>
@@ -104,13 +105,12 @@
104
105
  </div>
105
106
  <div class="content-wrapper">
106
107
  <div class="content">
107
- <p>
108
- Please click "I am not a robot" to continue
109
- </p>
110
- <div class="g-recaptcha" data-sitekey="6Lcj-R8TAAAAABs3FrRPuQhLMbp5QrHsHufzLf7b" data-callback="handleCaptcha" data-theme="dark">
108
+
109
+ <div id="px-captcha">
111
110
  </div>
112
111
  <p>
113
- Access to this page has been denied because we believe you are using automation tools to browse the website.
112
+ Access to this page has been denied because we believe you are using automation tools to browse the
113
+ website.
114
114
  </p>
115
115
  <p>
116
116
  This may happen as a result of the following:
@@ -124,7 +124,8 @@
124
124
  </li>
125
125
  </ul>
126
126
  <p>
127
- Please make sure that Javascript and cookies are enabled on your browser and that you are not blocking them from loading.
127
+ Please make sure that Javascript and cookies are enabled on your browser and that you are not blocking
128
+ them from loading.
128
129
  </p>
129
130
  <p>
130
131
  Reference ID: #{{refId}}
@@ -135,62 +136,40 @@
135
136
  <div class="page-footer">
136
137
  <p>
137
138
  Powered by
138
- <a href="https://www.perimeterx.com">PerimeterX</a>
139
+ <a href="https://www.perimeterx.com/whywasiblocked">PerimeterX</a>
139
140
  , Inc.
140
141
  </p>
141
142
  </div>
142
143
  </div>
143
144
  </section>
144
- <!-- Captcha -->
145
+ <!-- Px -->
145
146
  <script>
146
- function captchaSolved(res) {
147
- window.location.href = '/px/captcha_callback?status=' + res.status;
148
- }
149
-
150
- function handleCaptcha(response) {
151
- var appId = '{{appId}}';
152
- var vid = '{{vid}}';
153
- var uuid = '{{uuid}}';
154
- var collectorUrl = '{{{hostUrl}}}';
155
- var req = new XMLHttpRequest();
156
- req.open('POST', collectorUrl + '/api/v1/collector/captcha');
157
- req.setRequestHeader('Content-Type', 'application/json');
158
- req.addEventListener('error', function() {
159
- captchaSolved({
160
- status: 1
161
- });
162
- });
163
- req.addEventListener('cancel', function() {
164
- captchaSolved({
165
- status: 2
166
- });
167
- });
168
- req.addEventListener('load', function() {
169
- if (req.status == 200) {
170
- try {
171
- var responseJSON = JSON.parse(req.responseText);
172
- return captchaSolved(responseJSON);
173
- } catch (ex) {}
174
- }
175
- captchaSolved({
176
- status: 3
177
- });
178
- });
179
- req.send(JSON.stringify({
180
- appId: appId,
181
- uuid: uuid,
182
- vid: vid,
183
- pxCaptcha: response,
184
- hostname: window.location.hostname,
185
- request: {
186
- url: window.location.href
187
- }
188
- }));
147
+ window._pxAppId = '{{appId}}';
148
+ window._pxJsClientSrc = '{{{jsClientSrc}}}';
149
+ window._pxFirstPartyEnabled = {{firstPartyEnabled}};
150
+ window._pxVid = '{{vid}}';
151
+ window._pxUuid = '{{uuid}}';
152
+ window._pxHostUrl = '{{{hostUrl}}}';
153
+ </script>
154
+ <script>
155
+ var s = document.createElement('script');
156
+ s.src = '{{{blockScript}}}';
157
+ var p = document.getElementsByTagName('head')[0];
158
+ p.insertBefore(s, null);
159
+ if ({{firstPartyEnabled}}) {
160
+ s.onerror = function () {
161
+ s = document.createElement('script');
162
+ var suffixIndex = '{{{blockScript}}}'.indexOf('captcha.js');
163
+ var temperedBlockScript = '{{{blockScript}}}'.substring(suffixIndex);
164
+ s.src = '//captcha.px-cdn.net/{{appId}}/' + temperedBlockScript;
165
+ p.parentNode.insertBefore(s, p);
166
+ };
189
167
  }
190
168
  </script>
169
+
191
170
  <!-- Custom Script -->
192
171
  {{#jsRef}}
193
- <script src="{{jsRef}}"></script>
172
+ <script src="{{{jsRef}}}"></script>
194
173
  {{/jsRef}}
195
174
  </body>
196
- </html>
175
+ </html>