Skip to content

Commit 71b522b

Browse files
authored
feat: Internal environment ID support. (#217)
This PR propagates the environment ID internal to the common client. A subsequent PR will add hooks support and utilize this environment ID support.
1 parent 73802e8 commit 71b522b

19 files changed

+559
-41
lines changed

apps/flutter_client_contract_test_service/pubspec.lock

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -391,33 +391,33 @@ packages:
391391
dependency: transitive
392392
description:
393393
name: launchdarkly_common_client
394-
sha256: e691f25676dfb659975843d7ef5e178297939bf3f4a53cab75381be07f27feec
394+
sha256: a48e52d8aebf8a12fb8816b4776a75d68dffeaaeb3b9a9b35189467218fecd35
395395
url: "https://pub.dev"
396396
source: hosted
397-
version: "1.6.1"
397+
version: "1.6.2"
398398
launchdarkly_dart_common:
399399
dependency: transitive
400400
description:
401401
name: launchdarkly_dart_common
402-
sha256: "323b0ae2bc756c7c83e95494983b72b190e012e090758a920a992358cbc025a2"
402+
sha256: a5275ba48635364690181d7172d679889fffca501a7dbec74992c4057357ac6f
403403
url: "https://pub.dev"
404404
source: hosted
405-
version: "1.6.0"
405+
version: "1.6.1"
406406
launchdarkly_event_source_client:
407407
dependency: transitive
408408
description:
409409
name: launchdarkly_event_source_client
410-
sha256: "3506de716320c80898e12b825063a69a9a7169042902cdd6eb164f46b3ec60e3"
410+
sha256: "7931ffed3d38272db1bf768e351bc49bda8a391b6811a6e4a7004149c2aee1d0"
411411
url: "https://pub.dev"
412412
source: hosted
413-
version: "1.2.0"
413+
version: "1.2.1"
414414
launchdarkly_flutter_client_sdk:
415415
dependency: "direct main"
416416
description:
417417
path: "../../packages/flutter_client_sdk"
418418
relative: true
419419
source: path
420-
version: "4.11.1"
420+
version: "4.11.2"
421421
lints:
422422
dependency: "direct dev"
423423
description:
@@ -462,10 +462,10 @@ packages:
462462
dependency: transitive
463463
description:
464464
name: meta
465-
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
465+
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
466466
url: "https://pub.dev"
467467
source: hosted
468-
version: "1.16.0"
468+
version: "1.17.0"
469469
mime:
470470
dependency: transitive
471471
description:

apps/sse_contract_test_service/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ packages:
311311
path: "../../packages/event_source_client"
312312
relative: true
313313
source: path
314-
version: "1.2.1"
314+
version: "2.0.0"
315315
lints:
316316
dependency: "direct dev"
317317
description:

melos.yaml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ environment:
44
sdk: '>=3.4.0 <4.0.0'
55

66
packages:
7-
# Remove the event_source_client from the workspace temporarily to allow a breaking change.
8-
- packages/common
9-
- packages/common_client
7+
- packages/*
108
- packages/flutter_client_sdk/example
119
- apps/*
1210

@@ -21,10 +19,7 @@ scripts:
2119
# Add more packages as more of them have tests.
2220
# Tests are ran with flutter as it supports coverage. Some packages may also include flutter
2321
# dependencies.
24-
run: >
25-
MELOS_PACKAGES="launchdarkly_dart_common,launchdarkly_common_client,launchdarkly_flutter_client_sdk" melos exec -- flutter test . --coverage &&
26-
cd packages/event_source_client && dart test
27-
22+
run: MELOS_PACKAGES="launchdarkly_dart_common,launchdarkly_common_client,launchdarkly_flutter_client_sdk" melos exec -- flutter test . --coverage
2823
merge-trace-files:
2924
description: Merge all packages coverage trace files ignoring data related to generated files.
3025
run: >

packages/common_client/lib/src/data_sources/data_source.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ sealed class DataSourceEvent {}
55
final class DataEvent implements DataSourceEvent {
66
final String type;
77
final String data;
8+
final String? environmentId;
89

9-
DataEvent(this.type, this.data);
10+
DataEvent(this.type, this.data, {this.environmentId});
1011
}
1112

1213
final class StatusEvent implements DataSourceEvent {

packages/common_client/lib/src/data_sources/data_source_event_handler.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,14 @@ final class DataSourceEventHandler {
5050
final LDLogger _logger;
5151

5252
Future<MessageStatus> handleMessage(
53-
LDContext context, String type, String data) async {
53+
LDContext context, String type, String data,
54+
{String? environmentId}) async {
5455
switch (type) {
5556
case 'put':
5657
{
5758
try {
5859
final parsed = jsonDecode(data);
59-
return _processPut(context, parsed);
60+
return _processPut(context, parsed, environmentId);
6061
} catch (err) {
6162
_logger.error('put message contained invalid json: $err');
6263
_statusManager.setErrorByKind(
@@ -95,12 +96,13 @@ final class DataSourceEventHandler {
9596
}
9697
}
9798

98-
Future<MessageStatus> _processPut(LDContext context, dynamic parsed) async {
99+
Future<MessageStatus> _processPut(
100+
LDContext context, dynamic parsed, String? environmentId) async {
99101
try {
100102
final putData = LDEvaluationResultsSerialization.fromJson(parsed).map(
101103
(key, value) => MapEntry(
102104
key, ItemDescriptor(version: value.version, flag: value)));
103-
await _flagManager.init(context, putData);
105+
await _flagManager.init(context, putData, environmentId: environmentId);
104106
_statusManager.setValid();
105107
return MessageStatus.messageHandled;
106108
} catch (err) {

packages/common_client/lib/src/data_sources/data_source_manager.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ final class DataSourceManager {
144144
switch (event) {
145145
case DataEvent():
146146
var handled = await _dataSourceEventHandler.handleMessage(
147-
_activeContext!, event.type, event.data);
147+
_activeContext!, event.type, event.data,
148+
environmentId: event.environmentId);
148149
if (handled == MessageStatus.messageHandled &&
149150
_identifyCompleter != null) {
150151
if (_identifyCompleter!.isCompleted) {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const _envIdHeader = 'x-ld-envid';
2+
3+
final _splitRegex = RegExp(r'\s*,\s*');
4+
5+
/// Get the environment ID from headers.
6+
String? getEnvironmentId(Map<String, String>? headers) {
7+
// Headers will always be in lower case from the http response.
8+
// If multiple headers are associated with a single key, then they will be
9+
// in a comma separated list with potential whitespace.
10+
final headerValue = headers?[_envIdHeader];
11+
if (headerValue == null) {
12+
return null;
13+
}
14+
return headerValue.split(_splitRegex).first;
15+
}

packages/common_client/lib/src/data_sources/polling_data_source.dart

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import 'package:launchdarkly_dart_common/launchdarkly_dart_common.dart';
55
import 'dart:math';
66

77
import '../config/data_source_config.dart';
8+
import '../config/defaults/credential_type.dart';
9+
import '../config/defaults/default_config.dart';
810
import 'data_source.dart';
911
import 'data_source_status.dart';
12+
import 'get_environment_id.dart';
1013

1114
HttpClient _defaultClientFactory(HttpProperties httpProperties) {
1215
return HttpClient(httpProperties: httpProperties);
@@ -41,6 +44,8 @@ final class PollingDataSource implements DataSource {
4144

4245
final StreamController<DataSourceEvent> _eventController = StreamController();
4346

47+
late final String _credential;
48+
4449
@override
4550
Stream<DataSourceEvent> get events => _eventController.stream;
4651

@@ -68,7 +73,8 @@ final class PollingDataSource implements DataSource {
6873
_defaultClientFactory})
6974
: _endpoints = endpoints,
7075
_logger = logger.subLogger('PollingDataSource'),
71-
_dataSourceConfig = dataSourceConfig {
76+
_dataSourceConfig = dataSourceConfig,
77+
_credential = credential {
7278
_pollingInterval = testingInterval ?? dataSourceConfig.pollingInterval;
7379

7480
if (_dataSourceConfig.useReport) {
@@ -134,7 +140,18 @@ final class PollingDataSource implements DataSource {
134140
}
135141
_lastEtag = etag;
136142

137-
_eventController.sink.add(DataEvent('put', res.body));
143+
var environmentId = getEnvironmentId(res.headers);
144+
145+
if (environmentId == null &&
146+
DefaultConfig.credentialConfig.credentialType ==
147+
CredentialType.clientSideId) {
148+
// When using a client-side ID we can use it to represent the
149+
// environment.
150+
environmentId = _credential;
151+
}
152+
153+
_eventController.sink
154+
.add(DataEvent('put', res.body, environmentId: environmentId));
138155
} else {
139156
if (isHttpGloballyRecoverable(res.statusCode)) {
140157
_eventController.sink.add(StatusEvent(

packages/common_client/lib/src/data_sources/streaming_data_source.dart

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import 'package:launchdarkly_dart_common/launchdarkly_dart_common.dart';
55
import 'package:launchdarkly_event_source_client/launchdarkly_event_source_client.dart';
66

77
import '../config/data_source_config.dart';
8+
import '../config/defaults/credential_type.dart';
89
import '../config/defaults/default_config.dart';
910
import 'data_source.dart';
1011
import 'data_source_status.dart';
12+
import 'get_environment_id.dart';
1113

1214
typedef MessageHandler = void Function(MessageEvent);
1315
typedef ErrorHandler = void Function(dynamic);
@@ -43,14 +45,18 @@ final class StreamingDataSource implements DataSource {
4345
late final String _contextString;
4446
bool _stopped = false;
4547

46-
StreamSubscription<MessageEvent>? _subscription;
48+
StreamSubscription<Event>? _subscription;
4749

4850
final StreamController<DataSourceEvent> _dataController = StreamController();
4951

5052
late final bool _useReport;
5153

5254
SSEClient? _client;
5355

56+
String? _environmentId;
57+
58+
final String _credential;
59+
5460
@override
5561
Stream<DataSourceEvent> get events => _dataController.stream;
5662

@@ -73,7 +79,8 @@ final class StreamingDataSource implements DataSource {
7379
_logger = logger.subLogger('StreamingDataSource'),
7480
_dataSourceConfig = dataSourceConfig,
7581
_clientFactory = clientFactory,
76-
_httpProperties = httpProperties {
82+
_httpProperties = httpProperties,
83+
_credential = credential {
7784
final plainContextString =
7885
jsonEncode(LDContextSerialization.toJson(context, isEvent: false));
7986

@@ -122,8 +129,22 @@ final class StreamingDataSource implements DataSource {
122129
return;
123130
}
124131

125-
_logger.debug('Received event, data: ${event.data}');
126-
_dataController.sink.add(DataEvent(event.type, event.data));
132+
switch (event) {
133+
case MessageEvent():
134+
_logger.debug('Received message event, data: ${event.data}');
135+
_dataController.sink.add(
136+
DataEvent(event.type, event.data, environmentId: _environmentId));
137+
case OpenEvent():
138+
_logger.debug('Received connect event, data: ${event.headers}');
139+
if (event.headers != null) {
140+
_environmentId = getEnvironmentId(event.headers);
141+
} else if (DefaultConfig.credentialConfig.credentialType ==
142+
CredentialType.clientSideId) {
143+
// When using a client-side ID we can use it to represent the
144+
// environment.
145+
_environmentId = _credential;
146+
}
147+
}
127148
})
128149
..onError((err) {
129150
if (_permanentShutdown) {

packages/common_client/lib/src/flag_manager/flag_manager.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,14 @@ final class FlagManager {
4040
/// Gets all the current flags.
4141
Map<String, ItemDescriptor> getAll() => _flagStore.getAll();
4242

43+
/// Gets the environment ID for the current flag set.
44+
String? get environmentId => _flagStore.environmentId;
45+
4346
/// Initializes the flag manager with data from a data source.
4447
/// Persistence initialization is handled by [FlagPersistence].
45-
Future<void> init(LDContext context, Map<String, ItemDescriptor> newFlags) =>
46-
_flagPersistence.init(context, newFlags);
48+
Future<void> init(LDContext context, Map<String, ItemDescriptor> newFlags,
49+
{String? environmentId}) =>
50+
_flagPersistence.init(context, newFlags, environmentId: environmentId);
4751

4852
/// Attempt to update a flag. If the flag is for the wrong context, or
4953
/// it is of an older version, then an update will not be performed.

0 commit comments

Comments
 (0)