Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/trim-whitespace-default-host.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ruby': patch
---

Trim whitespace from `api_key`, `personal_api_key`, and `host` config values, and default `host` to `https://us.i.posthog.com`.
23 changes: 21 additions & 2 deletions lib/posthog/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def _decrement_instance_count(api_key)
# on the calling thread. Useful in forking environments like Sidekiq
# and Resque. Defaults to +false+.
# @option opts [Proc] :on_error Handles error calls from the API.
# @option opts [String] :host Fully qualified hostname of the PostHog server. Defaults to `https://app.posthog.com`
# @option opts [String] :host Fully qualified hostname of the PostHog server. Defaults to `https://us.i.posthog.com`
# @option opts [Integer] :feature_flags_polling_interval How often to poll for feature flag definition changes.
# Measured in seconds, defaults to 30.
# @option opts [Integer] :feature_flag_request_timeout_seconds How long to wait for feature flag evaluation.
Expand All @@ -74,7 +74,9 @@ def _decrement_instance_count(api_key)
def initialize(opts = {})
symbolize_keys!(opts)

opts[:host] ||= 'https://app.posthog.com'
opts[:api_key] = normalize_string_option(opts[:api_key])
opts[:personal_api_key] = normalize_string_option(opts[:personal_api_key], blank_as_nil: true)
opts[:host] = normalize_host_option(opts[:host])

@queue = Queue.new
@api_key = opts[:api_key]
Expand Down Expand Up @@ -102,6 +104,7 @@ def initialize(opts = {})
@personal_api_key = opts[:personal_api_key]

check_api_key!
logger.error('api_key is empty after trimming whitespace; check your project API key') if @api_key == ''

# Warn when multiple clients are created with the same API key (can cause dropped events)
unless opts[:test_mode] || opts[:disable_singleton_warning]
Expand Down Expand Up @@ -585,6 +588,22 @@ def check_api_key!
raise ArgumentError, 'API key must be initialized' if @api_key.nil?
end

def normalize_string_option(value, blank_as_nil: false)
return value unless value.is_a?(String)

normalized = value.strip
return nil if blank_as_nil && normalized.empty?

normalized
end

def normalize_host_option(host)
normalized = normalize_string_option(host)
return 'https://us.i.posthog.com' if normalized.nil? || normalized.empty?

normalized
end

def ensure_worker_running
return if worker_running?

Expand Down
2 changes: 1 addition & 1 deletion lib/posthog/defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Defaults
MAX_HASH_SIZE = 50_000

module Request
HOST = 'app.posthog.com'
HOST = 'us.i.posthog.com'
PORT = 443
PATH = '/batch/'
SSL = true
Expand Down
62 changes: 44 additions & 18 deletions spec/posthog/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require 'spec_helper'

module PostHog
flags_endpoint = 'https://app.posthog.com/flags/?v=2'
flags_endpoint = 'https://us.i.posthog.com/flags/?v=2'

RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = nil

Expand All @@ -16,6 +16,7 @@ module PostHog
allow(logger).to receive(:warn)
allow(logger).to receive(:info)
allow(logger).to receive(:debug)
allow(logger).to receive(:error)
end

describe '#initialize' do
Expand All @@ -32,16 +33,41 @@ module PostHog
end

it 'handles skip_ssl_verification' do
expect(PostHog::Transport).to receive(:new).with({ api_host: 'https://app.posthog.com',
expect(PostHog::Transport).to receive(:new).with({ api_host: 'https://us.i.posthog.com',
skip_ssl_verification: true })
expect { Client.new api_key: API_KEY, skip_ssl_verification: true }.to_not raise_error
end

it 'trims whitespace-sensitive options' do
client = Client.new(
api_key: " \n#{API_KEY}\t ",
personal_api_key: " \n\t ",
host: " \nhttps://eu.i.posthog.com/\t ",
test_mode: true
)

expect(client.instance_variable_get(:@api_key)).to eq(API_KEY)
expect(client.instance_variable_get(:@personal_api_key)).to be_nil
expect(client.instance_variable_get(:@feature_flags_poller).instance_variable_get(:@host)).to eq('https://eu.i.posthog.com/')
end

it 'defaults a blank host after trimming whitespace' do
client = Client.new(api_key: API_KEY, host: " \n\t ", test_mode: true)

expect(client.instance_variable_get(:@feature_flags_poller).instance_variable_get(:@host)).to eq('https://us.i.posthog.com')
end

it 'logs when the api_key is empty after trimming whitespace' do
Client.new(api_key: " \n\t ", test_mode: true)

expect(logger).to have_received(:error).with(include('api_key is empty after trimming whitespace'))
end

context 'singleton warning' do
before do
# Stub HTTP to allow creating clients without test_mode (which triggers the warning)
stub_request(:post, 'https://app.posthog.com/batch/').to_return(status: 200, body: '{}')
stub_request(:get, %r{https://app\.posthog\.com/api/feature_flag/}).to_return(status: 200, body: '{}')
stub_request(:post, 'https://us.i.posthog.com/batch/').to_return(status: 200, body: '{}')
stub_request(:get, %r{https://us\.i\.posthog\.com/api/feature_flag/}).to_return(status: 200, body: '{}')
end

it 'warns when multiple clients are created with the same API key' do
Expand Down Expand Up @@ -252,7 +278,7 @@ module PostHog

stub_request(
:get,
'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
).to_return(status: 200, body: api_feature_flag_res.to_json)
stub_request(:post, flags_endpoint)
.to_return(status: 200, body: flags_response.to_json)
Expand Down Expand Up @@ -297,7 +323,7 @@ module PostHog

stub_request(
:get,
'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
).to_return(status: 200, body: api_feature_flag_res.to_json)
stub_request(:post, flags_endpoint)
.to_return(status: 200, body: flags_response.to_json)
Expand All @@ -322,7 +348,7 @@ module PostHog
'off-feature' => false } }
stub_request(
:get,
'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
).to_return(status: 200, body: {}.to_json)
stub_request(:post, flags_endpoint)
.to_return(status: 200, body: flags_response.to_json)
Expand All @@ -347,7 +373,7 @@ module PostHog

stub_request(
:get,
'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
).to_return(status: 401, body: { 'error' => 'not authorized' }.to_json)
stub_request(:post, flags_endpoint)
.to_return(status: 200, body: flags_response.to_json)
Expand All @@ -364,7 +390,7 @@ module PostHog
expect(properties['$feature/beta-feature']).to eq('random-variant')
expect(properties['$active_feature_flags']).to eq(['beta-feature'])

assert_not_requested :get, 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
assert_not_requested :get, 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
end

it 'manages memory well when sending feature flags' do
Expand All @@ -388,7 +414,7 @@ module PostHog
}
stub_request(
:get,
'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
).to_return(status: 200, body: api_feature_flag_res.to_json)

stub_const('PostHog::Defaults::MAX_HASH_SIZE', 10)
Expand Down Expand Up @@ -456,7 +482,7 @@ module PostHog

stub_request(
:get,
'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
).to_return(status: 200, body: api_feature_flag_res.to_json)

stub_const('PostHog::Defaults::MAX_HASH_SIZE', 10)
Expand Down Expand Up @@ -613,7 +639,7 @@ module PostHog

stub_request(
:get,
'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
).to_return(status: 200, body: api_feature_flag_res.to_json)
stub_request(:post, flags_endpoint)
.to_return(status: 200, body: flags_response.to_json)
Expand Down Expand Up @@ -651,7 +677,7 @@ module PostHog

stub_request(
:get,
'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
).to_return(status: 200, body: api_feature_flag_res.to_json)
stub_request(:post, flags_endpoint)
.to_return(status: 200, body: flags_response.to_json)
Expand All @@ -677,7 +703,7 @@ module PostHog
it 'ignores feature flags with invalid send_feature_flags parameter' do
stub_request(
:get,
'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
).to_return(status: 200, body: { flags: [] }.to_json)
c = Client.new(api_key: API_KEY, personal_api_key: API_KEY, test_mode: true)

Expand Down Expand Up @@ -715,7 +741,7 @@ module PostHog
}
stub_request(
:get,
'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
).to_return(status: 200, body: api_feature_flag_res.to_json)

# This should NOT be called because only_evaluate_locally is true
Expand Down Expand Up @@ -767,7 +793,7 @@ module PostHog
}
stub_request(
:get,
'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
).to_return(status: 200, body: api_feature_flag_res.to_json)

# This should NOT be called because only_evaluate_locally is true
Expand Down Expand Up @@ -818,7 +844,7 @@ module PostHog
}
stub_request(
:get,
'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
).to_return(status: 200, body: api_feature_flag_res.to_json)

# This SHOULD be called because only_evaluate_locally is explicitly false
Expand Down Expand Up @@ -1160,7 +1186,7 @@ def run
# Mock response for api/feature_flag
stub_request(
:get,
'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true'
).to_return(status: 200, body: api_feature_flag_res.to_json)

# Mock response for `/flags`
Expand Down
4 changes: 2 additions & 2 deletions spec/posthog/feature_flag_error_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

module PostHog
describe 'Feature Flag Error Tracking' do
let(:flags_endpoint) { 'https://app.posthog.com/flags/?v=2' }
let(:feature_flag_endpoint) { 'https://app.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' }
let(:flags_endpoint) { 'https://us.i.posthog.com/flags/?v=2' }
let(:feature_flag_endpoint) { 'https://us.i.posthog.com/api/feature_flag/local_evaluation?token=testsecret&send_cohorts=true' }
let(:client) { Client.new(api_key: API_KEY, personal_api_key: API_KEY, test_mode: true) }

before do
Expand Down
Loading
Loading