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: []