-
Notifications
You must be signed in to change notification settings - Fork 19
feat: Add experimental plugin support. #225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
ade623a
78618b9
3577b4b
b2e3a31
c5ccacf
67e2fe2
054b814
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,9 @@ | ||
| /// Represents the credential type used by the SDK. The credential type is | ||
| /// determined by the platform the SDK is running on. | ||
| enum CredentialType { | ||
| /// The SDK is using a mobile key credential. | ||
| mobileKey, | ||
|
|
||
| /// The SDK is using a client-side ID. | ||
| clientSideId, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import 'hook.dart'; | ||
|
|
||
| List<Hook>? combineHooks(List<Hook>? baseHooks, List<Hook>? extendedHooks) { | ||
| if (baseHooks == null) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If both are null, that is fine as well, we just end up returning null, which the calling code will then pass to an optional constructor argument. |
||
| return extendedHooks; | ||
| } | ||
| if (extendedHooks == null) { | ||
| return baseHooks; | ||
| } | ||
| List<Hook> combined = []; | ||
| combined.addAll(baseHooks); | ||
| combined.addAll(extendedHooks); | ||
| return combined; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exposed the credential type here on the common-client. It isn't exposed on the SDK client, but exposing it would probably be fine if we choose to in the future. |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file isn't strictly part of the plugins support. We have not released hooks yet and this should be a readonly list. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import 'dart:collection'; | ||
| import 'dart:math'; | ||
|
|
||
| import 'package:launchdarkly_dart_common/launchdarkly_dart_common.dart'; | ||
|
|
@@ -129,7 +130,7 @@ abstract class LDCommonConfig { | |
| final List<String> globalPrivateAttributes; | ||
|
|
||
| /// An initial list of hooks. | ||
| final List<Hook>? hooks; | ||
| final UnmodifiableListView<Hook>? hooks; | ||
|
|
||
| LDCommonConfig(this.sdkCredential, this.autoEnvAttributes, | ||
| {this.applicationInfo, | ||
|
|
@@ -142,7 +143,7 @@ abstract class LDCommonConfig { | |
| DataSourceConfig? dataSourceConfig, | ||
| bool? allAttributesPrivate, | ||
| List<String>? globalPrivateAttributes, | ||
| this.hooks}) | ||
| List<Hook>? hooks}) | ||
| : httpProperties = httpProperties ?? HttpProperties(), | ||
| serviceEndpoints = | ||
| serviceEndpoints ?? client_endpoints.ServiceEndpoints(), | ||
|
|
@@ -153,7 +154,8 @@ abstract class LDCommonConfig { | |
| dataSourceConfig = dataSourceConfig ?? DataSourceConfig(), | ||
| allAttributesPrivate = | ||
| allAttributesPrivate ?? DefaultConfig.allAttributesPrivate, | ||
| globalPrivateAttributes = globalPrivateAttributes ?? []; | ||
| globalPrivateAttributes = globalPrivateAttributes ?? [], | ||
| hooks = hooks != null ? UnmodifiableListView(List.from(hooks)) : null; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make an unmodifiable list from a copy of the input list. If a copy isn't made, then the original list could be mutated and the view would reflect those mutations. |
||
| } | ||
|
|
||
| /// Enable / disable options for Auto Environment Attributes functionality. When enabled, the SDK will automatically | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import 'package:launchdarkly_dart_common/launchdarkly_dart_common.dart' | ||
| show LDLogger; | ||
|
|
||
| import 'plugin.dart'; | ||
| import '../hooks/hook.dart'; | ||
|
|
||
| List<Hook>? safeGetHooks<TClient>( | ||
| List<PluginBase<TClient>>? plugins, LDLogger logger) { | ||
| if (plugins == null) return null; | ||
|
|
||
| return plugins | ||
| .map<List<Hook>>((plugin) { | ||
| try { | ||
| return plugin.hooks; | ||
| } catch (err) { | ||
| logger.warn( | ||
| 'Exception thrown getting hooks for plugin ${plugin.metadata.name}. Unable to get hooks for plugin.'); | ||
| } | ||
| return []; | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }) | ||
| .expand<Hook>((hooks) => hooks) | ||
| .toList(); | ||
| } | ||
|
|
||
| void safeRegisterPlugins<TClient>( | ||
| TClient client, | ||
| PluginEnvironmentMetadata metadata, | ||
| List<PluginBase<TClient>>? plugins, | ||
| LDLogger logger) { | ||
| plugins?.forEach((plugin) { | ||
| try { | ||
| plugin.register(client, metadata); | ||
| } catch (err) { | ||
| logger.warn( | ||
| 'Exception thrown when registering plugin ${plugin.metadata.name}'); | ||
| } | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| import 'package:launchdarkly_dart_common/launchdarkly_dart_common.dart' | ||
| show ApplicationInfo; | ||
|
|
||
| import '../config/defaults/credential_type.dart'; | ||
| import '../hooks/hook.dart' show Hook; | ||
|
|
||
| /// Metadata about a plugin implementation. | ||
| /// | ||
| /// May be used in logs and analytics to identify the plugin. | ||
| final class PluginMetadata { | ||
| /// The name of the plugin. | ||
| final String name; | ||
|
|
||
| const PluginMetadata({required this.name}); | ||
|
|
||
| @override | ||
| String toString() { | ||
| return 'PluginMetadata{name: $name}'; | ||
| } | ||
| } | ||
|
|
||
| /// Metadata about the SDK that is running the plugin. | ||
| final class PluginSdkMetadata { | ||
| /// The name of the SDK. | ||
| final String name; | ||
|
|
||
| /// The version of the SDK. | ||
| final String version; | ||
|
|
||
| /// If this is a wrapper SDK, then this is the name of the wrapper. | ||
| final String? wrapperName; | ||
|
|
||
| /// If this is a wrapper SDK, then this is the version of the wrapper. | ||
| final String? wrapperVersion; | ||
|
|
||
| PluginSdkMetadata( | ||
jsonbailey marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| {required this.name, | ||
| required this.version, | ||
| this.wrapperName, | ||
| this.wrapperVersion}); | ||
|
|
||
| @override | ||
| String toString() { | ||
| return 'PluginSdkMetadata{name: $name, version: $version,' | ||
| ' wrapperName: $wrapperName, wrapperVersion: $wrapperVersion}'; | ||
| } | ||
| } | ||
|
|
||
| /// Information about the credential used to initialize the SDK. | ||
| final class PluginCredentialInfo { | ||
| /// The type of credential. | ||
| final CredentialType type; | ||
|
|
||
| /// The value of the credential. | ||
| final String value; | ||
|
|
||
| PluginCredentialInfo({required this.type, required this.value}); | ||
|
|
||
| @override | ||
| String toString() { | ||
| return 'PluginCredentialInfo{type: $type, value: $value}'; | ||
| } | ||
| } | ||
|
|
||
| /// Metadata about the environment where the plugin is running. | ||
| final class PluginEnvironmentMetadata { | ||
| /// Metadata about the SDK that is running the plugin. | ||
| final PluginSdkMetadata sdk; | ||
|
|
||
| /// Metadata about the application where the LaunchDarkly SDK is running. | ||
| /// | ||
| /// Plugins only have access to application info collected during | ||
| /// configuration. Application information collected by environment reporting | ||
| /// is not available. | ||
| /// | ||
| /// If access to the environment reporting information is required, then it | ||
| /// is available via the [LDContext] by using hooks. | ||
| /// | ||
| /// Only present if any application information is available. | ||
| final ApplicationInfo? application; | ||
|
|
||
| /// Information about the credential used to initialize the SDK. | ||
| final PluginCredentialInfo credential; | ||
|
|
||
| PluginEnvironmentMetadata( | ||
| {required this.sdk, required this.credential, this.application}); | ||
|
|
||
| @override | ||
| String toString() { | ||
| return 'PluginEnvironmentMetadata{sdk: $sdk, credential: $credential,' | ||
| ' application: $application}'; | ||
| } | ||
| } | ||
|
|
||
| /// Base class from which all plugins must derive. | ||
| /// | ||
| /// Implementation note: SDK packages must export a specialized version of this | ||
| /// for their specific TClient type. This class cannot provide a type, because | ||
| /// it would limit the API to methods available in the base client. | ||
| abstract base class PluginBase<TClient> { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generic base class that is then specialized in the leaf-node SDK. |
||
| /// Metadata associated with this plugin. | ||
| /// | ||
| /// Plugin implementations must implement this property. | ||
| /// ```dart | ||
| /// final _metadata = PluginMetadata(name: 'MyPluginName'); | ||
| /// | ||
| /// @override | ||
| /// PluginMetadata get metadata => _metadata; | ||
| /// ``` | ||
| PluginMetadata get metadata; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No body for the getter makes this abstract. |
||
|
|
||
| /// Registers the plugin with the SDK. Called once during SDK initialization. | ||
| /// | ||
| /// The SDK initialization will typically not have been completed at this | ||
| /// point, so the plugin should take appropriate actions to ensure the SDK is | ||
| /// ready before sending track events or evaluating flags. For example the | ||
| /// plugin could wait for the [Hook.afterIdentify] stage to indicate success | ||
| /// before tracking any events. | ||
| /// | ||
| /// The [client] the plugin is registered with. | ||
| void register( | ||
| TClient client, PluginEnvironmentMetadata environmentMetadata) {} | ||
|
|
||
| /// Hooks which are bundled with this plugin. | ||
| /// | ||
| /// Implementations should override this method to return their bundled | ||
| /// hooks. | ||
| /// ```dart | ||
| /// @override | ||
| /// List<Hook> get hooks => [MyBundledHook()]; | ||
| /// ``` | ||
| List<Hook> get hooks => []; | ||
|
|
||
| PluginBase(); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is exported now so that it can be a component of the meta-data.