thinkingdata-ruby 2.0.0 → 2.0.2

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.
@@ -1,495 +1,505 @@
1
- require 'securerandom'
2
- require 'thinkingdata-ruby/td_errors'
3
- require 'thinkingdata-ruby/td_version'
4
-
5
- ##
6
- # ThinkingData module
7
- module ThinkingData
8
- @is_enable_log = false
9
- @is_stringent = false
10
-
11
- ##
12
- # Enable SDK log or not
13
- # @param enable [Boolean] true or false
14
- def self.set_enable_log(enable)
15
- unless [true, false].include? enable
16
- enable = false
17
- end
18
- @is_enable_log = enable
19
- end
20
-
21
- ##
22
- # Get log status
23
- # @return [Boolean] enable or not
24
- def self.get_enable_log
25
- @is_enable_log
26
- end
27
-
28
- ##
29
- # Check or not parameter
30
- # @param enable [Boolean] check or not
31
- def self.set_stringent(enable)
32
- unless [true, false].include? enable
33
- enable = false
34
- end
35
- @is_stringent = enable
36
- end
37
-
38
- ##
39
- # Get parameter check status of SDK
40
- # @return [Boolean] check or not
41
- def self.get_stringent
42
- @is_stringent
43
- end
44
-
45
- ##
46
- # Analytics class。 Provides the function of tracking data
47
- class TDAnalytics
48
- LIB_PROPERTIES = {
49
- '#lib' => 'ruby',
50
- '#lib_version' => ThinkingData::VERSION,
51
- }
52
-
53
- @dynamic_block = nil
54
-
55
- ##
56
- # Init function
57
- # @param consumer [consumer] data consumer: TDLoggerConsumer | TDDebugConsumer | TDBatchConsumer
58
- # @param error_handler [TDErrorHandler] custom error handler, process SDK error. It could be nil
59
- # @param uuid [Boolean] Whether to automatically add uuid
60
- def initialize(consumer, error_handler = nil, uuid: false)
61
- @error_handler = error_handler || TDErrorHandler.new
62
- @consumer = consumer
63
- @super_properties = {}
64
- @uuid_enable = uuid
65
- TDLog.info("SDK init success.")
66
- end
67
-
68
- ##
69
- # Set common properties
70
- def set_super_properties(properties, skip_local_check = false)
71
- unless ThinkingData::get_stringent == false || skip_local_check || _check_properties(:track, properties)
72
- @error_handler.handle(IllegalParameterError.new("Invalid super properties"))
73
- return false
74
- end
75
- properties.each do |k, v|
76
- if v.is_a?(Time)
77
- @super_properties[k] = _format_time(v)
78
- else
79
- @super_properties[k] = v
80
- end
81
- end
82
- end
83
-
84
- ##
85
- # Clear super properties
86
- def clear_super_properties
87
- @super_properties = {}
88
- end
89
-
90
- ##
91
- # Set dynamic super properties
92
- def set_dynamic_super_properties(&block)
93
- @dynamic_block = block
94
- end
95
-
96
- ##
97
- # Clear dynamic super properties
98
- def clear_dynamic_super_properties
99
- @dynamic_block = nil
100
- end
101
-
102
- ##
103
- # Report ordinary event
104
- # event_name: (require) A string of 50 letters and digits that starts with '#' or a letter
105
- # distinct_id: (optional) distinct ID
106
- # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
107
- # properties: (optional) string、number、Time、boolean
108
- # time: (optional)Time
109
- # ip: (optional) ip
110
- # first_check_id: (optional) The value cannot be null for the first event
111
- # skip_local_check: (optional) check data or not
112
- def track(event_name: nil, distinct_id: nil, account_id: nil, properties: {}, time: nil, ip: nil,first_check_id:nil, skip_local_check: false)
113
- begin
114
- _check_name event_name
115
- _check_id(distinct_id, account_id)
116
- unless skip_local_check
117
- _check_properties(:track, properties)
118
- end
119
- rescue TDAnalyticsError => e
120
- @error_handler.handle(e)
121
- return false
122
- end
123
-
124
- _internal_track(:track, event_name: event_name, distinct_id: distinct_id, account_id: account_id, properties: properties, time: time, ip: ip, first_check_id: first_check_id)
125
- end
126
-
127
- ##
128
- # Report overridable event
129
- # event_name: (require) A string of 50 letters and digits that starts with '#' or a letter
130
- # event_id: (require) string
131
- # distinct_id: (optional) distinct ID
132
- # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
133
- # properties: (optional) string、number、Time、boolean
134
- # time: (optional)Time
135
- # ip: (optional) ip
136
- # skip_local_check: (optional) check data or not
137
- def track_overwrite(event_name: nil,event_id: nil, distinct_id: nil, account_id: nil, properties: {}, time: nil, ip: nil, skip_local_check: false)
138
- begin
139
- _check_name event_name
140
- _check_event_id event_id
141
- _check_id(distinct_id, account_id)
142
- unless skip_local_check
143
- _check_properties(:track_overwrite, properties)
144
- end
145
- rescue TDAnalyticsError => e
146
- @error_handler.handle(e)
147
- return false
148
- end
149
-
150
- _internal_track(:track_overwrite, event_name: event_name, event_id: event_id, distinct_id: distinct_id, account_id: account_id, properties: properties, time: time, ip: ip)
151
- end
152
-
153
- ##
154
- # Report updatable event
155
- # event_name: (require) A string of 50 letters and digits that starts with '#' or a letter
156
- # event_id: (require) string
157
- # distinct_id: (optional) distinct ID
158
- # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
159
- # properties: (optional) string、number、Time、boolean
160
- # time: (optional)Time
161
- # ip: (optional) ip
162
- # skip_local_check: (optional) check data or not
163
- def track_update(event_name: nil,event_id: nil, distinct_id: nil, account_id: nil, properties: {}, time: nil, ip: nil, skip_local_check: false)
164
- begin
165
- _check_name event_name
166
- _check_event_id event_id
167
- _check_id(distinct_id, account_id)
168
- unless skip_local_check
169
- _check_properties(:track_update, properties)
170
- end
171
- rescue TDAnalyticsError => e
172
- @error_handler.handle(e)
173
- return false
174
- end
175
-
176
- _internal_track(:track_update, event_name: event_name, event_id: event_id, distinct_id: distinct_id, account_id: account_id, properties: properties, time: time, ip: ip)
177
- end
178
-
179
- ##
180
- # Set user properties. would overwrite existing names
181
- # distinct_id: (optional) distinct ID
182
- # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
183
- # properties: (optional) string、number、Time、boolean
184
- # ip: (optional) ip
185
- def user_set(distinct_id: nil, account_id: nil, properties: {}, ip: nil)
186
- begin
187
- _check_id(distinct_id, account_id)
188
- _check_properties(:user_set, properties)
189
- rescue TDAnalyticsError => e
190
- @error_handler.handle(e)
191
- return false
192
- end
193
-
194
- _internal_track(:user_set, distinct_id: distinct_id, account_id: account_id, properties: properties, ip: ip)
195
- end
196
-
197
- ##
198
- # Set user properties, If such property had been set before, this message would be neglected
199
- # distinct_id: (optional) distinct ID
200
- # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
201
- # properties: (optional) string、number、Time、boolean
202
- # ip: (optional) ip
203
- def user_set_once(distinct_id: nil, account_id: nil, properties: {}, ip: nil)
204
- begin
205
- _check_id(distinct_id, account_id)
206
- _check_properties(:user_setOnce, properties)
207
- rescue TDAnalyticsError => e
208
- @error_handler.handle(e)
209
- return false
210
- end
211
-
212
- _internal_track(:user_setOnce,
213
- distinct_id: distinct_id,
214
- account_id: account_id,
215
- properties: properties,
216
- ip: ip,
217
- )
218
- end
219
-
220
- ##
221
- # To append user properties of array type
222
- # distinct_id: (optional) distinct ID
223
- # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
224
- # properties: (optional) string、number、Time、boolean
225
- def user_append(distinct_id: nil, account_id: nil, properties: {})
226
- begin
227
- _check_id(distinct_id, account_id)
228
- _check_properties(:user_append, properties)
229
- rescue TDAnalyticsError => e
230
- @error_handler.handle(e)
231
- return false
232
- end
233
-
234
- _internal_track(:user_append,
235
- distinct_id: distinct_id,
236
- account_id: account_id,
237
- properties: properties,
238
- )
239
- end
240
-
241
- ##
242
- # To append user properties of array type. It filters out duplicate values
243
- # distinct_id: (optional) distinct ID
244
- # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
245
- # properties: (optional) string、number、Time、boolean
246
- def user_uniq_append(distinct_id: nil, account_id: nil, properties: {})
247
- begin
248
- _check_id(distinct_id, account_id)
249
- _check_properties(:user_uniq_append, properties)
250
- rescue TDAnalyticsError => e
251
- @error_handler.handle(e)
252
- return false
253
- end
254
-
255
- _internal_track(:user_uniq_append,
256
- distinct_id: distinct_id,
257
- account_id: account_id,
258
- properties: properties,
259
- )
260
- end
261
-
262
- ##
263
- # Clear the user properties of users
264
- # distinct_id: (optional) distinct ID
265
- # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
266
- # properties: (optional) string、number、Time、boolean
267
- def user_unset(distinct_id: nil, account_id: nil, property: nil)
268
- properties = {}
269
- if property.is_a?(Array)
270
- property.each do |k|
271
- properties[k] = 0
272
- end
273
- else
274
- properties[property] = 0
275
- end
276
-
277
- begin
278
- _check_id(distinct_id, account_id)
279
- _check_properties(:user_unset, properties)
280
- rescue TDAnalyticsError => e
281
- @error_handler.handle(e)
282
- return false
283
- end
284
-
285
- _internal_track(:user_unset,
286
- distinct_id: distinct_id,
287
- account_id: account_id,
288
- properties: properties,
289
- )
290
- end
291
-
292
- ##
293
- # To accumulate operations against the property
294
- # distinct_id: (optional) distinct ID
295
- # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
296
- # properties: (optional) string、number、Time、boolean
297
- def user_add(distinct_id: nil, account_id: nil, properties: {})
298
- begin
299
- _check_id(distinct_id, account_id)
300
- _check_properties(:user_add, properties)
301
- rescue TDAnalyticsError => e
302
- @error_handler.handle(e)
303
- return false
304
- end
305
-
306
- _internal_track(:user_add,
307
- distinct_id: distinct_id,
308
- account_id: account_id,
309
- properties: properties,
310
- )
311
- end
312
-
313
- ##
314
- # Delete a user, This operation cannot be undone
315
- # distinct_id: (optional) distinct ID
316
- # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
317
- def user_del(distinct_id: nil, account_id: nil)
318
- begin
319
- _check_id(distinct_id, account_id)
320
- rescue TDAnalyticsError => e
321
- @error_handler.handle(e)
322
- return false
323
- end
324
-
325
- _internal_track(:user_del,
326
- distinct_id: distinct_id,
327
- account_id: account_id,
328
- )
329
- end
330
-
331
- ##
332
- # Report data immediately
333
- def flush
334
- TDLog.info("SDK flush data.")
335
- return true unless defined? @consumer.flush
336
- ret = true
337
- begin
338
- @consumer.flush
339
- rescue TDAnalyticsError => e
340
- @error_handler.handle(e)
341
- ret = false
342
- end
343
- ret
344
- end
345
-
346
- ##
347
- # Close and exit sdk
348
- def close
349
- return true unless defined? @consumer.close
350
- ret = true
351
- begin
352
- @consumer.close
353
- rescue TDAnalyticsError => e
354
- @error_handler.handle(e)
355
- ret = false
356
- end
357
-
358
- TDLog.info("SDK close.")
359
-
360
- ret
361
- end
362
-
363
- private
364
-
365
- def _internal_track(type, properties: {}, event_name: nil, event_id:nil, account_id: nil, distinct_id: nil, ip: nil,first_check_id: nil, time: nil)
366
- if type == :track || type == :track_update || type == :track_overwrite
367
- dynamic_properties = @dynamic_block.respond_to?(:call) ? @dynamic_block.call : {}
368
- properties = LIB_PROPERTIES.merge(@super_properties).merge(dynamic_properties).merge(properties)
369
- end
370
-
371
- data = {
372
- '#type' => type,
373
- }
374
-
375
- properties.each do |k, v|
376
- if v.is_a?(Time)
377
- properties[k] = _format_time(v)
378
- end
379
- end
380
-
381
- _move_preset_properties([:'#ip', :"#time", :"#app_id", :"#uuid"], data, properties: properties)
382
-
383
- if data[:'#time'] == nil
384
- if time == nil
385
- time = Time.now
386
- end
387
- data[:'#time'] = _format_time(time)
388
- end
389
-
390
- data['properties'] = properties
391
- data['#event_name'] = event_name if (type == :track || type == :track_update || type == :track_overwrite)
392
- data['#event_id'] = event_id if (type == :track_update || type == :track_overwrite)
393
- data['#account_id'] = account_id if account_id
394
- data['#distinct_id'] = distinct_id if distinct_id
395
- data['#ip'] = ip if ip
396
- data['#first_check_id'] = first_check_id if first_check_id
397
- data[:'#uuid'] = SecureRandom.uuid if @uuid_enable and data[:'#uuid'] == nil
398
-
399
- ret = true
400
- begin
401
- @consumer.add(data)
402
- rescue TDAnalyticsError => e
403
- @error_handler.handle(e)
404
- ret = false
405
- end
406
-
407
- ret
408
- end
409
-
410
- def _format_time(time)
411
- time.strftime("%Y-%m-%d %H:%M:%S.#{((time.to_f * 1000.0).to_i % 1000).to_s.rjust(3, "0")}")
412
- end
413
-
414
- def _check_event_id(event_id)
415
- if ThinkingData::get_stringent == false
416
- return true
417
- end
418
-
419
- raise IllegalParameterError.new("the event_id or property cannot be nil") if event_id.nil?
420
- true
421
- end
422
-
423
- def _check_name(name)
424
- if ThinkingData::get_stringent == false
425
- return true
426
- end
427
-
428
- raise IllegalParameterError.new("the name of event or property cannot be nil") if name.nil?
429
-
430
- unless name.instance_of?(String) || name.instance_of?(Symbol)
431
- raise IllegalParameterError.new("#{name} is invalid. It must be String or Symbol")
432
- end
433
- true
434
- end
435
-
436
- def _check_properties(type, properties)
437
- if ThinkingData::get_stringent == false
438
- return true
439
- end
440
-
441
- unless properties.instance_of? Hash
442
- return false
443
- end
444
-
445
- properties.each do |k, v|
446
- _check_name k
447
- next if v.nil?
448
- unless v.is_a?(Integer) || v.is_a?(Float) || v.is_a?(Symbol) || v.is_a?(String) || v.is_a?(Time) || !!v == v || v.is_a?(Array)
449
- raise IllegalParameterError.new("The value of properties must be type in Integer, Float, Symbol, String, Array,and Time")
450
- end
451
-
452
- if type == :user_add
453
- raise IllegalParameterError.new("Property value for user add must be numbers") unless v.is_a?(Integer) || v.is_a?(Float)
454
- end
455
- if v.is_a?(Array)
456
- v.each_index do |i|
457
- if v[i].is_a?(Time)
458
- v[i] = _format_time(v[i])
459
- end
460
- end
461
- end
462
- end
463
- true
464
- end
465
-
466
- def _check_id(distinct_id, account_id)
467
- if ThinkingData::get_stringent == false
468
- return true
469
- end
470
-
471
- raise IllegalParameterError.new("account id or distinct id must be provided.") if distinct_id.nil? && account_id.nil?
472
- end
473
-
474
- def _move_preset_properties(keys, data, properties: {})
475
- property_keys = properties.keys
476
- keys.each { |k|
477
- if property_keys.include? k
478
- data[k] = properties[k]
479
- properties.delete(k)
480
- end
481
- }
482
- end
483
- end
484
-
485
- ##
486
- # SDK log module
487
- class TDLog
488
- def self.info(*msg)
489
- if ThinkingData::get_enable_log
490
- print("[ThinkingData][#{Time.now}] ")
491
- puts(msg)
492
- end
493
- end
494
- end
495
- end
1
+ require 'securerandom'
2
+ require 'thinkingdata-ruby/td_errors'
3
+ require 'thinkingdata-ruby/td_version'
4
+
5
+ ##
6
+ # ThinkingData module
7
+ module ThinkingData
8
+ @is_enable_log = false
9
+ @is_stringent = false
10
+
11
+ ##
12
+ # Enable SDK log or not
13
+ # @param enable [Boolean] true or false
14
+ def self.set_enable_log(enable)
15
+ unless [true, false].include? enable
16
+ enable = false
17
+ end
18
+ @is_enable_log = enable
19
+ end
20
+
21
+ ##
22
+ # Get log status
23
+ # @return [Boolean] enable or not
24
+ def self.get_enable_log
25
+ @is_enable_log
26
+ end
27
+
28
+ ##
29
+ # Check or not parameter
30
+ # @param enable [Boolean] check or not
31
+ def self.set_stringent(enable)
32
+ unless [true, false].include? enable
33
+ enable = false
34
+ end
35
+ @is_stringent = enable
36
+ end
37
+
38
+ ##
39
+ # Get parameter check status of SDK
40
+ # @return [Boolean] check or not
41
+ def self.get_stringent
42
+ @is_stringent
43
+ end
44
+
45
+ ##
46
+ # Analytics class。 Provides the function of tracking data
47
+ class TDAnalytics
48
+ LIB_PROPERTIES = {
49
+ '#lib' => 'ruby',
50
+ '#lib_version' => ThinkingData::VERSION,
51
+ }.freeze
52
+
53
+ ##
54
+ # Init function
55
+ # @param consumer [consumer] data consumer: TDLoggerConsumer | TDDebugConsumer | TDBatchConsumer
56
+ # @param error_handler [TDErrorHandler] custom error handler, process SDK error. It could be nil
57
+ # @param uuid [Boolean] Whether to automatically add uuid
58
+ def initialize(consumer, error_handler = nil, uuid: false)
59
+ @error_handler = error_handler || TDErrorHandler.new
60
+ @consumer = consumer
61
+ @super_properties = {}.freeze
62
+ @uuid_enable = uuid
63
+ @mutex = Mutex.new
64
+ TDLog.info("SDK init success.")
65
+ end
66
+
67
+ ##
68
+ # Set common properties
69
+ def set_super_properties(properties, skip_local_check = false)
70
+ @mutex.synchronize do
71
+ unless ThinkingData::get_stringent == false || skip_local_check || _check_properties(:track, properties)
72
+ @error_handler.handle(IllegalParameterError.new("Invalid super properties"))
73
+ return false
74
+ end
75
+ formatted = {}
76
+ properties.each do |k, v|
77
+ formatted[k] = v.is_a?(Time) ? _format_time(v) : v
78
+ end
79
+ @super_properties = @super_properties.merge(formatted).freeze
80
+ end
81
+ end
82
+
83
+ ##
84
+ # Clear super properties
85
+ def clear_super_properties
86
+ @mutex.synchronize do
87
+ @super_properties = {}.freeze
88
+ end
89
+ end
90
+
91
+ ##
92
+ # Set dynamic super properties
93
+ def set_dynamic_super_properties(&block)
94
+ @mutex.synchronize do
95
+ @dynamic_block = block
96
+ end
97
+ end
98
+
99
+ ##
100
+ # Clear dynamic super properties
101
+ def clear_dynamic_super_properties
102
+ @mutex.synchronize do
103
+ @dynamic_block = nil
104
+ end
105
+ end
106
+
107
+ ##
108
+ # Report ordinary event
109
+ # event_name: (require) A string of 50 letters and digits that starts with '#' or a letter
110
+ # distinct_id: (optional) distinct ID
111
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
112
+ # properties: (optional) string、number、Time、boolean
113
+ # time: (optional)Time
114
+ # ip: (optional) ip
115
+ # first_check_id: (optional) The value cannot be null for the first event
116
+ # skip_local_check: (optional) check data or not
117
+ def track(event_name: nil, distinct_id: nil, account_id: nil, properties: {}, time: nil, ip: nil,first_check_id:nil, skip_local_check: false)
118
+ begin
119
+ _check_name event_name
120
+ _check_id(distinct_id, account_id)
121
+ unless skip_local_check
122
+ _check_properties(:track, properties)
123
+ end
124
+ rescue TDAnalyticsError => e
125
+ @error_handler.handle(e)
126
+ return false
127
+ end
128
+
129
+ _internal_track(:track, event_name: event_name, distinct_id: distinct_id, account_id: account_id, properties: properties, time: time, ip: ip, first_check_id: first_check_id)
130
+ end
131
+
132
+ ##
133
+ # Report overridable event
134
+ # event_name: (require) A string of 50 letters and digits that starts with '#' or a letter
135
+ # event_id: (require) string
136
+ # distinct_id: (optional) distinct ID
137
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
138
+ # properties: (optional) string、number、Time、boolean
139
+ # time: (optional)Time
140
+ # ip: (optional) ip
141
+ # skip_local_check: (optional) check data or not
142
+ def track_overwrite(event_name: nil,event_id: nil, distinct_id: nil, account_id: nil, properties: {}, time: nil, ip: nil, skip_local_check: false)
143
+ begin
144
+ _check_name event_name
145
+ _check_event_id event_id
146
+ _check_id(distinct_id, account_id)
147
+ unless skip_local_check
148
+ _check_properties(:track_overwrite, properties)
149
+ end
150
+ rescue TDAnalyticsError => e
151
+ @error_handler.handle(e)
152
+ return false
153
+ end
154
+
155
+ _internal_track(:track_overwrite, event_name: event_name, event_id: event_id, distinct_id: distinct_id, account_id: account_id, properties: properties, time: time, ip: ip)
156
+ end
157
+
158
+ ##
159
+ # Report updatable event
160
+ # event_name: (require) A string of 50 letters and digits that starts with '#' or a letter
161
+ # event_id: (require) string
162
+ # distinct_id: (optional) distinct ID
163
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
164
+ # properties: (optional) string、number、Time、boolean
165
+ # time: (optional)Time
166
+ # ip: (optional) ip
167
+ # skip_local_check: (optional) check data or not
168
+ def track_update(event_name: nil,event_id: nil, distinct_id: nil, account_id: nil, properties: {}, time: nil, ip: nil, skip_local_check: false)
169
+ begin
170
+ _check_name event_name
171
+ _check_event_id event_id
172
+ _check_id(distinct_id, account_id)
173
+ unless skip_local_check
174
+ _check_properties(:track_update, properties)
175
+ end
176
+ rescue TDAnalyticsError => e
177
+ @error_handler.handle(e)
178
+ return false
179
+ end
180
+
181
+ _internal_track(:track_update, event_name: event_name, event_id: event_id, distinct_id: distinct_id, account_id: account_id, properties: properties, time: time, ip: ip)
182
+ end
183
+
184
+ ##
185
+ # Set user properties. would overwrite existing names
186
+ # distinct_id: (optional) distinct ID
187
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
188
+ # properties: (optional) string、number、Time、boolean
189
+ # ip: (optional) ip
190
+ def user_set(distinct_id: nil, account_id: nil, properties: {}, ip: nil)
191
+ begin
192
+ _check_id(distinct_id, account_id)
193
+ _check_properties(:user_set, properties)
194
+ rescue TDAnalyticsError => e
195
+ @error_handler.handle(e)
196
+ return false
197
+ end
198
+
199
+ _internal_track(:user_set, distinct_id: distinct_id, account_id: account_id, properties: properties, ip: ip)
200
+ end
201
+
202
+ ##
203
+ # Set user properties, If such property had been set before, this message would be neglected
204
+ # distinct_id: (optional) distinct ID
205
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
206
+ # properties: (optional) string、number、Time、boolean
207
+ # ip: (optional) ip
208
+ def user_set_once(distinct_id: nil, account_id: nil, properties: {}, ip: nil)
209
+ begin
210
+ _check_id(distinct_id, account_id)
211
+ _check_properties(:user_setOnce, properties)
212
+ rescue TDAnalyticsError => e
213
+ @error_handler.handle(e)
214
+ return false
215
+ end
216
+
217
+ _internal_track(:user_setOnce,
218
+ distinct_id: distinct_id,
219
+ account_id: account_id,
220
+ properties: properties,
221
+ ip: ip,
222
+ )
223
+ end
224
+
225
+ ##
226
+ # To append user properties of array type
227
+ # distinct_id: (optional) distinct ID
228
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
229
+ # properties: (optional) string、number、Time、boolean
230
+ def user_append(distinct_id: nil, account_id: nil, properties: {})
231
+ begin
232
+ _check_id(distinct_id, account_id)
233
+ _check_properties(:user_append, properties)
234
+ rescue TDAnalyticsError => e
235
+ @error_handler.handle(e)
236
+ return false
237
+ end
238
+
239
+ _internal_track(:user_append,
240
+ distinct_id: distinct_id,
241
+ account_id: account_id,
242
+ properties: properties,
243
+ )
244
+ end
245
+
246
+ ##
247
+ # To append user properties of array type. It filters out duplicate values
248
+ # distinct_id: (optional) distinct ID
249
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
250
+ # properties: (optional) string、number、Time、boolean
251
+ def user_uniq_append(distinct_id: nil, account_id: nil, properties: {})
252
+ begin
253
+ _check_id(distinct_id, account_id)
254
+ _check_properties(:user_uniq_append, properties)
255
+ rescue TDAnalyticsError => e
256
+ @error_handler.handle(e)
257
+ return false
258
+ end
259
+
260
+ _internal_track(:user_uniq_append,
261
+ distinct_id: distinct_id,
262
+ account_id: account_id,
263
+ properties: properties,
264
+ )
265
+ end
266
+
267
+ ##
268
+ # Clear the user properties of users
269
+ # distinct_id: (optional) distinct ID
270
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
271
+ # properties: (optional) string、number、Time、boolean
272
+ def user_unset(distinct_id: nil, account_id: nil, property: nil)
273
+ properties = {}
274
+ if property.is_a?(Array)
275
+ property.each do |k|
276
+ properties[k] = 0
277
+ end
278
+ else
279
+ properties[property] = 0
280
+ end
281
+
282
+ begin
283
+ _check_id(distinct_id, account_id)
284
+ _check_properties(:user_unset, properties)
285
+ rescue TDAnalyticsError => e
286
+ @error_handler.handle(e)
287
+ return false
288
+ end
289
+
290
+ _internal_track(:user_unset,
291
+ distinct_id: distinct_id,
292
+ account_id: account_id,
293
+ properties: properties,
294
+ )
295
+ end
296
+
297
+ ##
298
+ # To accumulate operations against the property
299
+ # distinct_id: (optional) distinct ID
300
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
301
+ # properties: (optional) string、number、Time、boolean
302
+ def user_add(distinct_id: nil, account_id: nil, properties: {})
303
+ begin
304
+ _check_id(distinct_id, account_id)
305
+ _check_properties(:user_add, properties)
306
+ rescue TDAnalyticsError => e
307
+ @error_handler.handle(e)
308
+ return false
309
+ end
310
+
311
+ _internal_track(:user_add,
312
+ distinct_id: distinct_id,
313
+ account_id: account_id,
314
+ properties: properties,
315
+ )
316
+ end
317
+
318
+ ##
319
+ # Delete a user, This operation cannot be undone
320
+ # distinct_id: (optional) distinct ID
321
+ # account_id: (optional) account ID. distinct_id, account_id can't both be empty.
322
+ def user_del(distinct_id: nil, account_id: nil)
323
+ begin
324
+ _check_id(distinct_id, account_id)
325
+ rescue TDAnalyticsError => e
326
+ @error_handler.handle(e)
327
+ return false
328
+ end
329
+
330
+ _internal_track(:user_del,
331
+ distinct_id: distinct_id,
332
+ account_id: account_id,
333
+ )
334
+ end
335
+
336
+ ##
337
+ # Report data immediately
338
+ def flush
339
+ TDLog.info("SDK flush data.")
340
+ return true unless @consumer.respond_to?(:flush)
341
+ ret = true
342
+ begin
343
+ @consumer.flush
344
+ rescue TDAnalyticsError => e
345
+ @error_handler.handle(e)
346
+ ret = false
347
+ end
348
+ ret
349
+ end
350
+
351
+ ##
352
+ # Close and exit sdk
353
+ def close
354
+ return true unless @consumer.respond_to?(:close)
355
+ ret = true
356
+ # Consumer 自身已有锁保护,无需在此加锁
357
+ begin
358
+ @consumer.close
359
+ rescue TDAnalyticsError => e
360
+ @error_handler.handle(e)
361
+ ret = false
362
+ end
363
+
364
+ TDLog.info("SDK close.")
365
+
366
+ ret
367
+ end
368
+
369
+ private
370
+
371
+ def _internal_track(type, properties: {}, event_name: nil, event_id:nil, account_id: nil, distinct_id: nil, ip: nil,first_check_id: nil, time: nil)
372
+ ret = true
373
+
374
+ # Snapshot @dynamic_block under the mutex for memory visibility;
375
+ # @super_properties is a frozen hash replaced atomically — safe lock-free read.
376
+ # The dynamic block is called OUTSIDE the mutex to prevent deadlocks
377
+ # (if the block calls back into TDAnalytics) and throughput collapse.
378
+ if type == :track || type == :track_update || type == :track_overwrite
379
+ block = @mutex.synchronize { @dynamic_block }
380
+ dynamic_properties = block.respond_to?(:call) ? block.call : {}
381
+ properties = LIB_PROPERTIES.merge(@super_properties).merge(dynamic_properties).merge(properties)
382
+ else
383
+ # user_* methods: dup to avoid mutating the caller's hash
384
+ properties = properties.dup
385
+ end
386
+
387
+ data = {
388
+ '#type' => type,
389
+ }
390
+
391
+ properties.each do |k, v|
392
+ if v.is_a?(Time)
393
+ properties[k] = _format_time(v)
394
+ elsif v.is_a?(Array)
395
+ properties[k] = v.map { |e| e.is_a?(Time) ? _format_time(e) : e }
396
+ end
397
+ end
398
+
399
+ _move_preset_properties([:'#ip', :"#time", :"#app_id", :"#uuid"], data, properties: properties)
400
+
401
+ if data[:'#time'] == nil
402
+ if time == nil
403
+ time = Time.now
404
+ end
405
+ data[:'#time'] = _format_time(time)
406
+ end
407
+
408
+ data['properties'] = properties
409
+ data['#event_name'] = event_name if (type == :track || type == :track_update || type == :track_overwrite)
410
+ data['#event_id'] = event_id if (type == :track_update || type == :track_overwrite)
411
+ data['#account_id'] = account_id if account_id
412
+ data['#distinct_id'] = distinct_id if distinct_id
413
+ data['#ip'] = ip if ip
414
+ data['#first_check_id'] = first_check_id if first_check_id
415
+ data[:'#uuid'] = SecureRandom.uuid if @uuid_enable and data[:'#uuid'] == nil
416
+
417
+ begin
418
+ @consumer.add(data)
419
+ rescue TDAnalyticsError => e
420
+ @error_handler.handle(e)
421
+ ret = false
422
+ end
423
+
424
+ ret
425
+ end
426
+
427
+ def _format_time(time)
428
+ time.strftime("%Y-%m-%d %H:%M:%S.#{((time.to_f * 1000.0).to_i % 1000).to_s.rjust(3, "0")}")
429
+ end
430
+
431
+ def _check_event_id(event_id)
432
+ if ThinkingData::get_stringent == false
433
+ return true
434
+ end
435
+
436
+ raise IllegalParameterError.new("the event_id or property cannot be nil") if event_id.nil?
437
+ true
438
+ end
439
+
440
+ def _check_name(name)
441
+ if ThinkingData::get_stringent == false
442
+ return true
443
+ end
444
+
445
+ raise IllegalParameterError.new("the name of event or property cannot be nil") if name.nil?
446
+
447
+ unless name.instance_of?(String) || name.instance_of?(Symbol)
448
+ raise IllegalParameterError.new("#{name} is invalid. It must be String or Symbol")
449
+ end
450
+ true
451
+ end
452
+
453
+ def _check_properties(type, properties)
454
+ if ThinkingData::get_stringent == false
455
+ return true
456
+ end
457
+
458
+ unless properties.instance_of? Hash
459
+ return false
460
+ end
461
+
462
+ properties.each do |k, v|
463
+ _check_name k
464
+ next if v.nil?
465
+ unless v.is_a?(Integer) || v.is_a?(Float) || v.is_a?(Symbol) || v.is_a?(String) || v.is_a?(Time) || !!v == v || v.is_a?(Array)
466
+ raise IllegalParameterError.new("The value of properties must be type in Integer, Float, Symbol, String, Array,and Time")
467
+ end
468
+
469
+ if type == :user_add
470
+ raise IllegalParameterError.new("Property value for user add must be numbers") unless v.is_a?(Integer) || v.is_a?(Float)
471
+ end
472
+ end
473
+ true
474
+ end
475
+
476
+ def _check_id(distinct_id, account_id)
477
+ if ThinkingData::get_stringent == false
478
+ return true
479
+ end
480
+
481
+ raise IllegalParameterError.new("account id or distinct id must be provided.") if distinct_id.nil? && account_id.nil?
482
+ end
483
+
484
+ def _move_preset_properties(keys, data, properties: {})
485
+ property_keys = properties.keys
486
+ keys.each { |k|
487
+ if property_keys.include? k
488
+ data[k] = properties[k]
489
+ properties.delete(k)
490
+ end
491
+ }
492
+ end
493
+ end
494
+
495
+ ##
496
+ # SDK log module
497
+ class TDLog
498
+ def self.info(*msg)
499
+ if ThinkingData::get_enable_log
500
+ print("[ThinkingData][#{Time.now}] ")
501
+ puts(msg)
502
+ end
503
+ end
504
+ end
505
+ end