posthog-ruby 3.8.1 → 3.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4400cef0c43dd5e79c18f38712b6f0033271f798ad8f97362338516ab491185a
4
- data.tar.gz: c789f72bcd04df210a74e79d9069e20a5a7f02b0b9b48eae344f58a33c348c8e
3
+ metadata.gz: 1388daa77d143c2822f15642b375b1e833a893d86677337d0d8bfc88875b364c
4
+ data.tar.gz: 2e71adb4fdc958bd8793515dbb3317652d9e811166678e7e38c57e57a85f3a08
5
5
  SHA512:
6
- metadata.gz: fc6f2f8cfd188881c9aec1ab5867bf555ebfc8fdf12f87e2fb086884e75968e268df8346b1463a563bb48529f6f7c6326366718885c6e6584aa140ff5aa15f87
7
- data.tar.gz: 7b6c516b339b0beaa8a0980d5e1d02027e826065f6bb28c85817344227cced2500b95e359bfe8955af952f553dc9b6cee958be8ffe2f52629d1bd1cf3df5e6f6
6
+ metadata.gz: a3ab6ee23fabf6375facb994ed3a16de3ec21ec53c88da0121fe3158d9b6552127d081124bb02870ca0b04d47bd2df4a79b7a95c45d8991095e2a4cbdfc63d09
7
+ data.tar.gz: ae04e209088f84ccb2760f0dc5fc3760cfb25e2ecd452201cf16b5432e4761acd63ef9471fc87cf88e21a1d16ce744191ea7ed6a445b2544fcf3e3a0f2f1040a
@@ -3,10 +3,13 @@
3
3
  require 'posthog/defaults'
4
4
 
5
5
  module PostHog
6
+ # Retry backoff policy used by the SDK transport.
7
+ #
8
+ # @api private
6
9
  class BackoffPolicy
7
10
  include PostHog::Defaults::BackoffPolicy
8
11
 
9
- # @param [Hash] opts
12
+ # @param opts [Hash]
10
13
  # @option opts [Numeric] :min_timeout_ms The minimum backoff timeout
11
14
  # @option opts [Numeric] :max_timeout_ms The maximum backoff timeout
12
15
  # @option opts [Numeric] :multiplier The value to multiply the current
@@ -14,6 +14,7 @@ require 'posthog/feature_flags'
14
14
  require 'posthog/feature_flag_evaluations'
15
15
  require 'posthog/send_feature_flags_options'
16
16
  require 'posthog/exception_capture'
17
+ require 'posthog/internal/context'
17
18
 
18
19
  module PostHog
19
20
  class Client
@@ -49,29 +50,28 @@ module PostHog
49
50
  end
50
51
  end
51
52
 
52
- # @param [Hash] opts
53
- # @option opts [String] :api_key Your project's api_key
54
- # @option opts [String] :personal_api_key Your personal API key
55
- # @option opts [FixNum] :max_queue_size Maximum number of calls to be
56
- # remain queued. Defaults to 10_000.
57
- # @option opts [Bool] :test_mode +true+ if messages should remain
58
- # queued for testing. Defaults to +false+.
59
- # @option opts [Bool] :sync_mode +true+ to send events synchronously
60
- # on the calling thread. Useful in forking environments like Sidekiq
61
- # and Resque. Defaults to +false+.
62
- # @option opts [Proc] :on_error Handles error calls from the API.
63
- # @option opts [String] :host Fully qualified hostname of the PostHog server. Defaults to `https://us.i.posthog.com`
64
- # @option opts [Integer] :feature_flags_polling_interval How often to poll for feature flag definition changes.
65
- # Measured in seconds, defaults to 30.
66
- # @option opts [Integer] :feature_flag_request_timeout_seconds How long to wait for feature flag evaluation.
67
- # Measured in seconds, defaults to 3.
68
- # @option opts [Proc] :before_send A block that receives the event hash and should return either a modified hash
69
- # to be sent to PostHog or nil to prevent the event from being sent. e.g. `before_send: ->(event) { event }`
70
- # @option opts [Bool] :disable_singleton_warning +true+ to suppress the warning when multiple clients
71
- # share the same API key. Use only when you intentionally need multiple clients. Defaults to +false+.
72
- # @option opts [Object] :flag_definition_cache_provider An object implementing the
73
- # {FlagDefinitionCacheProvider} interface for distributed flag definition caching.
74
- # EXPERIMENTAL: This API may change in future minor version bumps.
53
+ # @param opts [Hash] Client configuration.
54
+ # @option opts [String] :api_key Your project's API key. Required.
55
+ # @option opts [String, nil] :personal_api_key Your personal API key. Required for local feature flag evaluation.
56
+ # @option opts [String] :host Fully qualified hostname of the PostHog server. Defaults to `https://us.i.posthog.com`.
57
+ # @option opts [Integer] :max_queue_size Maximum number of calls to remain queued. Defaults to 10_000.
58
+ # @option opts [Integer] :batch_size Maximum number of events to send in one async batch.
59
+ # @option opts [Boolean] :test_mode +true+ if messages should remain queued for testing. Defaults to +false+.
60
+ # @option opts [Boolean] :sync_mode +true+ to send events synchronously on the calling thread. Useful in
61
+ # forking environments like Sidekiq and Resque. Defaults to +false+.
62
+ # @option opts [Proc] :on_error Callback invoked as `on_error.call(status, error)` for API or serialization errors.
63
+ # @option opts [Integer] :feature_flags_polling_interval How often to poll for feature flag definition changes,
64
+ # in seconds. Defaults to 30.
65
+ # @option opts [Integer] :feature_flag_request_timeout_seconds How long to wait for feature flag evaluation,
66
+ # in seconds. Defaults to 3.
67
+ # @option opts [Proc] :before_send A callback that receives the event hash and should return either a modified
68
+ # hash to be sent to PostHog or nil to prevent the event from being sent. e.g. `before_send: ->(event) { event }`.
69
+ # @option opts [Boolean] :disable_singleton_warning +true+ to suppress the warning when multiple clients share
70
+ # the same API key. Use only when you intentionally need multiple clients. Defaults to +false+.
71
+ # @option opts [Boolean] :skip_ssl_verification +true+ to disable SSL certificate verification for requests.
72
+ # Intended only for local development or custom deployments.
73
+ # @option opts [Object] :flag_definition_cache_provider An object implementing the {FlagDefinitionCacheProvider}
74
+ # interface for distributed flag definition caching. EXPERIMENTAL: This API may change in future minor versions.
75
75
  def initialize(opts = {})
76
76
  symbolize_keys!(opts)
77
77
 
@@ -142,7 +142,9 @@ module PostHog
142
142
  # Synchronously waits until the worker has cleared the queue.
143
143
  #
144
144
  # Use only for scripts which are not long-running, and will specifically
145
- # exit
145
+ # exit.
146
+ #
147
+ # @return [void]
146
148
  def flush
147
149
  if @sync_mode
148
150
  # Wait for any in-flight sync send to complete
@@ -158,7 +160,9 @@ module PostHog
158
160
 
159
161
  # Clears the queue without waiting.
160
162
  #
161
- # Use only in test mode
163
+ # Use only in test mode.
164
+ #
165
+ # @return [void]
162
166
  def clear
163
167
  @queue.clear
164
168
  end
@@ -175,8 +179,10 @@ module PostHog
175
179
  #
176
180
  # @option attrs [String] :event Event name
177
181
  # @option attrs [Hash] :properties Event properties (optional)
178
- # @option attrs [Bool, Hash, SendFeatureFlagsOptions] :send_feature_flags
179
- # Whether to send feature flags with this event, or configuration for feature flag evaluation (optional)
182
+ # @option attrs [Hash] :groups Group analytics mapping from group type to group key (optional)
183
+ # @option attrs [Boolean, Hash, SendFeatureFlagsOptions] :send_feature_flags
184
+ # Deprecated. Whether to send feature flags with this event, or configuration for feature flag evaluation
185
+ # (optional)
180
186
  # @option attrs [PostHog::FeatureFlagEvaluations] :flags A snapshot returned by
181
187
  # {#evaluate_flags}. When present, `$feature/<key>` and `$active_feature_flags` are
182
188
  # attached from the snapshot without making an additional /flags request, and this
@@ -185,9 +191,14 @@ module PostHog
185
191
  # events in PostHog are deduplicated by the
186
192
  # combination of teamId, timestamp date,
187
193
  # event name, distinct id, and UUID
194
+ # @note If `:distinct_id` is omitted, request/context distinct_id is used when
195
+ # available; otherwise a UUID is generated and the event is marked personless
196
+ # with `$process_person_profile: false`.
197
+ # @return [Boolean] Whether the event was queued or sent.
188
198
  # @macro common_attrs
189
199
  def capture(attrs)
190
200
  symbolize_keys! attrs
201
+ enrich_capture_attrs_with_context(attrs)
191
202
 
192
203
  # Precedence: an explicit `flags` snapshot always wins, regardless of
193
204
  # `send_feature_flags`. The snapshot guarantees the event carries the same
@@ -260,22 +271,20 @@ module PostHog
260
271
  # Captures an exception as an event
261
272
  #
262
273
  # @param [Exception, String, Object] exception The exception to capture, a string message, or exception-like object
263
- # @param [String] distinct_id The ID for the user (optional, defaults to a generated UUID)
274
+ # @param [String] distinct_id The ID for the user (optional, defaults to request/context distinct_id
275
+ # or a generated UUID)
264
276
  # @param [Hash] additional_properties Additional properties to include with the exception event (optional)
265
- # @param [PostHog::FeatureFlagEvaluations] flags A snapshot returned by {#evaluate_flags}.
277
+ # @param flags [PostHog::FeatureFlagEvaluations, nil] A snapshot returned by {#evaluate_flags}.
266
278
  # Forwarded to the inner {#capture} call so the captured `$exception` event carries the
267
279
  # same `$feature/<key>` and `$active_feature_flags` properties as the snapshot.
280
+ # @return [Boolean, nil] Whether the exception event was queued or sent, or nil if the input could not be parsed.
268
281
  def capture_exception(exception, distinct_id = nil, additional_properties = {}, flags: nil)
269
282
  exception_info = ExceptionCapture.build_parsed_exception(exception)
270
283
 
271
284
  return if exception_info.nil?
272
285
 
273
- no_distinct_id_was_provided = distinct_id.nil?
274
- distinct_id ||= SecureRandom.uuid
275
-
276
286
  properties = { '$exception_list' => [exception_info] }
277
287
  properties.merge!(additional_properties) if additional_properties && !additional_properties.empty?
278
- properties['$process_person_profile'] = false if no_distinct_id_was_provided
279
288
 
280
289
  event_data = {
281
290
  distinct_id: distinct_id,
@@ -293,6 +302,7 @@ module PostHog
293
302
  # @param [Hash] attrs
294
303
  #
295
304
  # @option attrs [Hash] :properties User properties (optional)
305
+ # @return [Boolean] Whether the identify event was queued or sent.
296
306
  # @macro common_attrs
297
307
  def identify(attrs)
298
308
  symbolize_keys! attrs
@@ -307,6 +317,7 @@ module PostHog
307
317
  # @option attrs [String] :group_key Group key
308
318
  # @option attrs [Hash] :properties Group properties (optional)
309
319
  # @option attrs [String] :distinct_id Distinct ID (optional)
320
+ # @return [Boolean] Whether the group identify event was queued or sent.
310
321
  # @macro common_attrs
311
322
  def group_identify(attrs)
312
323
  symbolize_keys! attrs
@@ -318,23 +329,32 @@ module PostHog
318
329
  # @param [Hash] attrs
319
330
  #
320
331
  # @option attrs [String] :alias The alias to give the distinct id
332
+ # @return [Boolean] Whether the alias event was queued or sent.
321
333
  # @macro common_attrs
322
334
  def alias(attrs)
323
335
  symbolize_keys! attrs
324
336
  enqueue(FieldParser.parse_for_alias(attrs))
325
337
  end
326
338
 
327
- # @return [Hash] pops the last message from the queue
339
+ # @return [Hash] Pops the last message from the queue. Intended for test mode.
328
340
  def dequeue_last_message
329
341
  @queue.pop
330
342
  end
331
343
 
332
- # @return [Fixnum] number of messages in the queue
344
+ # @return [Integer] Number of messages in the queue. Intended for test mode.
333
345
  def queued_messages
334
346
  @queue.length
335
347
  end
336
348
 
337
- # @deprecated Use {#evaluate_flags} and {FeatureFlagEvaluations#is_enabled} instead.
349
+ # @deprecated Use {#evaluate_flags} and {FeatureFlagEvaluations#enabled?} instead.
350
+ # @param flag_key [String, Symbol] The unique key of the feature flag.
351
+ # @param distinct_id [String] The distinct id of the user.
352
+ # @param groups [Hash] Group analytics mapping from group type to group key.
353
+ # @param person_properties [Hash] Properties to use when evaluating the user locally or remotely.
354
+ # @param group_properties [Hash] Properties to use when evaluating groups locally or remotely.
355
+ # @param only_evaluate_locally [Boolean] Skip the remote /flags call.
356
+ # @param send_feature_flag_events [Boolean] Whether to capture `$feature_flag_called` for this access.
357
+ # @return [Boolean, nil] Whether the flag is enabled, or nil when the flag could not be evaluated.
338
358
  # TODO: In future version, rename to `feature_flag_enabled?`
339
359
  def is_feature_enabled( # rubocop:disable Naming/PredicateName
340
360
  flag_key,
@@ -364,8 +384,8 @@ module PostHog
364
384
  !!response
365
385
  end
366
386
 
367
- # @param [String, Symbol] flag_key The unique flag key of the feature flag
368
- # @return [String] The decrypted value of the feature flag payload
387
+ # @param flag_key [String, Symbol] The unique flag key of the remote config feature flag.
388
+ # @return [Hash] The parsed remote config payload response.
369
389
  def get_remote_config_payload(flag_key)
370
390
  @feature_flags_poller.get_remote_config_payload(flag_key.to_s)
371
391
  end
@@ -377,8 +397,10 @@ module PostHog
377
397
  # @param [Hash] groups
378
398
  # @param [Hash] person_properties key-value pairs of properties to associate with the user.
379
399
  # @param [Hash] group_properties
400
+ # @param only_evaluate_locally [Boolean] Skip the remote /flags call.
401
+ # @param send_feature_flag_events [Boolean] Whether to capture `$feature_flag_called` for this access.
380
402
  #
381
- # @return [String, nil] The value of the feature flag
403
+ # @return [String, Boolean, nil] The value of the feature flag
382
404
  #
383
405
  # The provided properties are used to calculate feature flags locally, if possible.
384
406
  #
@@ -418,6 +440,14 @@ module PostHog
418
440
 
419
441
  # @deprecated Use {#evaluate_flags} and {FeatureFlagEvaluations#get_flag} /
420
442
  # {FeatureFlagEvaluations#get_flag_payload} instead.
443
+ # @param key [String, Symbol] The unique key of the feature flag.
444
+ # @param distinct_id [String] The distinct id of the user.
445
+ # @param groups [Hash] Group analytics mapping from group type to group key.
446
+ # @param person_properties [Hash] Properties to use when evaluating the user locally or remotely.
447
+ # @param group_properties [Hash] Properties to use when evaluating groups locally or remotely.
448
+ # @param only_evaluate_locally [Boolean] Skip the remote /flags call.
449
+ # @param send_feature_flag_events [Boolean] Whether to capture `$feature_flag_called` for this access.
450
+ # @return [PostHog::FeatureFlagResult, nil]
421
451
  def get_feature_flag_result(
422
452
  key,
423
453
  distinct_id,
@@ -454,7 +484,8 @@ module PostHog
454
484
  # @param [Hash] person_properties key-value pairs of properties to associate with the user
455
485
  # @param [Hash] group_properties
456
486
  # @param [Boolean] only_evaluate_locally Skip the remote /flags call entirely
457
- # @param [Boolean] disable_geoip Stamped on captured access events
487
+ # @param [Boolean, nil] disable_geoip When true, disables GeoIP lookup for remote evaluation and stamps captured
488
+ # access events.
458
489
  # @param [Array<String, Symbol>] flag_keys When set, scopes the underlying /flags
459
490
  # request to only these flag keys (sent as `flag_keys_to_evaluate`).
460
491
  # Distinct from {FeatureFlagEvaluations#only}, which filters the
@@ -578,6 +609,7 @@ module PostHog
578
609
  # @param [Hash] groups
579
610
  # @param [Hash] person_properties key-value pairs of properties to associate with the user.
580
611
  # @param [Hash] group_properties
612
+ # @param only_evaluate_locally [Boolean] Skip the remote /flags call.
581
613
  #
582
614
  # @return [Hash] String (not symbol) key value pairs of flag and their values
583
615
  def get_all_flags(
@@ -600,11 +632,12 @@ module PostHog
600
632
  #
601
633
  # @param [String, Symbol] key The key of the feature flag
602
634
  # @param [String] distinct_id The distinct id of the user
603
- # @option [String or boolean] match_value The value of the feature flag to be matched
604
- # @option [Hash] groups
605
- # @option [Hash] person_properties key-value pairs of properties to associate with the user.
606
- # @option [Hash] group_properties
607
- # @option [Boolean] only_evaluate_locally
635
+ # @param match_value [String, Boolean, nil] The value of the feature flag to be matched
636
+ # @param groups [Hash]
637
+ # @param person_properties [Hash] key-value pairs of properties to associate with the user.
638
+ # @param group_properties [Hash]
639
+ # @param only_evaluate_locally [Boolean]
640
+ # @return [Object, nil] The parsed payload for the matched flag value.
608
641
  #
609
642
  # @deprecated Use {#evaluate_flags} and {FeatureFlagEvaluations#get_flag_payload} instead.
610
643
  def get_feature_flag_payload(
@@ -637,10 +670,10 @@ module PostHog
637
670
  # featureFlagPayloads: A hash of feature flag payloads
638
671
  #
639
672
  # @param [String] distinct_id The distinct id of the user
640
- # @option [Hash] groups
641
- # @option [Hash] person_properties key-value pairs of properties to associate with the user.
642
- # @option [Hash] group_properties
643
- # @option [Boolean] only_evaluate_locally
673
+ # @param groups [Hash]
674
+ # @param person_properties [Hash] key-value pairs of properties to associate with the user.
675
+ # @param group_properties [Hash]
676
+ # @param only_evaluate_locally [Boolean] Skip the remote /flags call.
644
677
  #
645
678
  def get_all_flags_and_payloads(
646
679
  distinct_id,
@@ -662,6 +695,9 @@ module PostHog
662
695
  response
663
696
  end
664
697
 
698
+ # Reload locally cached feature flag definitions.
699
+ #
700
+ # @return [void]
665
701
  def reload_feature_flags
666
702
  unless @personal_api_key
667
703
  logger.error(
@@ -672,6 +708,9 @@ module PostHog
672
708
  @feature_flags_poller.load_feature_flags(true)
673
709
  end
674
710
 
711
+ # Flush pending events and stop background resources.
712
+ #
713
+ # @return [void]
675
714
  def shutdown
676
715
  self.class._decrement_instance_count(@api_key) if @api_key
677
716
  @feature_flags_poller.shutdown_poller
@@ -685,6 +724,39 @@ module PostHog
685
724
 
686
725
  private
687
726
 
727
+ def enrich_capture_attrs_with_context(attrs)
728
+ context = Internal::Context.current
729
+ explicit_properties = attrs[:properties]
730
+ properties_are_hash = explicit_properties.nil? || explicit_properties.is_a?(Hash)
731
+ context_properties = context&.properties || {}
732
+ if properties_are_hash
733
+ attrs[:properties] = Internal::Context.merge_properties(context_properties, explicit_properties || {})
734
+ end
735
+
736
+ return if present_id?(attrs[:distinct_id])
737
+
738
+ if present_id?(context&.distinct_id)
739
+ attrs[:distinct_id] = context.distinct_id
740
+ return
741
+ end
742
+
743
+ attrs[:distinct_id] = SecureRandom.uuid
744
+ return unless properties_are_hash
745
+ return if property_key?(explicit_properties, '$process_person_profile')
746
+
747
+ attrs[:properties]['$process_person_profile'] = false
748
+ end
749
+
750
+ def present_id?(value)
751
+ !(value.nil? || (value.is_a?(String) && value.empty?))
752
+ end
753
+
754
+ def property_key?(properties, key)
755
+ return false unless properties.is_a?(Hash)
756
+
757
+ properties.key?(key) || properties.key?(key.to_sym)
758
+ end
759
+
688
760
  # Shared by the legacy single-flag path ({#get_feature_flag_result}) and the
689
761
  # snapshot's access-recording. Owns dedup-key construction, the
690
762
  # per-distinct_id sent-flags cache, and the `$feature_flag_called` capture call.
@@ -11,6 +11,9 @@
11
11
  # 💖 open source (under MIT License)
12
12
 
13
13
  module PostHog
14
+ # Builds PostHog exception payloads from Ruby exception objects.
15
+ #
16
+ # @api private
14
17
  module ExceptionCapture
15
18
  RUBY_INPUT_FORMAT = /
16
19
  ^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
@@ -18,6 +21,8 @@ module PostHog
18
21
  (?: :in\s('|`)(?:([\w:]+)\#)?([^']+)')?$
19
22
  /x
20
23
 
24
+ # @param value [Exception, String, Object] Exception input to parse.
25
+ # @return [Hash, nil] Parsed exception payload, or nil when the input is unsupported.
21
26
  def self.build_parsed_exception(value)
22
27
  title, message, backtrace = coerce_exception_input(value)
23
28
  return nil if title.nil?
@@ -25,6 +30,10 @@ module PostHog
25
30
  build_single_exception_from_data(title, message, backtrace)
26
31
  end
27
32
 
33
+ # @param title [String]
34
+ # @param message [String, nil]
35
+ # @param backtrace [Array<String>, nil]
36
+ # @return [Hash]
28
37
  def self.build_single_exception_from_data(title, message, backtrace)
29
38
  {
30
39
  'type' => title,
@@ -37,6 +46,8 @@ module PostHog
37
46
  }
38
47
  end
39
48
 
49
+ # @param backtrace [Array<String>, nil]
50
+ # @return [Hash, nil]
40
51
  def self.build_stacktrace(backtrace)
41
52
  return nil unless backtrace && !backtrace.empty?
42
53
 
@@ -50,6 +61,8 @@ module PostHog
50
61
  }
51
62
  end
52
63
 
64
+ # @param line [String]
65
+ # @return [Hash, nil]
53
66
  def self.parse_backtrace_line(line)
54
67
  match = line.match(RUBY_INPUT_FORMAT)
55
68
  return nil unless match
@@ -72,6 +85,8 @@ module PostHog
72
85
  frame
73
86
  end
74
87
 
88
+ # @param path [String]
89
+ # @return [Boolean]
75
90
  def self.gem_path?(path)
76
91
  path.include?('/gems/') ||
77
92
  path.include?('/ruby/') ||
@@ -79,6 +94,11 @@ module PostHog
79
94
  path.include?('/.rvm/')
80
95
  end
81
96
 
97
+ # @param frame [Hash]
98
+ # @param file_path [String]
99
+ # @param lineno [Integer]
100
+ # @param context_size [Integer]
101
+ # @return [void]
82
102
  def self.add_context_lines(frame, file_path, lineno, context_size = 5)
83
103
  lines = File.readlines(file_path)
84
104
  return if lines.empty?
@@ -97,6 +117,8 @@ module PostHog
97
117
  # Silently ignore file read errors
98
118
  end
99
119
 
120
+ # @param value [Exception, String, Object]
121
+ # @return [Array] Three-item array of title, message, and backtrace.
100
122
  def self.coerce_exception_input(value)
101
123
  if value.is_a?(String)
102
124
  title = 'Error'
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Represents a feature flag returned by /flags v2
4
3
  module PostHog
4
+ # Represents a feature flag returned by /flags v2.
5
+ #
6
+ # @api private
5
7
  class FeatureFlag
6
8
  attr_reader :key, :enabled, :variant, :reason, :metadata, :failed
7
9
 
10
+ # @param json [Hash] Raw feature flag data returned by /flags.
8
11
  def initialize(json)
9
12
  json.transform_keys!(&:to_s)
10
13
  @key = json['key']
@@ -15,15 +18,21 @@ module PostHog
15
18
  @failed = json['failed']
16
19
  end
17
20
 
21
+ # @return [String, Boolean] The variant value when present, otherwise the enabled status.
18
22
  # TODO: Rename to `value` in future version
19
23
  def get_value # rubocop:disable Naming/AccessorMethodName
20
24
  @variant || @enabled
21
25
  end
22
26
 
27
+ # @return [Object, nil] The flag payload from metadata.
23
28
  def payload
24
29
  @metadata&.payload
25
30
  end
26
31
 
32
+ # @param key [String, Symbol] The feature flag key.
33
+ # @param value [String, Boolean] The feature flag value.
34
+ # @param payload [Object, nil] The feature flag payload.
35
+ # @return [PostHog::FeatureFlag]
27
36
  def self.from_value_and_payload(key, value, payload)
28
37
  new({
29
38
  'key' => key,
@@ -40,10 +49,13 @@ module PostHog
40
49
  end
41
50
  end
42
51
 
43
- # Represents the reason why a flag was enabled/disabled
52
+ # Represents the reason why a flag was enabled/disabled.
53
+ #
54
+ # @api private
44
55
  class EvaluationReason
45
56
  attr_reader :code, :description, :condition_index
46
57
 
58
+ # @param json [Hash] Raw reason data returned by /flags.
47
59
  def initialize(json)
48
60
  json.transform_keys!(&:to_s)
49
61
  @code = json['code']
@@ -52,10 +64,13 @@ module PostHog
52
64
  end
53
65
  end
54
66
 
55
- # Represents metadata about a feature flag
67
+ # Represents metadata about a feature flag.
68
+ #
69
+ # @api private
56
70
  class FeatureFlagMetadata
57
71
  attr_reader :id, :version, :payload, :description
58
72
 
73
+ # @param json [Hash] Raw metadata returned by /flags.
59
74
  def initialize(json)
60
75
  json.transform_keys!(&:to_s)
61
76
  @id = json['id']
@@ -17,6 +17,7 @@ module PostHog
17
17
  #
18
18
  # For API errors with status codes, use the api_error() method which returns
19
19
  # a string like "api_error_500".
20
+ # @api private
20
21
  class FeatureFlagError
21
22
  ERRORS_WHILE_COMPUTING = 'errors_while_computing_flags'
22
23
  FLAG_MISSING = 'flag_missing'
@@ -4,7 +4,7 @@ require 'set'
4
4
 
5
5
  module PostHog
6
6
  # A snapshot of feature flag evaluations for one distinct_id, returned by
7
- # PostHog::Client#evaluate_flags. Calls to {#is_enabled} / {#get_flag} fire the
7
+ # PostHog::Client#evaluate_flags. Calls to {#enabled?} / {#get_flag} fire the
8
8
  # `$feature_flag_called` event (deduped through the existing per-distinct_id
9
9
  # cache); {#get_flag_payload} does not. Pass the snapshot to `capture(flags:)`
10
10
  # to attach `$feature/<key>` and `$active_feature_flags` without a second
@@ -19,8 +19,32 @@ module PostHog
19
19
 
20
20
  Host = Struct.new(:capture_flag_called_event_if_needed, :log_warning, keyword_init: true)
21
21
 
22
- attr_reader :distinct_id, :groups, :request_id, :evaluated_at, :flag_definitions_loaded_at
22
+ # @return [String] The distinct id these evaluations belong to.
23
+ attr_reader :distinct_id
23
24
 
25
+ # @return [Hash, nil] Group analytics mapping used for evaluation.
26
+ attr_reader :groups
27
+
28
+ # @return [String, nil] Request id returned by the /flags endpoint.
29
+ attr_reader :request_id
30
+
31
+ # @return [String, nil] Evaluation timestamp returned by the /flags endpoint.
32
+ attr_reader :evaluated_at
33
+
34
+ # @return [Time, nil] When local flag definitions were loaded.
35
+ attr_reader :flag_definitions_loaded_at
36
+
37
+ # @param host [Host, nil] Internal host callbacks used to record flag access events.
38
+ # @param distinct_id [String, nil] The distinct id these evaluations belong to.
39
+ # @param flags [Hash] Evaluated flags keyed by flag key.
40
+ # @param groups [Hash, nil] Group analytics mapping from group type to group key.
41
+ # @param disable_geoip [Boolean, nil] Whether GeoIP was disabled during evaluation.
42
+ # @param request_id [String, nil] The request id returned by the /flags endpoint.
43
+ # @param evaluated_at [String, nil] The evaluation timestamp returned by the /flags endpoint.
44
+ # @param flag_definitions_loaded_at [Time, nil] When local flag definitions were loaded.
45
+ # @param errors_while_computing [Boolean] Whether the server reported errors while computing flags.
46
+ # @param quota_limited [Boolean] Whether feature flag evaluation was quota limited.
47
+ # @param accessed [Array<String>, Set<String>, nil] Flag keys already accessed by this snapshot.
24
48
  def initialize(
25
49
  host: nil,
26
50
  distinct_id: nil,
@@ -47,10 +71,13 @@ module PostHog
47
71
  @accessed = Set.new(accessed || [])
48
72
  end
49
73
 
74
+ # @return [Array<String>] The evaluated flag keys in this snapshot.
50
75
  def keys
51
76
  @flags.keys
52
77
  end
53
78
 
79
+ # @param key [String, Symbol] The feature flag key.
80
+ # @return [Boolean] true when the flag is enabled, false when disabled or missing.
54
81
  def enabled?(key)
55
82
  key = key.to_s
56
83
  flag = @flags[key]
@@ -58,6 +85,9 @@ module PostHog
58
85
  flag&.enabled ? true : false
59
86
  end
60
87
 
88
+ # @param key [String, Symbol] The feature flag key.
89
+ # @return [String, Boolean, nil] Variant string for multivariate flags, true/false for boolean flags,
90
+ # or nil when the flag was not returned by the evaluation.
61
91
  def get_flag(key)
62
92
  key = key.to_s
63
93
  flag = @flags[key]
@@ -65,6 +95,8 @@ module PostHog
65
95
  _flag_value(flag)
66
96
  end
67
97
 
98
+ # @param key [String, Symbol] The feature flag key.
99
+ # @return [Object, nil] The parsed payload for the flag, if any.
68
100
  def get_flag_payload(key)
69
101
  flag = @flags[key.to_s]
70
102
  flag&.payload
@@ -73,10 +105,14 @@ module PostHog
73
105
  # Order-dependent: if nothing has been accessed yet, the returned snapshot is
74
106
  # empty. The method honors its name — pre-access flags before calling this if
75
107
  # you want a populated result.
108
+ # @return [PostHog::FeatureFlagEvaluations] A snapshot containing only flags already accessed with
109
+ # {#enabled?} or {#get_flag}.
76
110
  def only_accessed
77
111
  _clone_with(@flags.slice(*@accessed))
78
112
  end
79
113
 
114
+ # @param keys [Array<String, Symbol>, String, Symbol] Flag keys to keep in the returned snapshot.
115
+ # @return [PostHog::FeatureFlagEvaluations] A snapshot containing only the requested keys that exist.
80
116
  def only(keys)
81
117
  keys = Array(keys).map(&:to_s)
82
118
  missing = keys.reject { |k| @flags.key?(k) }
@@ -92,6 +128,9 @@ module PostHog
92
128
 
93
129
  # Builds the `$feature/<key>` and `$active_feature_flags` properties for a
94
130
  # captured event. Called from PostHog::Client#capture when `flags:` is set.
131
+ #
132
+ # @api private
133
+ # @return [Hash]
95
134
  def _get_event_properties
96
135
  properties = {}
97
136
  active = []
@@ -6,8 +6,19 @@ module PostHog
6
6
  # Represents the result of a feature flag evaluation
7
7
  # containing both the flag value and payload
8
8
  class FeatureFlagResult
9
- attr_reader :key, :variant, :payload
9
+ # @return [String, Symbol] The feature flag key.
10
+ attr_reader :key
10
11
 
12
+ # @return [String, nil] The variant key for multivariate flags.
13
+ attr_reader :variant
14
+
15
+ # @return [Object, nil] The parsed feature flag payload.
16
+ attr_reader :payload
17
+
18
+ # @param key [String, Symbol] The feature flag key.
19
+ # @param enabled [Boolean] Whether the feature flag is enabled.
20
+ # @param variant [String, nil] The variant key for multivariate flags.
21
+ # @param payload [Object, nil] The parsed feature flag payload.
11
22
  def initialize(key:, enabled:, variant: nil, payload: nil)
12
23
  @key = key
13
24
  @enabled = enabled
@@ -15,18 +26,27 @@ module PostHog
15
26
  @payload = payload
16
27
  end
17
28
 
18
- # Returns the effective value of the feature flag
19
- # variant if present, otherwise enabled status
29
+ # Returns the effective value of the feature flag: variant if present,
30
+ # otherwise enabled status.
31
+ #
32
+ # @return [String, Boolean]
20
33
  def value
21
34
  @variant || @enabled
22
35
  end
23
36
 
24
- # Returns whether or not the feature flag evaluated as enabled
37
+ # Returns whether or not the feature flag evaluated as enabled.
38
+ #
39
+ # @return [Boolean]
25
40
  def enabled?
26
41
  @enabled
27
42
  end
28
43
 
29
- # Factory method to create from flag value and payload
44
+ # Factory method to create from flag value and payload.
45
+ #
46
+ # @param key [String, Symbol] The feature flag key.
47
+ # @param value [String, Boolean, nil] The raw feature flag value.
48
+ # @param payload [Object, String, nil] The raw or JSON-encoded feature flag payload.
49
+ # @return [PostHog::FeatureFlagResult, nil]
30
50
  def self.from_value_and_payload(key, value, payload)
31
51
  return nil if value.nil?
32
52
 
@@ -43,6 +63,9 @@ module PostHog
43
63
  # returned when the body is not valid JSON); already-deserialized values
44
64
  # pass through. Public so {FeatureFlagEvaluations} can normalize payloads
45
65
  # the same way {FeatureFlagResult} does.
66
+ #
67
+ # @param payload [Object, String, nil] The raw payload value.
68
+ # @return [Object, nil] The parsed payload.
46
69
  def self.parse_payload(payload)
47
70
  return nil if payload.nil?
48
71
  return payload unless payload.is_a?(String)
@@ -20,10 +20,20 @@ module PostHog
20
20
  class RequiresServerEvaluation < StandardError
21
21
  end
22
22
 
23
+ # Polls and evaluates feature flag definitions for {PostHog::Client}.
24
+ #
25
+ # @api private
23
26
  class FeatureFlagsPoller
24
27
  include PostHog::Logging
25
28
  include PostHog::Utils
26
29
 
30
+ # @param polling_interval [Integer, nil] Seconds between local feature flag definition polls.
31
+ # @param personal_api_key [String, nil] Personal API key used to fetch local evaluation definitions.
32
+ # @param project_api_key [String] Project API key.
33
+ # @param host [String] PostHog API host URL.
34
+ # @param feature_flag_request_timeout_seconds [Integer] Timeout for feature flag requests.
35
+ # @param on_error [Proc, nil] Callback invoked as `on_error.call(status, error)`.
36
+ # @param flag_definition_cache_provider [Object, nil] Optional {FlagDefinitionCacheProvider} implementation.
27
37
  def initialize(
28
38
  polling_interval,
29
39
  personal_api_key,
@@ -446,6 +456,16 @@ module PostHog
446
456
  parsed_dt
447
457
  end
448
458
 
459
+ # Parse a single semver numeric identifier, rejecting empty, non-digit, or
460
+ # leading-zero values per semver 2.0.0 §2.
461
+ def self.parse_semver_numeric(part)
462
+ raise InconclusiveMatchError, 'Invalid semver format' if part.nil? || part.empty? || part !~ /^\d+$/
463
+ # Semver 2.0.0 §2: numeric identifiers MUST NOT include leading zeros.
464
+ raise InconclusiveMatchError, 'Invalid semver format' if part.length > 1 && part[0] == '0'
465
+
466
+ part.to_i
467
+ end
468
+
449
469
  # Parse a semver string into a comparable [major, minor, patch] integer array.
450
470
  # Handles v-prefix, whitespace, pre-release suffixes. Defaults missing components to 0.
451
471
  def self.parse_semver(value)
@@ -461,14 +481,9 @@ module PostHog
461
481
 
462
482
  raise InconclusiveMatchError, 'Invalid semver format' if parts.empty? || parts[0].to_s.empty?
463
483
 
464
- # Check for leading dot or non-numeric parts
465
- parts.each do |part|
466
- raise InconclusiveMatchError, 'Invalid semver format' if part.empty? || part !~ /^\d+$/
467
- end
468
-
469
- major = parts[0].to_i
470
- minor = parts.length > 1 ? parts[1].to_i : 0
471
- patch = parts.length > 2 ? parts[2].to_i : 0
484
+ major = parse_semver_numeric(parts[0])
485
+ minor = parts.length > 1 ? parse_semver_numeric(parts[1]) : 0
486
+ patch = parts.length > 2 ? parse_semver_numeric(parts[2]) : 0
472
487
 
473
488
  [major, minor, patch]
474
489
  end
@@ -525,20 +540,22 @@ module PostHog
525
540
 
526
541
  raise InconclusiveMatchError, 'Invalid semver wildcard format' if parts.empty?
527
542
 
528
- parts.each do |part|
529
- raise InconclusiveMatchError, 'Invalid semver wildcard format' if part !~ /^\d+$/
543
+ numeric = parts.map do |part|
544
+ parse_semver_numeric(part)
545
+ rescue InconclusiveMatchError
546
+ raise InconclusiveMatchError, 'Invalid semver wildcard format'
530
547
  end
531
548
 
532
- major = parts[0].to_i
533
- case parts.length
549
+ major = numeric[0]
550
+ case numeric.length
534
551
  when 1
535
552
  [[major, 0, 0], [major + 1, 0, 0]]
536
553
  when 2
537
- minor = parts[1].to_i
554
+ minor = numeric[1]
538
555
  [[major, minor, 0], [major, minor + 1, 0]]
539
556
  else
540
- minor = parts[1].to_i
541
- patch = parts[2].to_i
557
+ minor = numeric[1]
558
+ patch = numeric[2]
542
559
  [[major, minor, patch], [major, minor, patch + 1]]
543
560
  end
544
561
  end
@@ -3,6 +3,9 @@
3
3
  require 'posthog/logging'
4
4
 
5
5
  module PostHog
6
+ # Converts public SDK method arguments into PostHog API event payloads.
7
+ #
8
+ # @api private
6
9
  class FieldParser
7
10
  class << self
8
11
  include PostHog::Utils
@@ -14,6 +17,8 @@ module PostHog
14
17
  # - "properties"
15
18
  # - "groups"
16
19
  # - "uuid"
20
+ # @param fields [Hash]
21
+ # @return [Hash]
17
22
  def parse_for_capture(fields)
18
23
  common = parse_common_fields(fields)
19
24
 
@@ -45,6 +50,8 @@ module PostHog
45
50
  # In addition to the common fields, identify accepts:
46
51
  #
47
52
  # - "properties"
53
+ # @param fields [Hash]
54
+ # @return [Hash]
48
55
  def parse_for_identify(fields)
49
56
  common = parse_common_fields(fields)
50
57
 
@@ -63,6 +70,8 @@ module PostHog
63
70
  )
64
71
  end
65
72
 
73
+ # @param fields [Hash]
74
+ # @return [Hash]
66
75
  def parse_for_group_identify(fields)
67
76
  properties = fields[:properties] || {}
68
77
  group_type = fields[:group_type]
@@ -92,6 +101,8 @@ module PostHog
92
101
  # In addition to the common fields, alias accepts:
93
102
  #
94
103
  # - "alias"
104
+ # @param fields [Hash]
105
+ # @return [Hash]
95
106
  def parse_for_alias(fields)
96
107
  common = parse_common_fields(fields)
97
108
 
@@ -14,26 +14,31 @@ module PostHog
14
14
  #
15
15
  # == Required Methods
16
16
  #
17
- # [+flag_definitions+]
17
+ # @!method flag_definitions
18
18
  # Retrieve cached flag definitions. Return a Hash with +:flags+,
19
19
  # +:group_type_mapping+, and +:cohorts+ keys, or +nil+ if the cache
20
20
  # is empty. Returning +nil+ triggers an API fetch when no flags are
21
21
  # loaded yet (emergency fallback).
22
+ # @return [Hash, nil]
22
23
  #
23
- # [+should_fetch_flag_definitions?+]
24
+ # @!method should_fetch_flag_definitions?
24
25
  # Return +true+ if this instance should fetch new definitions from the
25
26
  # API, +false+ to read from cache instead. Use for distributed lock
26
27
  # coordination so only one worker fetches at a time.
28
+ # @return [Boolean]
27
29
  #
28
- # [+on_flag_definitions_received(data)+]
30
+ # @!method on_flag_definitions_received(data)
29
31
  # Called after successfully fetching new definitions from the API.
30
32
  # +data+ is a Hash with +:flags+, +:group_type_mapping+, and +:cohorts+
31
33
  # keys (plain Ruby types, not Concurrent:: wrappers). Store it in your
32
34
  # external cache.
35
+ # @param data [Hash]
36
+ # @return [void]
33
37
  #
34
- # [+shutdown+]
38
+ # @!method shutdown
35
39
  # Called when the PostHog client shuts down. Release any distributed
36
40
  # locks and clean up resources.
41
+ # @return [void]
37
42
  #
38
43
  # == Error Handling
39
44
  #
@@ -66,6 +71,7 @@ module PostHog
66
71
  #
67
72
  # @param provider [Object] the cache provider to validate
68
73
  # @raise [ArgumentError] if any required methods are missing
74
+ # @return [void]
69
75
  def self.validate!(provider)
70
76
  missing = REQUIRED_METHODS.reject { |m| provider.respond_to?(m) }
71
77
  return if missing.empty?
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PostHog
4
+ module Internal
5
+ # Internal request/fiber-local context applied to capture calls.
6
+ # Uses Rails' isolated execution state when available, otherwise falls back
7
+ # to thread-local storage in the core SDK.
8
+ #
9
+ # This is intentionally not exposed as a public SDK API in Ruby yet. It exists
10
+ # to let framework integrations such as posthog-rails propagate request-scoped
11
+ # tracing headers to regular capture and exception events without making the
12
+ # server-side SDK globally stateful per user.
13
+ class Context
14
+ STORAGE_KEY = :posthog_context
15
+
16
+ attr_reader :distinct_id, :session_id, :properties
17
+
18
+ def initialize(distinct_id: nil, session_id: nil, properties: {})
19
+ @distinct_id = distinct_id
20
+ @session_id = session_id
21
+ @properties = properties ? properties.dup : {}
22
+ apply_session_property!
23
+ end
24
+
25
+ def self.current
26
+ if defined?(ActiveSupport::IsolatedExecutionState)
27
+ ActiveSupport::IsolatedExecutionState[STORAGE_KEY]
28
+ else
29
+ Thread.current[STORAGE_KEY]
30
+ end
31
+ end
32
+
33
+ def self.current=(context)
34
+ if defined?(ActiveSupport::IsolatedExecutionState)
35
+ ActiveSupport::IsolatedExecutionState[STORAGE_KEY] = context
36
+ else
37
+ Thread.current[STORAGE_KEY] = context
38
+ end
39
+ end
40
+
41
+ def self.with_context(data = nil, fresh: false, **kwargs)
42
+ previous_context = current
43
+ raise ArgumentError, 'with_context requires a block' unless block_given?
44
+
45
+ self.current = resolve(merge_data_and_kwargs(data, kwargs), previous_context, fresh: fresh)
46
+ yield
47
+ ensure
48
+ self.current = previous_context
49
+ end
50
+
51
+ def self.resolve(data, parent, fresh: false)
52
+ data = normalize_data(data)
53
+
54
+ parent_properties = fresh || parent.nil? ? {} : parent.properties
55
+ properties = merge_properties(parent_properties, data[:properties] || {})
56
+ if data[:session_id] && !session_property_key?(data[:properties])
57
+ properties.delete('$session_id')
58
+ properties.delete(:$session_id)
59
+ end
60
+
61
+ new(
62
+ distinct_id: data[:distinct_id] || (fresh || parent.nil? ? nil : parent.distinct_id),
63
+ session_id: data[:session_id] || (fresh || parent.nil? ? nil : parent.session_id),
64
+ properties: properties
65
+ )
66
+ end
67
+ private_class_method :resolve
68
+
69
+ def self.merge_data_and_kwargs(data, kwargs)
70
+ data ||= {}
71
+ raise ArgumentError, 'context data must be a Hash' unless data.is_a?(Hash)
72
+
73
+ data.merge(kwargs)
74
+ end
75
+ private_class_method :merge_data_and_kwargs
76
+
77
+ def self.merge_properties(base, overrides)
78
+ merged = (base || {}).dup
79
+ (overrides || {}).each do |key, value|
80
+ merged.delete(key.to_s) if key.is_a?(Symbol)
81
+ merged.delete(key.to_sym) if key.is_a?(String)
82
+ merged[key] = value
83
+ end
84
+ merged
85
+ end
86
+
87
+ def self.normalize_data(data)
88
+ data ||= {}
89
+ raise ArgumentError, 'context data must be a Hash' unless data.is_a?(Hash)
90
+
91
+ properties = data[:properties] || data['properties'] || {}
92
+ raise ArgumentError, 'context properties must be a Hash' unless properties.is_a?(Hash)
93
+
94
+ {
95
+ distinct_id: data[:distinct_id] || data['distinct_id'] || data[:distinctId] || data['distinctId'],
96
+ session_id: data[:session_id] || data['session_id'] || data[:sessionId] || data['sessionId'],
97
+ properties: properties
98
+ }
99
+ end
100
+ private_class_method :normalize_data
101
+
102
+ def self.session_property_key?(properties)
103
+ return false unless properties.is_a?(Hash)
104
+
105
+ properties.key?('$session_id') || properties.key?(:$session_id)
106
+ end
107
+ private_class_method :session_property_key?
108
+
109
+ def apply_session_property!
110
+ return if session_id.nil? || properties.key?('$session_id') || properties.key?(:$session_id)
111
+
112
+ properties['$session_id'] = session_id
113
+ end
114
+ private :apply_session_property!
115
+ end
116
+ end
117
+
118
+ private_constant :Internal
119
+ end
@@ -3,8 +3,12 @@
3
3
  require 'logger'
4
4
 
5
5
  module PostHog
6
- # Wraps an existing logger and adds a prefix to all messages
6
+ # Wraps an existing logger and adds a prefix to all messages.
7
+ #
8
+ # @api private
7
9
  class PrefixedLogger
10
+ # @param logger [Logger, #debug, #info, #warn, #error]
11
+ # @param prefix [String]
8
12
  def initialize(logger, prefix)
9
13
  @logger = logger
10
14
  @prefix = prefix
@@ -37,6 +41,7 @@ module PostHog
37
41
 
38
42
  module Logging
39
43
  class << self
44
+ # @return [Logger, PostHog::PrefixedLogger] The logger used by the SDK.
40
45
  def logger
41
46
  return @logger if @logger
42
47
 
@@ -52,6 +57,7 @@ module PostHog
52
57
  @logger = PrefixedLogger.new(base_logger, '[posthog-ruby]')
53
58
  end
54
59
 
60
+ # @param logger [Logger, #debug, #info, #warn, #error] Custom logger used by the SDK.
55
61
  attr_writer :logger
56
62
  end
57
63
 
@@ -63,6 +69,7 @@ module PostHog
63
69
  end
64
70
  end
65
71
 
72
+ # @return [Logger, PostHog::PrefixedLogger] The logger used by the SDK.
66
73
  def logger
67
74
  Logging.logger
68
75
  end
@@ -4,7 +4,9 @@ require 'forwardable'
4
4
  require 'posthog/logging'
5
5
 
6
6
  module PostHog
7
- # A batch of `Message`s to be sent to the API
7
+ # A batch of messages to be sent to the API.
8
+ #
9
+ # @api private
8
10
  class MessageBatch
9
11
  class JSONGenerationError < StandardError
10
12
  end
@@ -13,12 +15,15 @@ module PostHog
13
15
  include PostHog::Logging
14
16
  include PostHog::Defaults::MessageBatch
15
17
 
18
+ # @param max_message_count [Integer] Maximum number of messages in the batch.
16
19
  def initialize(max_message_count)
17
20
  @messages = []
18
21
  @max_message_count = max_message_count
19
22
  @json_size = 0
20
23
  end
21
24
 
25
+ # @param message [Hash] Message to add to the batch.
26
+ # @return [Array<Hash>, nil]
22
27
  def <<(message)
23
28
  begin
24
29
  message_json = message.to_json
@@ -35,10 +40,12 @@ module PostHog
35
40
  end
36
41
  end
37
42
 
43
+ # @return [Boolean] Whether the batch is full.
38
44
  def full?
39
45
  item_count_exhausted? || size_exhausted?
40
46
  end
41
47
 
48
+ # @return [void]
42
49
  def clear
43
50
  @messages.clear
44
51
  @json_size = 0
@@ -1,21 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # A worker that doesn't consume jobs
4
3
  module PostHog
4
+ # A worker that doesn't consume jobs.
5
+ #
6
+ # @api private
5
7
  class NoopWorker
8
+ # @param queue [Queue]
6
9
  def initialize(queue)
7
10
  @queue = queue
8
11
  end
9
12
 
13
+ # @return [void]
10
14
  def run
11
15
  # Does nothing
12
16
  end
13
17
 
18
+ # @return [Boolean]
14
19
  # TODO: Rename to `requesting?` in future version
15
20
  def is_requesting? # rubocop:disable Naming/PredicateName
16
21
  false
17
22
  end
18
23
 
24
+ # @return [void]
19
25
  def shutdown
20
26
  # Does nothing
21
27
  end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PostHog
4
+ # API response wrapper returned by the SDK transport.
5
+ #
6
+ # @api private
4
7
  class Response
5
8
  attr_reader :status, :error
6
9
 
7
- # public: Simple class to wrap responses from the API
8
- #
9
- #
10
+ # @param status [Integer] HTTP status code, or -1 for SDK/transport errors.
11
+ # @param error [String, nil] Error message returned by the API or SDK.
10
12
  def initialize(status = 200, error = nil)
11
13
  @status = status
12
14
  @error = error
@@ -3,16 +3,29 @@
3
3
  require 'posthog/utils'
4
4
 
5
5
  module PostHog
6
- # Options for configuring feature flag behavior in capture calls
6
+ # Options for configuring deprecated feature flag behavior in capture calls.
7
+ #
8
+ # @deprecated Prefer passing a {PostHog::FeatureFlagEvaluations} snapshot to `capture(flags:)`.
7
9
  class SendFeatureFlagsOptions
8
- attr_reader :only_evaluate_locally, :person_properties, :group_properties
10
+ # @return [Boolean, nil] Whether remote feature flag evaluation should be skipped.
11
+ attr_reader :only_evaluate_locally
9
12
 
13
+ # @return [Hash] Person properties to use for feature flag evaluation.
14
+ attr_reader :person_properties
15
+
16
+ # @return [Hash] Group properties to use for feature flag evaluation.
17
+ attr_reader :group_properties
18
+
19
+ # @param only_evaluate_locally [Boolean, nil] Skip remote feature flag evaluation.
20
+ # @param person_properties [Hash, nil] Person properties to use for feature flag evaluation.
21
+ # @param group_properties [Hash, nil] Group properties to use for feature flag evaluation.
10
22
  def initialize(only_evaluate_locally: nil, person_properties: nil, group_properties: nil)
11
23
  @only_evaluate_locally = only_evaluate_locally
12
24
  @person_properties = person_properties || {}
13
25
  @group_properties = group_properties || {}
14
26
  end
15
27
 
28
+ # @return [Hash] A hash representation suitable for `capture(send_feature_flags:)`.
16
29
  def to_h
17
30
  {
18
31
  only_evaluate_locally: @only_evaluate_locally,
@@ -21,6 +34,8 @@ module PostHog
21
34
  }
22
35
  end
23
36
 
37
+ # @param hash [Hash]
38
+ # @return [PostHog::SendFeatureFlagsOptions, nil]
24
39
  def self.from_hash(hash)
25
40
  return nil unless hash.is_a?(Hash)
26
41
 
@@ -6,6 +6,9 @@ require 'posthog/transport'
6
6
  require 'posthog/utils'
7
7
 
8
8
  module PostHog
9
+ # Background worker that batches and sends queued events.
10
+ #
11
+ # @api private
9
12
  class SendWorker
10
13
  include PostHog::Utils
11
14
  include PostHog::Defaults
@@ -16,12 +19,13 @@ module PostHog
16
19
  # The worker continuously takes messages off the queue
17
20
  # and makes requests to the posthog.com api
18
21
  #
19
- # queue - Queue synchronized between client and worker
20
- # api_key - String of the project's API key
21
- # options - Hash of worker options
22
- # batch_size - Fixnum of how many items to send in a batch
23
- # on_error - Proc of what to do on an error
24
- #
22
+ # @param queue [Queue] Queue synchronized between client and worker.
23
+ # @param api_key [String] Project API key.
24
+ # @param options [Hash] Worker options.
25
+ # @option options [Integer] :batch_size How many items to send in a batch.
26
+ # @option options [Proc] :on_error Callback invoked as `on_error.call(status, error)`.
27
+ # @option options [String] :host PostHog API host URL.
28
+ # @option options [Boolean] :skip_ssl_verification Disable SSL certificate verification.
25
29
  def initialize(queue, api_key, options = {})
26
30
  symbolize_keys! options
27
31
  @queue = queue
@@ -33,8 +37,9 @@ module PostHog
33
37
  @transport = Transport.new api_host: options[:host], skip_ssl_verification: options[:skip_ssl_verification]
34
38
  end
35
39
 
36
- # public: Continuously runs the loop to check for new events
40
+ # Continuously runs the loop to check for new events.
37
41
  #
42
+ # @return [void]
38
43
  def run
39
44
  until Thread.current[:should_exit]
40
45
  return if @queue.empty?
@@ -54,12 +59,14 @@ module PostHog
54
59
  @transport.shutdown
55
60
  end
56
61
 
62
+ # @return [void]
57
63
  def shutdown
58
64
  @transport.shutdown
59
65
  end
60
66
 
61
67
  # public: Check whether we have outstanding requests.
62
68
  #
69
+ # @return [Boolean] Whether the worker has outstanding requests.
63
70
  # TODO: Rename to `requesting?` in future version
64
71
  def is_requesting? # rubocop:disable Naming/PredicateName
65
72
  @lock.synchronize { !@batch.empty? }
@@ -10,11 +10,24 @@ require 'net/https'
10
10
  require 'json'
11
11
 
12
12
  module PostHog
13
+ # HTTP transport used by the SDK workers.
14
+ #
15
+ # @api private
13
16
  class Transport
14
17
  include PostHog::Defaults::Request
15
18
  include PostHog::Utils
16
19
  include PostHog::Logging
17
20
 
21
+ # @param options [Hash] Transport configuration.
22
+ # @option options [String] :api_host Full PostHog API host URL.
23
+ # @option options [String] :host Hostname to connect to.
24
+ # @option options [Integer] :port Port to connect to.
25
+ # @option options [Boolean] :ssl Whether to use HTTPS.
26
+ # @option options [Hash] :headers HTTP headers for batch requests.
27
+ # @option options [String] :path HTTP path for batch requests.
28
+ # @option options [Integer] :retries Number of retry attempts for retryable failures.
29
+ # @option options [PostHog::BackoffPolicy] :backoff_policy Backoff policy used between retries.
30
+ # @option options [Boolean] :skip_ssl_verification Disable SSL certificate verification.
18
31
  def initialize(options = {})
19
32
  if options[:api_host]
20
33
  uri = URI.parse(options[:api_host])
@@ -43,6 +56,8 @@ module PostHog
43
56
 
44
57
  # Sends a batch of messages to the API
45
58
  #
59
+ # @param api_key [String] Project API key.
60
+ # @param batch [PostHog::MessageBatch, Array<Hash>] Batch of messages to send.
46
61
  # @return [Response] API response
47
62
  def send(api_key, batch)
48
63
  logger.debug("Sending request for #{batch.length} items")
@@ -72,7 +87,9 @@ module PostHog
72
87
  end
73
88
  end
74
89
 
75
- # Closes a persistent connection if it exists
90
+ # Closes a persistent connection if it exists.
91
+ #
92
+ # @return [void]
76
93
  def shutdown
77
94
  @http.finish if @http.started?
78
95
  end
data/lib/posthog/utils.rb CHANGED
@@ -6,6 +6,9 @@ module PostHog
6
6
  class InconclusiveMatchError < StandardError
7
7
  end
8
8
 
9
+ # Utility helpers used internally by the SDK.
10
+ #
11
+ # @api private
9
12
  module Utils
10
13
  module_function
11
14
 
@@ -145,12 +148,19 @@ module PostHog
145
148
  end
146
149
  end
147
150
 
151
+ # Hash that clears itself when it reaches a maximum length.
152
+ #
153
+ # @api private
148
154
  class SizeLimitedHash < Hash
155
+ # @param max_length [Integer]
149
156
  def initialize(max_length, ...)
150
157
  super(...)
151
158
  @max_length = max_length
152
159
  end
153
160
 
161
+ # @param key [Object]
162
+ # @param value [Object]
163
+ # @return [Object]
154
164
  def []=(key, value)
155
165
  clear if length >= @max_length
156
166
  super
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PostHog
4
- VERSION = '3.8.1'
4
+ VERSION = '3.9.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: posthog-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.8.1
4
+ version: 3.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''
@@ -46,6 +46,7 @@ files:
46
46
  - lib/posthog/feature_flags.rb
47
47
  - lib/posthog/field_parser.rb
48
48
  - lib/posthog/flag_definition_cache.rb
49
+ - lib/posthog/internal/context.rb
49
50
  - lib/posthog/logging.rb
50
51
  - lib/posthog/message_batch.rb
51
52
  - lib/posthog/noop_worker.rb