Skip to content

Commit bed79fb

Browse files
committed
Disable client when project token is missing
1 parent e35bf94 commit bed79fb

3 files changed

Lines changed: 321 additions & 12 deletions

File tree

src/PostHog/Config/PostHogOptions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.Extensions.Options;
2+
using PostHog.Library;
23

34
namespace PostHog;
45

@@ -26,6 +27,15 @@ public string? ProjectToken
2627

2728
internal bool HasLegacyProjectApiKey => _projectApiKey is not null;
2829

30+
internal void Normalize()
31+
{
32+
_projectToken = _projectToken.NullIfEmpty();
33+
_projectApiKey = _projectApiKey.NullIfEmpty();
34+
PersonalApiKey = PersonalApiKey.NullIfEmpty();
35+
HostUrl = HostUrl.NormalizeHostUrl();
36+
Disabled = Disabled || ProjectToken is null;
37+
}
38+
2939
/// <summary>
3040
/// Obsolete alias for <see cref="ProjectToken"/>.
3141
/// </summary>
@@ -49,6 +59,11 @@ public string? ProjectApiKey
4959
/// </remarks>
5060
public string? PersonalApiKey { get; set; }
5161

62+
/// <summary>
63+
/// Whether this client is disabled and should no-op instead of sending data to PostHog. (Default: false)
64+
/// </summary>
65+
public bool Disabled { get; set; }
66+
5267
/// <summary>
5368
/// PostHog API host, usually 'https://us.i.posthog.com' (default) or 'https://eu.i.posthog.com'
5469
/// </summary>

src/PostHog/PostHogClient.cs

Lines changed: 146 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,39 @@ static void NormalizeOptions(PostHogOptions options, ILogger<PostHogClient> logg
9595
logger.LogWarningProjectApiKeyDeprecated();
9696
}
9797

98-
options.ProjectToken = options.ProjectToken?.Trim();
99-
options.PersonalApiKey = options.PersonalApiKey.NullIfEmpty();
100-
options.HostUrl = options.HostUrl.NormalizeHostUrl();
98+
options.Normalize();
10199

102-
if (string.IsNullOrEmpty(options.ProjectToken))
100+
if (options.ProjectToken is null)
103101
{
104102
logger.LogErrorProjectTokenRequired();
105103
}
106104
}
107105

106+
bool IsDisabled(string methodName)
107+
{
108+
if (!_options.Value.Disabled)
109+
{
110+
return false;
111+
}
112+
113+
_logger.LogWarningClientDisabled(methodName);
114+
return true;
115+
}
116+
117+
bool RequiresMissingPersonalApiKey(AllFeatureFlagsOptions? options, string methodName)
118+
=> options is { OnlyEvaluateLocally: true } && IsPersonalApiKeyMissing(methodName);
119+
120+
bool IsPersonalApiKeyMissing(string methodName)
121+
{
122+
if (_options.Value.PersonalApiKey is not null)
123+
{
124+
return false;
125+
}
126+
127+
_logger.LogWarningPersonalApiKeyMissing(methodName);
128+
return true;
129+
}
130+
108131
/// <summary>
109132
/// To marry up whatever a user does before they sign up or log in with what they do after you need to make an
110133
/// alias call. This will allow you to answer questions like "Which marketing channels leads to users churning
@@ -122,27 +145,48 @@ public async Task<ApiResult> AliasAsync(
122145
string previousId,
123146
string newId,
124147
CancellationToken cancellationToken)
125-
=> await _apiClient.AliasAsync(previousId, newId, cancellationToken);
148+
{
149+
if (IsDisabled(nameof(AliasAsync)))
150+
{
151+
return new ApiResult(0);
152+
}
153+
154+
return await _apiClient.AliasAsync(previousId, newId, cancellationToken);
155+
}
126156

127157
/// <inheritdoc/>
128158
public async Task<ApiResult> IdentifyAsync(
129159
string distinctId,
130160
Dictionary<string, object>? personPropertiesToSet,
131161
Dictionary<string, object>? personPropertiesToSetOnce,
132162
CancellationToken cancellationToken)
133-
=> await _apiClient.IdentifyAsync(
163+
{
164+
if (IsDisabled(nameof(IdentifyAsync)))
165+
{
166+
return new ApiResult(0);
167+
}
168+
169+
return await _apiClient.IdentifyAsync(
134170
distinctId,
135171
personPropertiesToSet,
136172
personPropertiesToSetOnce,
137173
cancellationToken);
174+
}
138175

139176
/// <inheritdoc/>
140177
public Task<ApiResult> GroupIdentifyAsync(
141178
string type,
142179
StringOrValue<int> key,
143180
Dictionary<string, object>? properties,
144181
CancellationToken cancellationToken)
145-
=> _apiClient.GroupIdentifyAsync(type, key, properties, cancellationToken);
182+
{
183+
if (IsDisabled(nameof(GroupIdentifyAsync)))
184+
{
185+
return Task.FromResult(new ApiResult(0));
186+
}
187+
188+
return _apiClient.GroupIdentifyAsync(type, key, properties, cancellationToken);
189+
}
146190

147191
/// <inheritdoc/>
148192
public Task<ApiResult> GroupIdentifyAsync(
@@ -151,7 +195,14 @@ public Task<ApiResult> GroupIdentifyAsync(
151195
StringOrValue<int> key,
152196
Dictionary<string, object>? properties,
153197
CancellationToken cancellationToken)
154-
=> _apiClient.GroupIdentifyAsync(type, key, properties, cancellationToken, distinctId);
198+
{
199+
if (IsDisabled(nameof(GroupIdentifyAsync)))
200+
{
201+
return Task.FromResult(new ApiResult(0));
202+
}
203+
204+
return _apiClient.GroupIdentifyAsync(type, key, properties, cancellationToken, distinctId);
205+
}
155206

156207
/// <inheritdoc/>
157208
public bool Capture(
@@ -162,6 +213,11 @@ public bool Capture(
162213
bool sendFeatureFlags,
163214
DateTimeOffset? timestamp = null)
164215
{
216+
if (IsDisabled(nameof(Capture)))
217+
{
218+
return false;
219+
}
220+
165221
// If custom timestamp provided, add it to properties
166222
if (timestamp.HasValue)
167223
{
@@ -218,6 +274,11 @@ public bool CaptureException(
218274
bool sendFeatureFlags,
219275
DateTimeOffset? timestamp = null)
220276
{
277+
if (IsDisabled(nameof(CaptureException)))
278+
{
279+
return false;
280+
}
281+
221282
if (exception == null)
222283
{
223284
_logger.LogErrorCaptureExceptionNull();
@@ -305,6 +366,16 @@ public async Task<bool> IsFeatureEnabledAsync(
305366
FeatureFlagOptions? options,
306367
CancellationToken cancellationToken)
307368
{
369+
if (IsDisabled(nameof(IsFeatureEnabledAsync)))
370+
{
371+
return false;
372+
}
373+
374+
if (RequiresMissingPersonalApiKey(options, nameof(IsFeatureEnabledAsync)))
375+
{
376+
return false;
377+
}
378+
308379
var result = await GetFeatureFlagAsync(
309380
featureKey,
310381
distinctId,
@@ -321,6 +392,16 @@ public async Task<bool> IsFeatureEnabledAsync(
321392
FeatureFlagOptions? options,
322393
CancellationToken cancellationToken)
323394
{
395+
if (IsDisabled(nameof(GetFeatureFlagAsync)))
396+
{
397+
return null;
398+
}
399+
400+
if (RequiresMissingPersonalApiKey(options, nameof(GetFeatureFlagAsync)))
401+
{
402+
return null;
403+
}
404+
324405
LocalEvaluator? localEvaluator;
325406
try
326407
{
@@ -461,7 +542,12 @@ void HandleRemoteError(Exception ex, string errorType)
461542
/// <inheritdoc/>
462543
public async Task<JsonDocument?> GetRemoteConfigPayloadAsync(string key, CancellationToken cancellationToken)
463544
{
464-
if (_options.Value.PersonalApiKey is null)
545+
if (IsDisabled(nameof(GetRemoteConfigPayloadAsync)))
546+
{
547+
return null;
548+
}
549+
550+
if (IsPersonalApiKeyMissing(nameof(GetRemoteConfigPayloadAsync)))
465551
{
466552
_logger.LogWarningPersonalApiKeyRequiredForRemoteConfigPayload();
467553
return null;
@@ -565,6 +651,16 @@ public async Task<IReadOnlyDictionary<string, FeatureFlag>> GetAllFeatureFlagsAs
565651
AllFeatureFlagsOptions? options,
566652
CancellationToken cancellationToken)
567653
{
654+
if (IsDisabled(nameof(GetAllFeatureFlagsAsync)))
655+
{
656+
return new Dictionary<string, FeatureFlag>();
657+
}
658+
659+
if (RequiresMissingPersonalApiKey(options, nameof(GetAllFeatureFlagsAsync)))
660+
{
661+
return new Dictionary<string, FeatureFlag>();
662+
}
663+
568664
if (_options.Value.PersonalApiKey is not null)
569665
{
570666
// Attempt to load local feature flags.
@@ -650,7 +746,12 @@ public async Task LoadFeatureFlagsAsync(CancellationToken cancellationToken)
650746
{
651747
_logger.LogInfoLoadFeatureFlags();
652748

653-
if (_options.Value.PersonalApiKey is null)
749+
if (IsDisabled(nameof(LoadFeatureFlagsAsync)))
750+
{
751+
return;
752+
}
753+
754+
if (IsPersonalApiKeyMissing(nameof(LoadFeatureFlagsAsync)))
654755
{
655756
_logger.LogWarningPersonalApiKeyRequired();
656757
return;
@@ -678,7 +779,15 @@ public async Task LoadFeatureFlagsAsync(CancellationToken cancellationToken)
678779
}
679780

680781
/// <inheritdoc/>
681-
public async Task FlushAsync() => await _asyncBatchHandler.FlushAsync();
782+
public async Task FlushAsync()
783+
{
784+
if (IsDisabled(nameof(FlushAsync)))
785+
{
786+
return;
787+
}
788+
789+
await _asyncBatchHandler.FlushAsync();
790+
}
682791

683792
/// <inheritdoc/>
684793
public string Version => VersionConstants.Version;
@@ -688,6 +797,11 @@ public async Task LoadFeatureFlagsAsync(CancellationToken cancellationToken)
688797
[Obsolete("This method is for internal use only and may go away soon.")]
689798
internal async Task<LocalEvaluator?> GetLocalEvaluatorAsync(CancellationToken cancellationToken)
690799
{
800+
if (IsDisabled(nameof(GetLocalEvaluatorAsync)))
801+
{
802+
return null;
803+
}
804+
691805
try
692806
{
693807
return await _featureFlagsLoader.GetFeatureFlagsForLocalEvaluationAsync(cancellationToken);
@@ -705,7 +819,15 @@ public async Task LoadFeatureFlagsAsync(CancellationToken cancellationToken)
705819
/// <summary>
706820
/// Clears the local flags cache.
707821
/// </summary>
708-
public void ClearLocalFlagsCache() => _featureFlagsLoader.Clear();
822+
public void ClearLocalFlagsCache()
823+
{
824+
if (IsDisabled(nameof(ClearLocalFlagsCache)))
825+
{
826+
return;
827+
}
828+
829+
_featureFlagsLoader.Clear();
830+
}
709831

710832
/// <inheritdoc/>
711833
public async ValueTask DisposeAsync()
@@ -875,4 +997,16 @@ public static partial void LogErrorUnableToGetRemoteConfigPayload(
875997
Level = LogLevel.Error,
876998
Message = "CaptureException failed with an exception")]
877999
public static partial void LogErrorCaptureExceptionFailed(this ILogger<PostHogClient> logger, Exception exception);
1000+
1001+
[LoggerMessage(
1002+
EventId = 21,
1003+
Level = LogLevel.Warning,
1004+
Message = "PostHog SDK is disabled; {MethodName} is a no-op.")]
1005+
public static partial void LogWarningClientDisabled(this ILogger<PostHogClient> logger, string methodName);
1006+
1007+
[LoggerMessage(
1008+
EventId = 22,
1009+
Level = LogLevel.Warning,
1010+
Message = "PostHog personal_api_key is not configured; {MethodName} is a no-op.")]
1011+
public static partial void LogWarningPersonalApiKeyMissing(this ILogger<PostHogClient> logger, string methodName);
8781012
}

0 commit comments

Comments
 (0)