capi_param_builder_ruby 1.0.0
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.
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 245f1fdca8be3ca8c5e82e8fb9b52791687b4eecad6414fd16bc0cda904a06e3
|
4
|
+
data.tar.gz: 6248cd486f6aa855845b1638c4d955560643ab38dc4eb66642c8913690c0f960
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e659e96931128047110e7f083a704d1faf21686f411ad2b61404db5690d439f491d967550e0e9034712d6080ae7ba21050eb013fcbea44d9963fdacc1003bf42
|
7
|
+
data.tar.gz: c96a1c43c76c7c5282c1bdebb38e1f249e56b08656c04a92885b2488e75f4a8df0f4297b524bb304ed8b6f9ef5d020b4c373a8034312b1b4e12a99e503254bd7
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
# This source code is licensed under the license found in the
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
6
|
+
|
7
|
+
class CookieSettings
|
8
|
+
attr_accessor :name, :value, :domain, :max_age
|
9
|
+
def initialize(name, value, domain, max_age)
|
10
|
+
@name = name
|
11
|
+
@value = value
|
12
|
+
@domain = domain
|
13
|
+
@max_age = max_age
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
# This source code is licensed under the license found in the
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
6
|
+
|
7
|
+
class EtldPlusOneResolver
|
8
|
+
def resolve(host_name)
|
9
|
+
raise NotImplementedError, "Subclasses must implement the resolve method."
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
# This source code is licensed under the license found in the
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
6
|
+
|
7
|
+
class FbcParamConfigs
|
8
|
+
attr_accessor :query, :prefix, :ebp_path
|
9
|
+
def initialize(query, prefix, ebp_path)
|
10
|
+
@query = query
|
11
|
+
@prefix = prefix
|
12
|
+
@ebp_path = ebp_path
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
# This source code is licensed under the license found in the
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
6
|
+
|
7
|
+
require_relative 'capi_param_builder/fbc_param_configs'
|
8
|
+
require_relative 'capi_param_builder/cookie_settings'
|
9
|
+
require_relative 'capi_param_builder/etld_plus_one_resolver'
|
10
|
+
require 'set'
|
11
|
+
require 'uri'
|
12
|
+
require 'cgi'
|
13
|
+
|
14
|
+
class ParamBuilder
|
15
|
+
FBC_NAME = "_fbc"
|
16
|
+
FBP_NAME = "_fbp"
|
17
|
+
DEFAULT_1PC_AGE = 90 * 24 * 3600
|
18
|
+
LANGUAGE_TOKEN = "BQ"
|
19
|
+
SUPPORTED_LANGUAGE_TOKENS = ["AQ", "Ag", "Aw", "BA", "BQ", "Bg"]
|
20
|
+
MIN_PAYLOAD_SPLIT_LENGTH = 4
|
21
|
+
MAX_PAYLOAD_WITH_LANGUAGE_TOKEN_LENGTH = 5
|
22
|
+
IPV4_REGEX = /\A(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\z/
|
23
|
+
IPV6_REGEX = /\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\z|\A(?:[0-9a-fA-F]{1,4}:){1,7}:\z|\A(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}\z|\A(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}\z|\A(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}\z|\A(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}\z|\A(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}\z|\A[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}\z|\A:(?::[0-9a-fA-F]{1,4}){1,7}\z|\A::\z|\A(?:[0-9a-fA-F]{1,4}:){6}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\z/
|
24
|
+
|
25
|
+
def initialize(input = nil)
|
26
|
+
@fbc_params_configs = [FbcParamConfigs.new("fbclid", "", "clickID")]
|
27
|
+
|
28
|
+
if input.nil?
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
if input.is_a?(Array)
|
33
|
+
@domain_list = []
|
34
|
+
input.each do |domain_item|
|
35
|
+
@domain_list.push(extract_host_from_http_host(domain_item.downcase))
|
36
|
+
end
|
37
|
+
elsif input.is_a?(EtldPlusOneResolver)
|
38
|
+
@etld_plus_one_resolver = input
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private def pre_process_cookies(cookies, cookie_name)
|
43
|
+
# Sanity check
|
44
|
+
if cookies.nil? || cookies[cookie_name].nil?
|
45
|
+
return nil
|
46
|
+
end
|
47
|
+
cookie_value = cookies[cookie_name]
|
48
|
+
parts = cookie_value.split(/\./)
|
49
|
+
if parts.size < MIN_PAYLOAD_SPLIT_LENGTH || \
|
50
|
+
parts.size > MAX_PAYLOAD_WITH_LANGUAGE_TOKEN_LENGTH
|
51
|
+
return nil
|
52
|
+
end
|
53
|
+
if parts.size == MAX_PAYLOAD_WITH_LANGUAGE_TOKEN_LENGTH && \
|
54
|
+
!SUPPORTED_LANGUAGE_TOKENS.include?(parts[MAX_PAYLOAD_WITH_LANGUAGE_TOKEN_LENGTH - 1])
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
# Append language token if not present
|
58
|
+
if parts.size == MIN_PAYLOAD_SPLIT_LENGTH
|
59
|
+
updated_cookie_value = cookie_value + "." + LANGUAGE_TOKEN
|
60
|
+
@cookie_to_set_dict[cookie_name] = CookieSettings.new(
|
61
|
+
cookie_name, updated_cookie_value, @etld_plus_one, DEFAULT_1PC_AGE)
|
62
|
+
return updated_cookie_value
|
63
|
+
end
|
64
|
+
# No change
|
65
|
+
return cookie_value
|
66
|
+
end
|
67
|
+
|
68
|
+
private def build_param_configs(
|
69
|
+
existing_payload, current_query, prefix, value)
|
70
|
+
is_click_id = current_query == "fbclid"
|
71
|
+
existing_payload ||= ""
|
72
|
+
existing_payload += \
|
73
|
+
[is_click_id ? "" : "_", prefix, is_click_id ? "" : "_", value].join
|
74
|
+
return existing_payload
|
75
|
+
end
|
76
|
+
|
77
|
+
private def get_new_fbc_payload_from_url(queries, referer=nil)
|
78
|
+
# Get query params from referer
|
79
|
+
if !referer.nil?
|
80
|
+
referer_uri = URI.parse(referer)
|
81
|
+
if !referer_uri.query.nil?
|
82
|
+
referer_query_params = CGI.parse(referer_uri.query)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
# Get the new fbc payload
|
86
|
+
new_fbc_payload = nil
|
87
|
+
@fbc_params_configs.each do |config|
|
88
|
+
if !queries.nil? && queries.has_key?(config.query)
|
89
|
+
query_value = queries[config.query]
|
90
|
+
if query_value.is_a?(Array)
|
91
|
+
query_value = queries[config.query][0]
|
92
|
+
end
|
93
|
+
new_fbc_payload = build_param_configs(
|
94
|
+
new_fbc_payload, config.query, config.prefix, query_value)
|
95
|
+
elsif !referer_query_params.nil? && \
|
96
|
+
referer_query_params.has_key?(config.query)
|
97
|
+
query_value = referer_query_params[config.query]
|
98
|
+
if query_value.is_a?(Array)
|
99
|
+
query_value = referer_query_params[config.query][0]
|
100
|
+
end
|
101
|
+
new_fbc_payload = build_param_configs(
|
102
|
+
new_fbc_payload, config.query, config.prefix, query_value)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
return new_fbc_payload
|
106
|
+
end
|
107
|
+
|
108
|
+
def process_request(host, queries, cookies, referer=nil)
|
109
|
+
compute_etld_plus_one_for_host(host)
|
110
|
+
@cookie_to_set_dict = {}
|
111
|
+
@cookie_to_set = Set.new()
|
112
|
+
@fbc = pre_process_cookies(cookies, FBC_NAME)
|
113
|
+
@fbp = pre_process_cookies(cookies, FBP_NAME)
|
114
|
+
|
115
|
+
# Get new fbc payload
|
116
|
+
new_fbc_payload = get_new_fbc_payload_from_url(queries, referer)
|
117
|
+
|
118
|
+
# fbc update
|
119
|
+
updated_fbc_cookie = get_updated_fbc_cookie(@fbc, new_fbc_payload)
|
120
|
+
if !updated_fbc_cookie.nil?
|
121
|
+
@cookie_to_set_dict[FBC_NAME] = updated_fbc_cookie
|
122
|
+
@fbc = updated_fbc_cookie.value
|
123
|
+
end
|
124
|
+
# fbp update
|
125
|
+
updated_fbp_cookie = get_updated_fbp_cookie(@fbp)
|
126
|
+
if !updated_fbp_cookie.nil?
|
127
|
+
@cookie_to_set_dict[FBP_NAME] = updated_fbp_cookie
|
128
|
+
@fbp = updated_fbp_cookie.value
|
129
|
+
end
|
130
|
+
@cookie_to_set = Set.new(@cookie_to_set_dict.values)
|
131
|
+
return @cookie_to_set
|
132
|
+
end
|
133
|
+
|
134
|
+
def get_cookies_to_set()
|
135
|
+
return @cookie_to_set
|
136
|
+
end
|
137
|
+
|
138
|
+
def get_fbc()
|
139
|
+
return @fbc
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_fbp()
|
143
|
+
return @fbp
|
144
|
+
end
|
145
|
+
|
146
|
+
private def compute_etld_plus_one_for_host(host)
|
147
|
+
if @etld_plus_one.nil? || @host.nil?
|
148
|
+
@host = host
|
149
|
+
host_name = extract_host_from_http_host(host)
|
150
|
+
if is_ip_address(host_name)
|
151
|
+
@etld_plus_one = maybe_bracket_ipv6(host_name)
|
152
|
+
@sub_domain_index = 0
|
153
|
+
else
|
154
|
+
@etld_plus_one = get_etld_plus_one(host_name)
|
155
|
+
@sub_domain_index = @etld_plus_one.split(".").size - 1
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
private def extract_host_from_http_host(host)
|
161
|
+
if !host.rindex("://").nil?
|
162
|
+
host = host.split("://", 2)[1]
|
163
|
+
end
|
164
|
+
pos_colon = host.rindex(":")
|
165
|
+
pos_bracket = host.rindex("]")
|
166
|
+
|
167
|
+
if pos_colon.nil?
|
168
|
+
return host
|
169
|
+
end
|
170
|
+
# if there's no right bracket (not IPv6 host), or colon is after
|
171
|
+
# right bracket it's a port separator
|
172
|
+
# examples:
|
173
|
+
# [::1]:8080 => trim
|
174
|
+
# google.com:8080 => trim
|
175
|
+
if pos_bracket.nil? || pos_colon > pos_bracket
|
176
|
+
host = host[0...pos_colon]
|
177
|
+
end
|
178
|
+
# for IPv6, remove the brackets
|
179
|
+
length = host.length
|
180
|
+
if length >= 2 && host[0] == "[" && host[length - 1] == "]"
|
181
|
+
return host[1 ... length - 1]
|
182
|
+
end
|
183
|
+
return host
|
184
|
+
end
|
185
|
+
|
186
|
+
private def is_ip_address(hostname)
|
187
|
+
is_ipv4 = IPV4_REGEX.match(hostname)
|
188
|
+
if !is_ipv4.nil?
|
189
|
+
return true
|
190
|
+
end
|
191
|
+
is_ipv6 = IPV6_REGEX.match(hostname)
|
192
|
+
return !is_ipv6.nil?
|
193
|
+
end
|
194
|
+
|
195
|
+
private def maybe_bracket_ipv6(host)
|
196
|
+
if host.rindex(":") != nil
|
197
|
+
return "[" + host + "]"
|
198
|
+
end
|
199
|
+
return host
|
200
|
+
end
|
201
|
+
|
202
|
+
private def get_etld_plus_one(host_name)
|
203
|
+
if !@etld_plus_one_resolver.nil?
|
204
|
+
return @etld_plus_one_resolver.resolve(host_name)
|
205
|
+
elsif !@domain_list.nil?
|
206
|
+
@domain_list.each do |domain|
|
207
|
+
normalized_host_name = host_name.downcase
|
208
|
+
if normalized_host_name == domain ||
|
209
|
+
normalized_host_name.end_with?(".#{domain}")
|
210
|
+
return domain
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
if host_name.count(".") > 2
|
215
|
+
return host_name.split(".", 2)[1]
|
216
|
+
end
|
217
|
+
return host_name
|
218
|
+
end
|
219
|
+
|
220
|
+
private def get_updated_fbc_cookie(existing_fbc = nil, new_fbc_payload)
|
221
|
+
if @fbc_params_configs.nil?
|
222
|
+
return nil
|
223
|
+
end
|
224
|
+
|
225
|
+
if new_fbc_payload.nil?
|
226
|
+
return nil
|
227
|
+
end
|
228
|
+
|
229
|
+
if existing_fbc.nil?
|
230
|
+
cookie_update = true
|
231
|
+
else
|
232
|
+
parts = existing_fbc.split(/\./)
|
233
|
+
cookie_update = new_fbc_payload != parts[3]
|
234
|
+
end
|
235
|
+
if cookie_update == false
|
236
|
+
return nil
|
237
|
+
end
|
238
|
+
|
239
|
+
current_ms = (Time.now.to_f * 1000).to_i.to_s
|
240
|
+
new_fbc = "fb." +
|
241
|
+
@sub_domain_index.to_s +
|
242
|
+
"." +
|
243
|
+
current_ms +
|
244
|
+
"." +
|
245
|
+
new_fbc_payload +
|
246
|
+
"." +
|
247
|
+
LANGUAGE_TOKEN
|
248
|
+
updated_cookie_setting = CookieSettings.new(
|
249
|
+
FBC_NAME, new_fbc, @etld_plus_one, DEFAULT_1PC_AGE)
|
250
|
+
return updated_cookie_setting
|
251
|
+
end
|
252
|
+
|
253
|
+
private def get_updated_fbp_cookie(existing_fbp = nil)
|
254
|
+
if existing_fbp != nil
|
255
|
+
return nil
|
256
|
+
end
|
257
|
+
new_fbp_payload = rand(0..2147483647).to_s
|
258
|
+
current_ms = (Time.now.to_f * 1000).to_i.to_s
|
259
|
+
new_fbp = "fb." +
|
260
|
+
@sub_domain_index.to_s +
|
261
|
+
"." +
|
262
|
+
current_ms +
|
263
|
+
"." +
|
264
|
+
new_fbp_payload +
|
265
|
+
"." +
|
266
|
+
LANGUAGE_TOKEN
|
267
|
+
updated_cookie_setting = CookieSettings.new(
|
268
|
+
FBP_NAME, new_fbp, @etld_plus_one, DEFAULT_1PC_AGE)
|
269
|
+
return updated_cookie_setting
|
270
|
+
end
|
271
|
+
end
|
metadata
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capi_param_builder_ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Facebook
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-04-25 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/capi_param_builder.rb
|
20
|
+
- lib/capi_param_builder/cookie_settings.rb
|
21
|
+
- lib/capi_param_builder/etld_plus_one_resolver.rb
|
22
|
+
- lib/capi_param_builder/fbc_param_configs.rb
|
23
|
+
homepage:
|
24
|
+
licenses:
|
25
|
+
- Nonstandard
|
26
|
+
metadata: {}
|
27
|
+
post_install_message:
|
28
|
+
rdoc_options: []
|
29
|
+
require_paths:
|
30
|
+
- lib
|
31
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '3.0'
|
36
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
requirements: []
|
42
|
+
rubygems_version: 3.5.22
|
43
|
+
signing_key:
|
44
|
+
specification_version: 4
|
45
|
+
summary: Parameter builder SDK for Conversion API events
|
46
|
+
test_files: []
|