wingify-fme-ruby-sdk 1.50.0
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 +7 -0
- data/lib/resources/debug_messages.json +21 -0
- data/lib/resources/error_messages.json +63 -0
- data/lib/resources/info_messages.json +43 -0
- data/lib/resources/warn_messages.json +1 -0
- data/lib/vwo.rb +40 -0
- data/lib/wingify/api/get_flag.rb +244 -0
- data/lib/wingify/api/set_attribute.rb +57 -0
- data/lib/wingify/api/track_event.rb +80 -0
- data/lib/wingify/constants/constants.rb +106 -0
- data/lib/wingify/decorators/storage_decorator.rb +82 -0
- data/lib/wingify/enums/api_enum.rb +23 -0
- data/lib/wingify/enums/campaign_type_enum.rb +19 -0
- data/lib/wingify/enums/debug_category_enum.rb +20 -0
- data/lib/wingify/enums/decision_types_enum.rb +18 -0
- data/lib/wingify/enums/event_enum.rb +22 -0
- data/lib/wingify/enums/headers_enum.rb +20 -0
- data/lib/wingify/enums/hooks_enum.rb +17 -0
- data/lib/wingify/enums/http_method_enum.rb +18 -0
- data/lib/wingify/enums/log_level_enum.rb +21 -0
- data/lib/wingify/enums/log_level_to_number.rb +27 -0
- data/lib/wingify/enums/status_enum.rb +19 -0
- data/lib/wingify/enums/storage_enum.rb +22 -0
- data/lib/wingify/enums/url_enum.rb +22 -0
- data/lib/wingify/models/campaign/campaign_model.rb +192 -0
- data/lib/wingify/models/campaign/feature_model.rb +111 -0
- data/lib/wingify/models/campaign/impact_campaign_model.rb +38 -0
- data/lib/wingify/models/campaign/metric_model.rb +44 -0
- data/lib/wingify/models/campaign/rule_model.rb +56 -0
- data/lib/wingify/models/campaign/variable_model.rb +51 -0
- data/lib/wingify/models/campaign/variation_model.rb +137 -0
- data/lib/wingify/models/gateway_service_model.rb +39 -0
- data/lib/wingify/models/schemas/settings_schema_validation.rb +104 -0
- data/lib/wingify/models/settings/settings_model.rb +101 -0
- data/lib/wingify/models/storage/storage_data_model.rb +44 -0
- data/lib/wingify/models/user/context_model.rb +154 -0
- data/lib/wingify/models/user/context_vwo_model.rb +38 -0
- data/lib/wingify/models/user/get_flag_response.rb +50 -0
- data/lib/wingify/models/vwo_options_model.rb +129 -0
- data/lib/wingify/packages/decision_maker/decision_maker.rb +60 -0
- data/lib/wingify/packages/logger/core/log_manager.rb +92 -0
- data/lib/wingify/packages/logger/core/transport_manager.rb +76 -0
- data/lib/wingify/packages/logger/log_message_builder.rb +70 -0
- data/lib/wingify/packages/logger/logger.rb +38 -0
- data/lib/wingify/packages/logger/transports/console_transport.rb +49 -0
- data/lib/wingify/packages/network_layer/client/network_client.rb +276 -0
- data/lib/wingify/packages/network_layer/handlers/request_handler.rb +37 -0
- data/lib/wingify/packages/network_layer/manager/network_manager.rb +145 -0
- data/lib/wingify/packages/network_layer/models/global_request_model.rb +105 -0
- data/lib/wingify/packages/network_layer/models/request_model.rb +179 -0
- data/lib/wingify/packages/network_layer/models/response_model.rb +62 -0
- data/lib/wingify/packages/segmentation_evaluator/core/segmentation_manager.rb +77 -0
- data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_regex_enum.rb +29 -0
- data/lib/wingify/packages/segmentation_evaluator/enums/segment_operand_value_enum.rb +26 -0
- data/lib/wingify/packages/segmentation_evaluator/enums/segment_operator_value_enum.rb +33 -0
- data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_evaluator.rb +218 -0
- data/lib/wingify/packages/segmentation_evaluator/evaluators/segment_operand_evaluator.rb +415 -0
- data/lib/wingify/packages/segmentation_evaluator/utils/segment_util.rb +44 -0
- data/lib/wingify/packages/storage/connector.rb +26 -0
- data/lib/wingify/packages/storage/storage.rb +47 -0
- data/lib/wingify/services/batch_event_queue.rb +179 -0
- data/lib/wingify/services/campaign_decision_service.rb +161 -0
- data/lib/wingify/services/hooks_service.rb +51 -0
- data/lib/wingify/services/logger_service.rb +114 -0
- data/lib/wingify/services/settings_service.rb +178 -0
- data/lib/wingify/services/storage_service.rb +66 -0
- data/lib/wingify/utils/batch_event_dispatcher_util.rb +178 -0
- data/lib/wingify/utils/brand_context.rb +33 -0
- data/lib/wingify/utils/brand_util.rb +53 -0
- data/lib/wingify/utils/campaign_util.rb +284 -0
- data/lib/wingify/utils/data_type_util.rb +105 -0
- data/lib/wingify/utils/debugger_service_util.rb +40 -0
- data/lib/wingify/utils/decision_util.rb +259 -0
- data/lib/wingify/utils/event_util.rb +55 -0
- data/lib/wingify/utils/function_util.rb +141 -0
- data/lib/wingify/utils/gateway_service_util.rb +101 -0
- data/lib/wingify/utils/impression_util.rb +66 -0
- data/lib/wingify/utils/log_message_util.rb +42 -0
- data/lib/wingify/utils/meg_util.rb +357 -0
- data/lib/wingify/utils/network_util.rb +503 -0
- data/lib/wingify/utils/rule_evaluation_util.rb +57 -0
- data/lib/wingify/utils/settings_util.rb +38 -0
- data/lib/wingify/utils/usage_stats_util.rb +119 -0
- data/lib/wingify/utils/uuid_util.rb +96 -0
- data/lib/wingify/wingify_builder.rb +261 -0
- data/lib/wingify/wingify_client.rb +227 -0
- data/lib/wingify.rb +117 -0
- metadata +327 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Copyright 2024-2026 Wingify Software Pvt. Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
require_relative '../constants/constants'
|
|
17
|
+
require_relative '../enums/log_level_to_number'
|
|
18
|
+
|
|
19
|
+
# Manages usage statistics for the SDK.
|
|
20
|
+
# Tracks various features and configurations being used by the client.
|
|
21
|
+
# Implements Singleton pattern to ensure a single instance.
|
|
22
|
+
class UsageStatsUtil
|
|
23
|
+
@instance = nil
|
|
24
|
+
@usage_stats_data = {}
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
# Provides access to the singleton instance of UsageStatsUtil.
|
|
28
|
+
#
|
|
29
|
+
# @return [UsageStatsUtil] The single instance of UsageStatsUtil
|
|
30
|
+
def instance
|
|
31
|
+
@instance ||= new
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Sets usage statistics based on provided options.
|
|
35
|
+
# Maps various SDK features and configurations to boolean flags.
|
|
36
|
+
#
|
|
37
|
+
# @param options [Hash] Configuration options for the SDK
|
|
38
|
+
def set_usage_stats(options)
|
|
39
|
+
instance.set_usage_stats(options)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Retrieves the current usage statistics.
|
|
43
|
+
#
|
|
44
|
+
# @return [Hash] Record containing boolean flags for various SDK features in use
|
|
45
|
+
def get_usage_stats
|
|
46
|
+
instance.get_usage_stats
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def clear_usage_stats
|
|
50
|
+
instance.clear_usage_stats
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private_class_method :new
|
|
55
|
+
|
|
56
|
+
def initialize
|
|
57
|
+
@usage_stats_data = {}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def set_usage_stats(options)
|
|
61
|
+
storage = options[:storage]
|
|
62
|
+
logger = options[:logger]
|
|
63
|
+
event_batching = options[:batch_event_data]
|
|
64
|
+
integrations = options[:integrations]
|
|
65
|
+
poll_interval = options[:poll_interval]
|
|
66
|
+
vwo_meta = options[:_vwo_meta]
|
|
67
|
+
gateway_service = options[:gateway_service]
|
|
68
|
+
threading = options[:threading]
|
|
69
|
+
|
|
70
|
+
data = {}
|
|
71
|
+
|
|
72
|
+
data[:a] = SettingsService.instance.account_id
|
|
73
|
+
data[:env] = SettingsService.instance.sdk_key
|
|
74
|
+
data[:ig] = 1 if integrations
|
|
75
|
+
data[:eb] = 1 if event_batching
|
|
76
|
+
data[:gs] = 1 if gateway_service
|
|
77
|
+
|
|
78
|
+
# if logger has transport or transports, then it is custom logger
|
|
79
|
+
if logger && (logger.key?(:transport) || logger.key?(:transports))
|
|
80
|
+
data[:cl] = 1
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
data[:ss] = 1 if storage
|
|
84
|
+
|
|
85
|
+
if logger && logger.key?(:level)
|
|
86
|
+
data[:ll] = LogLevelToNumber.to_number(logger[:level]) || -1
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
data[:pi] = poll_interval if poll_interval
|
|
90
|
+
|
|
91
|
+
if vwo_meta && vwo_meta.key?(:ea)
|
|
92
|
+
data[:_ea] = 1
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# check if threading is not passed or is if passed then enabled should be true
|
|
96
|
+
if !threading || (threading && threading[:enabled] == true)
|
|
97
|
+
data[:th] = 1
|
|
98
|
+
# check if max_pool_size is passed
|
|
99
|
+
if threading && threading[:max_pool_size]
|
|
100
|
+
data[:th_mps] = threading[:max_pool_size]
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if defined?(RUBY_VERSION)
|
|
105
|
+
data[:lv] = RUBY_VERSION
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
@usage_stats_data = data
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def get_usage_stats
|
|
112
|
+
@usage_stats_data
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def clear_usage_stats
|
|
116
|
+
@usage_stats_data = {}
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Copyright 2024-2026 Wingify Software Pvt. Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
require 'uuidtools'
|
|
16
|
+
require 'securerandom'
|
|
17
|
+
require_relative '../constants/constants'
|
|
18
|
+
|
|
19
|
+
class UUIDUtil
|
|
20
|
+
# Generates a random UUID based on an API key.
|
|
21
|
+
#
|
|
22
|
+
# @param sdk_key [String] The API key used to generate a namespace for the UUID.
|
|
23
|
+
# @return [String] A random UUID string.
|
|
24
|
+
def self.get_random_uuid(sdk_key)
|
|
25
|
+
namespace = UUIDTools::UUID.sha1_create(UUIDTools::UUID_DNS_NAMESPACE, sdk_key)
|
|
26
|
+
random_uuid = UUIDTools::UUID.sha1_create(namespace, SecureRandom.uuid)
|
|
27
|
+
random_uuid.to_s
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Generates a UUID for a user based on their user_id and account_id.
|
|
31
|
+
#
|
|
32
|
+
# @param user_id [String] The user's ID.
|
|
33
|
+
# @param account_id [String] The account ID associated with the user.
|
|
34
|
+
# @return [String] A UUID string formatted without dashes and in uppercase.
|
|
35
|
+
def self.get_uuid(user_id, account_id)
|
|
36
|
+
vwo_namespace = UUIDTools::UUID.sha1_create(UUIDTools::UUID_URL_NAMESPACE, Constants::SEED_URL)
|
|
37
|
+
user_id_namespace = generate_uuid(account_id, vwo_namespace)
|
|
38
|
+
uuid_for_user_id_account_id = generate_uuid(user_id, user_id_namespace)
|
|
39
|
+
|
|
40
|
+
uuid_for_user_id_account_id.to_s.delete('-').upcase
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Helper function to generate a UUID v5 based on a name and a namespace.
|
|
44
|
+
#
|
|
45
|
+
# @param name [String] The name from which to generate the UUID.
|
|
46
|
+
# @param namespace [UUIDTools::UUID] The namespace used to generate the UUID.
|
|
47
|
+
# @return [UUIDTools::UUID] A UUID string or nil if inputs are invalid.
|
|
48
|
+
def self.generate_uuid(name, namespace)
|
|
49
|
+
return nil if name.nil? || namespace.nil?
|
|
50
|
+
# Convert name to string to handle integer inputs
|
|
51
|
+
name_str = name.to_s
|
|
52
|
+
UUIDTools::UUID.sha1_create(namespace, name_str)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Checks if the given ID is a valid Web UUID.
|
|
56
|
+
#
|
|
57
|
+
# @param id [String] The ID to check.
|
|
58
|
+
# @return [Boolean] True if the ID is a valid Web UUID, false otherwise.
|
|
59
|
+
def self.web_uuid?(id)
|
|
60
|
+
return false unless id.is_a?(String)
|
|
61
|
+
|
|
62
|
+
!!(id =~ Constants::WEB_UUID_REGEX)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Generates a UUID for a user based on their user_id and account_id.
|
|
66
|
+
#
|
|
67
|
+
# @param settings [SettingsModel] The settings of the VWO client.
|
|
68
|
+
# @param context [Hash] The context of the user.
|
|
69
|
+
# @param api_name [String] The name of the API called.
|
|
70
|
+
# @return [String] A UUID string formatted without dashes and in uppercase.
|
|
71
|
+
def self.get_uuid_from_context(settings, context, api_name)
|
|
72
|
+
if settings.get_is_web_connectivity_enabled != false
|
|
73
|
+
# if web connectivity is enabled, check if context[:id] is a valid web UUID
|
|
74
|
+
if context && web_uuid?(context[:id])
|
|
75
|
+
# if context[:id] is a valid web UUID, set it as uuid
|
|
76
|
+
LoggerService.log(LogLevelEnum::DEBUG, "WEB_UUID_FOUND", {apiName: api_name, uuid: context[:id]})
|
|
77
|
+
return context[:id]
|
|
78
|
+
else
|
|
79
|
+
# if context[:useIdForWeb] is true and context[:id] is not a valid web UUID, throw error
|
|
80
|
+
if context && context[:useIdForWeb] == true
|
|
81
|
+
raise StandardError, 'UUID passed in context.id is not a valid UUID'
|
|
82
|
+
end
|
|
83
|
+
return get_uuid(
|
|
84
|
+
context[:id].to_s,
|
|
85
|
+
SettingsService.instance.account_id.to_s
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
# if web connectivity is disabled, fallback to server-side UUID derivation
|
|
90
|
+
return get_uuid(
|
|
91
|
+
context[:id].to_s,
|
|
92
|
+
SettingsService.instance.account_id.to_s
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Copyright 2024-2026 Wingify Software Pvt. Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# require 'json'
|
|
16
|
+
# require 'uri'
|
|
17
|
+
require_relative './enums/log_level_enum'
|
|
18
|
+
require_relative 'wingify_client'
|
|
19
|
+
require_relative './services/settings_service'
|
|
20
|
+
require_relative './packages/storage/storage'
|
|
21
|
+
require_relative './packages/network_layer/manager/network_manager'
|
|
22
|
+
require_relative './packages/segmentation_evaluator/core/segmentation_manager'
|
|
23
|
+
require_relative './services/logger_service'
|
|
24
|
+
require_relative './services/batch_event_queue'
|
|
25
|
+
require_relative './utils/function_util'
|
|
26
|
+
require_relative './constants/constants'
|
|
27
|
+
require_relative './utils/usage_stats_util'
|
|
28
|
+
require_relative './enums/api_enum'
|
|
29
|
+
require_relative './utils/batch_event_dispatcher_util'
|
|
30
|
+
|
|
31
|
+
class WingifyBuilder
|
|
32
|
+
attr_reader :settings, :storage, :log_manager, :is_settings_fetch_in_progress, :wingify_instance, :is_valid_poll_interval_passed_from_init
|
|
33
|
+
|
|
34
|
+
# Initialize the VWOBuilder with the given options
|
|
35
|
+
# @param options [Hash] The options for the VWOBuilder
|
|
36
|
+
def initialize(options)
|
|
37
|
+
@options = options
|
|
38
|
+
@settings = nil
|
|
39
|
+
@storage = nil
|
|
40
|
+
@log_manager = nil
|
|
41
|
+
@is_settings_fetch_in_progress = false
|
|
42
|
+
@is_valid_poll_interval_passed_from_init = false
|
|
43
|
+
@wingify_instance = nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Initializes the batch event processing system
|
|
47
|
+
# Validates batch event settings and configures the BatchEventsQueue
|
|
48
|
+
# Sets up event dispatcher and flushes any existing events
|
|
49
|
+
# @raise [StandardError] If batch event configuration is invalid
|
|
50
|
+
def init_batch
|
|
51
|
+
# if gateway service is configured, then do not initialize batch event queue
|
|
52
|
+
if SettingsService.instance.is_gateway_service_provided
|
|
53
|
+
LoggerService.log(LogLevelEnum::INFO, "GATEWAY_AND_BATCH_EVENTS_CONFIG_MISMATCH")
|
|
54
|
+
return self
|
|
55
|
+
end
|
|
56
|
+
begin
|
|
57
|
+
if @options.key?(:batch_event_data)
|
|
58
|
+
if @options[:batch_event_data].is_a?(Hash)
|
|
59
|
+
# Validate batch event parameters
|
|
60
|
+
events_per_request = @options[:batch_event_data][:events_per_request]
|
|
61
|
+
request_time_interval = @options[:batch_event_data][:request_time_interval]
|
|
62
|
+
|
|
63
|
+
if (!events_per_request.is_a?(Numeric) || events_per_request <= 0) &&
|
|
64
|
+
(!request_time_interval.is_a?(Numeric) || request_time_interval <= 0)
|
|
65
|
+
LoggerService.log(LogLevelEnum::ERROR, "INVALID_BATCH_EVENTS_CONFIG", { an: ApiEnum::INIT})
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
BatchEventsQueue.configure(
|
|
69
|
+
@options[:batch_event_data].merge(
|
|
70
|
+
{
|
|
71
|
+
account_id: @options[:account_id],
|
|
72
|
+
dispatcher: method(:dispatcher)
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
@batch_event_data = @options[:batch_event_data]
|
|
77
|
+
else
|
|
78
|
+
LoggerService.log(LogLevelEnum::ERROR, "INVALID_BATCH_EVENTS_CONFIG", { an: ApiEnum::INIT})
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
rescue StandardError => e
|
|
82
|
+
LoggerService.log(LogLevelEnum::ERROR, "FAILED_TO_INITIALIZE_SERVICE", { service: 'Batch Event Queue', err: e.message, an: ApiEnum::INIT})
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Set the network manager
|
|
87
|
+
# @return [VWOBuilder] The VWOBuilder instance
|
|
88
|
+
def set_network_manager
|
|
89
|
+
begin
|
|
90
|
+
network_instance = NetworkManager.instance(@options[:threading] || {})
|
|
91
|
+
retry_config = @options[:retry_config]
|
|
92
|
+
client = (@options[:network] && @options[:network][:client]) ? @options[:network][:client] : nil
|
|
93
|
+
network_instance.attach_client(client, retry_config)
|
|
94
|
+
LoggerService.log(LogLevelEnum::DEBUG, "SERVICE_INITIALIZED", {service: "Network Layer"})
|
|
95
|
+
network_instance.get_config.set_development_mode(@options[:is_development_mode]) if @options[:is_development_mode]
|
|
96
|
+
self
|
|
97
|
+
rescue StandardError => e
|
|
98
|
+
LoggerService.log(LogLevelEnum::ERROR, "FAILED_TO_INITIALIZE_SERVICE", { service: 'Network Manager', err: e.message, an: ApiEnum::INIT})
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Set the segmentation manager
|
|
104
|
+
# @return [VWOBuilder] The VWOBuilder instance
|
|
105
|
+
def set_segmentation
|
|
106
|
+
SegmentationManager.instance.attach_evaluator(@options[:segmentation]) if @options[:segmentation]
|
|
107
|
+
LoggerService.log(LogLevelEnum::DEBUG, "SERVICE_INITIALIZED", {service: "Segmentation Evaluator"})
|
|
108
|
+
self
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Fetch the settings from the server
|
|
112
|
+
# @param force [Boolean] Whether to force the fetch of settings
|
|
113
|
+
# @return [Hash] The settings
|
|
114
|
+
def fetch_settings(force = false)
|
|
115
|
+
return @settings if !force && @settings
|
|
116
|
+
|
|
117
|
+
@is_settings_fetch_in_progress = true
|
|
118
|
+
settings = SettingsService.new(@options).get_settings()
|
|
119
|
+
@is_settings_fetch_in_progress = false
|
|
120
|
+
@settings = settings unless force
|
|
121
|
+
settings
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Get the settings from the server
|
|
125
|
+
# @param force [Boolean] Whether to force the fetch of settings
|
|
126
|
+
# @return [Hash] The settings
|
|
127
|
+
def get_settings(force = false)
|
|
128
|
+
return @settings if !force && @settings
|
|
129
|
+
|
|
130
|
+
begin
|
|
131
|
+
fetch_settings(force)
|
|
132
|
+
rescue StandardError => e
|
|
133
|
+
LoggerService.log(LogLevelEnum::ERROR, "ERROR_FETCHING_SETTINGS", { err: e.message, an: ApiEnum::INIT})
|
|
134
|
+
{}
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Set the storage
|
|
139
|
+
# @return [VWOBuilder] The VWOBuilder instance
|
|
140
|
+
def set_storage
|
|
141
|
+
if @options[:storage]
|
|
142
|
+
@storage = Storage.instance.attach_connector(@options[:storage])
|
|
143
|
+
Storage.instance.is_storage_enabled = true
|
|
144
|
+
else
|
|
145
|
+
@storage = nil
|
|
146
|
+
end
|
|
147
|
+
LoggerService.log(LogLevelEnum::DEBUG, "SERVICE_INITIALIZED", {service: "Storage"})
|
|
148
|
+
self
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Set the settings service
|
|
152
|
+
# @return [VWOBuilder] The VWOBuilder instance
|
|
153
|
+
def set_settings_service
|
|
154
|
+
@settings_service = SettingsService.new(@options)
|
|
155
|
+
self
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Set the logger
|
|
159
|
+
# @return [VWOBuilder] The VWOBuilder instance
|
|
160
|
+
def set_logger
|
|
161
|
+
begin
|
|
162
|
+
@log_manager = LoggerService.new(@options[:logger] || {})
|
|
163
|
+
LoggerService.log(LogLevelEnum::DEBUG, "SERVICE_INITIALIZED", {service: "Logger"})
|
|
164
|
+
rescue => e
|
|
165
|
+
puts "Got error while setting logger: #{e.message}"
|
|
166
|
+
end
|
|
167
|
+
self
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Initialize the polling
|
|
171
|
+
# @return [VWOBuilder] The VWOBuilder instance
|
|
172
|
+
def init_polling
|
|
173
|
+
poll_interval = @options[:poll_interval]
|
|
174
|
+
|
|
175
|
+
if poll_interval && poll_interval.is_a?(Numeric) && poll_interval >= 1000
|
|
176
|
+
# this is to check if the poll_interval passed in options is valid
|
|
177
|
+
@is_valid_poll_interval_passed_from_init = true
|
|
178
|
+
check_and_poll
|
|
179
|
+
elsif poll_interval
|
|
180
|
+
# only log error if poll_interval is present in options
|
|
181
|
+
LoggerService.log(LogLevelEnum::ERROR, "INVALID_POLLING_CONFIGURATION", {
|
|
182
|
+
key: 'poll_interval',
|
|
183
|
+
correctType: 'number >= 1000',
|
|
184
|
+
an: ApiEnum::INIT
|
|
185
|
+
})
|
|
186
|
+
end
|
|
187
|
+
self
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Build the WingifyClient instance
|
|
191
|
+
# @param settings [Hash] The settings for the client instance
|
|
192
|
+
# @return [WingifyClient] The client instance
|
|
193
|
+
def build(settings)
|
|
194
|
+
@wingify_instance = WingifyClient.new(settings, @options)
|
|
195
|
+
# if poll_interval is not present in options, set it to the pollInterval from settings
|
|
196
|
+
update_poll_interval_and_check_and_poll(settings)
|
|
197
|
+
@wingify_instance
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def update_poll_interval_and_check_and_poll(settings, should_check_and_poll = true)
|
|
201
|
+
# only update the poll_interval if it poll_interval is not valid or not present in options
|
|
202
|
+
if !@is_valid_poll_interval_passed_from_init
|
|
203
|
+
@options[:poll_interval] = settings["pollInterval"] || Constants::POLLING_INTERVAL
|
|
204
|
+
LoggerService.log(LogLevelEnum::DEBUG, "USING_POLL_INTERVAL_FROM_SETTINGS", {
|
|
205
|
+
source: settings["pollInterval"] ? 'settings' : 'default',
|
|
206
|
+
pollInterval: @options[:poll_interval]
|
|
207
|
+
})
|
|
208
|
+
end
|
|
209
|
+
# should_check_and_poll will be true only when we are updating the poll_interval first time from self.build method
|
|
210
|
+
# if we are updating the poll_interval already running polling, we don't need to check and poll again
|
|
211
|
+
if should_check_and_poll && !@is_valid_poll_interval_passed_from_init
|
|
212
|
+
check_and_poll
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# This method is used to check and poll the settings from the server
|
|
217
|
+
# @return [VWOBuilder] The VWOBuilder instance
|
|
218
|
+
def check_and_poll
|
|
219
|
+
@thread_pool = NetworkManager.instance.get_client.get_thread_pool
|
|
220
|
+
@thread_pool.post do
|
|
221
|
+
loop do
|
|
222
|
+
sleep(@options[:poll_interval]/ 1000.0)
|
|
223
|
+
begin
|
|
224
|
+
latest_settings = fetch_settings(true)
|
|
225
|
+
if latest_settings && latest_settings.to_json != @settings.to_json
|
|
226
|
+
@settings = latest_settings
|
|
227
|
+
LoggerService.log(LogLevelEnum::INFO, "POLLING_SET_SETTINGS")
|
|
228
|
+
@wingify_instance.update_settings(latest_settings.clone, false) if @wingify_instance
|
|
229
|
+
update_poll_interval_and_check_and_poll(latest_settings, false)
|
|
230
|
+
elsif latest_settings
|
|
231
|
+
LoggerService.log(LogLevelEnum::INFO, "POLLING_NO_CHANGE_IN_SETTINGS")
|
|
232
|
+
end
|
|
233
|
+
rescue StandardError => e
|
|
234
|
+
LoggerService.log(LogLevelEnum::ERROR, "ERROR_UPDATING_SETTINGS", { err: e.message, an: ApiEnum::INIT})
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def dispatcher(events, callback)
|
|
241
|
+
BatchEventDispatcherUtil.dispatch(
|
|
242
|
+
{
|
|
243
|
+
ev: events
|
|
244
|
+
},
|
|
245
|
+
callback,
|
|
246
|
+
{
|
|
247
|
+
a: @options[:account_id],
|
|
248
|
+
env: @options[:sdk_key]
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Initialize the usage stats
|
|
254
|
+
# @return [VWOBuilder] The VWOBuilder instance
|
|
255
|
+
def init_usage_stats
|
|
256
|
+
return self if @options[:is_usage_stats_disabled]
|
|
257
|
+
|
|
258
|
+
UsageStatsUtil.set_usage_stats(@options)
|
|
259
|
+
self
|
|
260
|
+
end
|
|
261
|
+
end
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Copyright 2024-2026 Wingify Software Pvt. Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
require_relative 'services/settings_service'
|
|
16
|
+
require_relative 'models/user/context_model'
|
|
17
|
+
require_relative 'api/get_flag'
|
|
18
|
+
require_relative 'api/set_attribute'
|
|
19
|
+
require_relative 'api/track_event'
|
|
20
|
+
require_relative 'utils/settings_util'
|
|
21
|
+
require_relative 'services/logger_service'
|
|
22
|
+
require_relative 'enums/log_level_enum'
|
|
23
|
+
require_relative 'utils/network_util'
|
|
24
|
+
require_relative 'models/schemas/settings_schema_validation'
|
|
25
|
+
require_relative 'services/batch_event_queue'
|
|
26
|
+
require_relative 'enums/api_enum'
|
|
27
|
+
require_relative 'utils/uuid_util'
|
|
28
|
+
|
|
29
|
+
class WingifyClient
|
|
30
|
+
attr_accessor :settings, :original_settings
|
|
31
|
+
attr_reader :options
|
|
32
|
+
|
|
33
|
+
def initialize(settings, options)
|
|
34
|
+
@options = options
|
|
35
|
+
@settings = settings
|
|
36
|
+
@original_settings = settings.dup
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
set_settings_and_add_campaigns_to_rules(settings, self)
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
LoggerService.log(LogLevelEnum::ERROR, "ERROR_ADDING_CAMPAIGNS_TO_RULES", { err: e.message, an: ApiEnum::INIT})
|
|
42
|
+
end
|
|
43
|
+
LoggerService.log(LogLevelEnum::INFO, "CLIENT_INITIALIZED")
|
|
44
|
+
self
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Get the flag for a given feature key and context
|
|
48
|
+
# @param feature_key [String] The key of the feature to get the flag for
|
|
49
|
+
# @param context [Hash] The context of the user
|
|
50
|
+
# @return [GetFlagResponse] The flag for the given feature key and context
|
|
51
|
+
def get_flag(feature_key, context)
|
|
52
|
+
api_name = 'get_flag'
|
|
53
|
+
uuid = nil
|
|
54
|
+
session_id = nil
|
|
55
|
+
|
|
56
|
+
# check if sessionId is present in context and should be non null and non empty
|
|
57
|
+
if context.is_a?(Hash) && context.key?(:sessionId) && !context[:sessionId].nil? && !context[:sessionId].to_s.empty?
|
|
58
|
+
session_id = context[:sessionId].to_i
|
|
59
|
+
else
|
|
60
|
+
session_id = Time.now.to_i
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
begin
|
|
64
|
+
hooks_service = HooksService.new(@options)
|
|
65
|
+
LoggerService.log(LogLevelEnum::DEBUG, "API_CALLED", {apiName: api_name})
|
|
66
|
+
unless context.is_a?(Hash)
|
|
67
|
+
raise TypeError, 'Invalid context'
|
|
68
|
+
end
|
|
69
|
+
unless context[:id].is_a?(String) && !context[:id].empty?
|
|
70
|
+
raise TypeError, 'Invalid context, id should be a non-empty string'
|
|
71
|
+
end
|
|
72
|
+
# make a copy of the context to avoid modifying the original context
|
|
73
|
+
context_copy = context.dup
|
|
74
|
+
uuid = UUIDUtil.get_uuid_from_context(@settings, context_copy, api_name)
|
|
75
|
+
# add the uuid to the context copy
|
|
76
|
+
context_copy[:uuid] = uuid
|
|
77
|
+
|
|
78
|
+
# Validate bucketingSeed: must be a non-empty, non-whitespace-only string
|
|
79
|
+
if context_copy.key?(:bucketingSeed)
|
|
80
|
+
seed = context_copy[:bucketingSeed]
|
|
81
|
+
if seed.nil? || seed.is_a?(Numeric) || seed.is_a?(Hash) || seed.is_a?(Array) || (seed.is_a?(String) && seed.strip.empty?)
|
|
82
|
+
LoggerService.log(LogLevelEnum::ERROR, "INVALID_BUCKETING_SEED", { apiName: api_name, type: seed.class.name })
|
|
83
|
+
context_copy.delete(:bucketingSeed)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
unless feature_key.is_a?(String) && !feature_key.empty?
|
|
88
|
+
raise TypeError, 'feature_key should be a non-empty string'
|
|
89
|
+
end
|
|
90
|
+
unless SettingsService.instance.is_settings_valid
|
|
91
|
+
raise TypeError, 'Invalid Settings'
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context_model = ContextModel.new.model_from_dictionary(context_copy)
|
|
95
|
+
FlagApi.new.get(feature_key, @settings, context_model, hooks_service)
|
|
96
|
+
rescue StandardError => e
|
|
97
|
+
LoggerService.log(LogLevelEnum::ERROR, "EXECUTION_FAILED", {apiName: api_name, err: e.message, an: ApiEnum::GET_FLAG})
|
|
98
|
+
GetFlagResponse.new(false, [], uuid, session_id)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Track an event with given properties and context
|
|
103
|
+
# @param event_name [String] The name of the event to track
|
|
104
|
+
# @param context [Hash] The context of the user
|
|
105
|
+
# @param event_properties [Hash] The properties of the event
|
|
106
|
+
# @return [Hash] The result of the event tracking
|
|
107
|
+
def track_event(event_name, context, event_properties = {})
|
|
108
|
+
api_name = 'track_event'
|
|
109
|
+
|
|
110
|
+
begin
|
|
111
|
+
hooks_service = HooksService.new(@options)
|
|
112
|
+
LoggerService.log(LogLevelEnum::DEBUG, "API_CALLED", {apiName: api_name})
|
|
113
|
+
|
|
114
|
+
unless context.is_a?(Hash) && context[:id].is_a?(String) && !context[:id].empty?
|
|
115
|
+
raise TypeError, 'Invalid context, id should be a non-empty string'
|
|
116
|
+
end
|
|
117
|
+
# make a copy of the context to avoid modifying the original context
|
|
118
|
+
context_copy = context.dup
|
|
119
|
+
context_copy[:uuid] = UUIDUtil.get_uuid_from_context(@settings, context_copy, api_name)
|
|
120
|
+
|
|
121
|
+
unless event_name.is_a?(String) && !event_name.empty?
|
|
122
|
+
raise TypeError, 'event_name should be a non-empty string'
|
|
123
|
+
end
|
|
124
|
+
unless event_properties.is_a?(Hash)
|
|
125
|
+
raise TypeError, 'event_properties should be a hash'
|
|
126
|
+
end
|
|
127
|
+
unless SettingsService.instance.is_settings_valid
|
|
128
|
+
raise TypeError, 'Invalid Settings'
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
context_model = ContextModel.new.model_from_dictionary(context_copy)
|
|
132
|
+
TrackApi.new.track(@settings, event_name, context_model, event_properties, hooks_service)
|
|
133
|
+
rescue StandardError => e
|
|
134
|
+
LoggerService.log(LogLevelEnum::ERROR, "EXECUTION_FAILED", {apiName: api_name, err: e.message, an: ApiEnum::TRACK_EVENT})
|
|
135
|
+
{ event_name: false }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Set attributes for a given context
|
|
140
|
+
# @param attributes [Hash] The attributes to set
|
|
141
|
+
# @param context [Hash] The context of the user
|
|
142
|
+
# @return [Hash] The result of the attribute setting
|
|
143
|
+
def set_attribute(attributes, context = nil)
|
|
144
|
+
api_name = 'set_attribute'
|
|
145
|
+
|
|
146
|
+
begin
|
|
147
|
+
LoggerService.log(LogLevelEnum::DEBUG, "API_CALLED", {apiName: api_name})
|
|
148
|
+
unless context.is_a?(Hash)
|
|
149
|
+
raise TypeError, 'Invalid context'
|
|
150
|
+
end
|
|
151
|
+
unless context[:id].is_a?(String) && !context[:id].empty?
|
|
152
|
+
raise TypeError, 'Invalid context, id should be a non-empty string'
|
|
153
|
+
end
|
|
154
|
+
# make a copy of the context to avoid modifying the original context
|
|
155
|
+
context_copy = context.dup
|
|
156
|
+
context_copy[:uuid] = UUIDUtil.get_uuid_from_context(@settings, context_copy, api_name)
|
|
157
|
+
|
|
158
|
+
unless attributes.is_a?(Hash) && !attributes.empty?
|
|
159
|
+
raise TypeError, 'Attributes should be a hash with key-value pairs and non-empty'
|
|
160
|
+
end
|
|
161
|
+
unless SettingsService.instance.is_settings_valid
|
|
162
|
+
raise TypeError, 'Invalid Settings'
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
context_model = ContextModel.new.model_from_dictionary(context_copy)
|
|
166
|
+
SetAttributeApi.new.set_attribute(attributes, context_model)
|
|
167
|
+
rescue StandardError => e
|
|
168
|
+
LoggerService.log(LogLevelEnum::ERROR, "EXECUTION_FAILED", {apiName: api_name, err: e.message, an: ApiEnum::SET_ATTRIBUTE})
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Update the settings of the VWO client instance
|
|
173
|
+
# @param settings [Hash] The settings to update with (optional)
|
|
174
|
+
# @param is_via_webhook [Boolean] Whether the update is via webhook (default: true)
|
|
175
|
+
# @return [void]
|
|
176
|
+
def update_settings(settings = nil, is_via_webhook = true)
|
|
177
|
+
api_name = 'update_settings'
|
|
178
|
+
|
|
179
|
+
begin
|
|
180
|
+
LoggerService.log(LogLevelEnum::DEBUG, "API_CALLED", {apiName: api_name})
|
|
181
|
+
|
|
182
|
+
# Fetch settings from server or use provided settings if not empty
|
|
183
|
+
settings_to_update = if settings.nil? || settings.empty?
|
|
184
|
+
SettingsService.instance.fetch_settings(is_via_webhook)
|
|
185
|
+
else
|
|
186
|
+
settings
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Validate settings schema
|
|
190
|
+
unless SettingsSchema.new.is_settings_valid(settings_to_update)
|
|
191
|
+
raise TypeError, "Got Invalid Settings, settings: #{settings_to_update}"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Set the settings on the client instance
|
|
195
|
+
set_settings_and_add_campaigns_to_rules(settings_to_update, self)
|
|
196
|
+
LoggerService.log(LogLevelEnum::INFO, "SETTINGS_UPDATED", {apiName: api_name, isViaWebhook: is_via_webhook})
|
|
197
|
+
rescue StandardError => e
|
|
198
|
+
LoggerService.log(
|
|
199
|
+
LogLevelEnum::ERROR,
|
|
200
|
+
"UPDATING_CLIENT_INSTANCE_FAILED_WHEN_WEBHOOK_TRIGGERED",
|
|
201
|
+
{
|
|
202
|
+
apiName: api_name,
|
|
203
|
+
isViaWebhook: is_via_webhook,
|
|
204
|
+
err: e.message,
|
|
205
|
+
an: ApiEnum::UPDATE_SETTINGS
|
|
206
|
+
}, false
|
|
207
|
+
)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Flushes the batch events queue
|
|
212
|
+
# @return [void]
|
|
213
|
+
def flush_events
|
|
214
|
+
api_name = 'flush_events'
|
|
215
|
+
begin
|
|
216
|
+
LoggerService.log(LogLevelEnum::DEBUG, "API_CALLED", {apiName: api_name})
|
|
217
|
+
if BatchEventsQueue.instance.nil?
|
|
218
|
+
raise StandardError, "Batch events queue is not initialized"
|
|
219
|
+
end
|
|
220
|
+
# flush the batch events queue
|
|
221
|
+
@response = BatchEventsQueue.instance.flush(true)
|
|
222
|
+
@response
|
|
223
|
+
rescue StandardError => e
|
|
224
|
+
LoggerService.log(LogLevelEnum::ERROR, "EXECUTION_FAILED", {apiName: api_name, err: e.message, an: ApiEnum::FLUSH_EVENTS})
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|