Class: AnalyticsClient

Inherits:
Object
  • Object
show all
Defined in:
lib/zoho_analytics_client.rb

Defined Under Namespace

Classes: Builder

Constant Summary collapse

CLIENT_VERSION =
"2.8.0"
COMMON_ENCODE_CHAR =
"UTF-8"
DC_URLS =
{
  "US"  => { "accounts" => "https://accounts.zoho.com",    "analytics" => "https://analyticsapi.zoho.com" },
  "EU"  => { "accounts" => "https://accounts.zoho.eu",     "analytics" => "https://analyticsapi.zoho.eu" },
  "IN"  => { "accounts" => "https://accounts.zoho.in",     "analytics" => "https://analyticsapi.zoho.in" },
  "AU"  => { "accounts" => "https://accounts.zoho.com.au", "analytics" => "https://analyticsapi.zoho.com.au" },
  "CN"  => { "accounts" => "https://accounts.zoho.com.cn", "analytics" => "https://analyticsapi.zoho.com.cn" },
  "CA"  => { "accounts" => "https://accounts.zohocloud.ca", "analytics" => "https://analyticsapi.zohocloud.ca" },
  "JP"  => { "accounts" => "https://accounts.zoho.jp",     "analytics" => "https://analyticsapi.zoho.jp" },
  "SA"  => { "accounts" => "https://accounts.zoho.sa",     "analytics" => "https://analyticsapi.zoho.sa" },
  "UAE" => { "accounts" => "https://accounts.zoho.ae",     "analytics" => "https://analyticsapi.zoho.ae" }
}

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.newObject



29
30
31
# File 'lib/zoho_analytics_client.rb', line 29

def self.new
  Builder.new
end

Instance Method Details

#add_headers(request, headers) ⇒ Object



594
595
596
# File 'lib/zoho_analytics_client.rb', line 594

def add_headers(request, headers)
  headers&.each { |key, value| request[key] = value }
end

#cleanup_obsolete_token_filesObject



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/zoho_analytics_client.rb', line 331

def cleanup_obsolete_token_files
  return unless @store_path && Dir.exist?(@store_path)

  Dir.glob(File.join(@store_path, "za_tokens_*.dat")).each do |file|
    begin
      mtime = File.mtime(file)
      if Time.now - mtime > 24 * 60 * 60 # older than 1 day (86400 seconds)
        File.delete(file)
        # You might want to log this deletion for traceability
        # puts "Deleted obsolete token file: #{file}"
      end
    rescue => e
      # Optionally log errors, but continue
      # puts "Error checking or deleting file #{file}: #{e.message}"
    end
  end
end

#create_http_client(uri) ⇒ Object

Helper: HTTP/Proxy



583
584
585
586
587
588
589
590
591
592
# File 'lib/zoho_analytics_client.rb', line 583

def create_http_client(uri)
  if @proxy
    Net::HTTP.new(uri.host, uri.port, @proxy_host, @proxy_port, @proxy_user_name, @proxy_password)
  else
    Net::HTTP.new(uri.host, uri.port)
  end.tap do |http|
    http.use_ssl = (uri.scheme == 'https')
    http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  end
end

#create_request(method, uri, config) ⇒ Object



349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/zoho_analytics_client.rb', line 349

def create_request(method, uri, config)
  encoded_config = nil
  if config && !config.empty?
    encoded_config = "CONFIG=#{ERB::Util.url_encode(config.to_json)}"
  end

  case method.upcase
  when 'GET'
    uri.query = encoded_config if encoded_config
    Net::HTTP::Get.new(uri)
  when 'POST'
    request = Net::HTTP::Post.new(uri)
    request.body = encoded_config if encoded_config
    request.content_type = 'application/x-www-form-urlencoded'
    request
  when 'PUT'
    request = Net::HTTP::Put.new(uri)
    request.body = encoded_config if encoded_config
    request.content_type = 'application/x-www-form-urlencoded'
    request
  when 'DELETE'
    request = Net::HTTP::Delete.new(uri)
    request.body = encoded_config if encoded_config
    request.content_type = 'application/x-www-form-urlencoded'
    request
  else
    raise ArgumentError, "Unsupported HTTP method: #{method}"
  end
end

#decrypt_and_read_fileObject



222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/zoho_analytics_client.rb', line 222

def decrypt_and_read_file
  raw = File.binread(token_file_path)
  iv = raw[0, 16]
  encrypted = raw[16..-1]
  decipher = OpenSSL::Cipher.new('AES-256-CBC')
  decipher.decrypt
  decipher.key = derive_file_key
  decipher.iv = iv
  decrypted = decipher.update(encrypted) + decipher.final
  JSON.parse(decrypted)
rescue
  {}
end

#decrypt_token(encrypted_token, refresh_token) ⇒ Object



199
200
201
202
203
204
205
206
207
208
# File 'lib/zoho_analytics_client.rb', line 199

def decrypt_token(encrypted_token, refresh_token)
  data = Base64.strict_decode64(encrypted_token)
  iv = data[0, 16]
  encrypted = data[16..-1]
  decipher = OpenSSL::Cipher.new('AES-256-CBC')
  decipher.decrypt
  decipher.key = derive_token_key(refresh_token)
  decipher.iv = iv
  decipher.update(encrypted) + decipher.final
end

#derive_file_keyObject



176
177
178
179
180
# File 'lib/zoho_analytics_client.rb', line 176

def derive_file_key
  secret = @oauth["clientId"].to_s + @oauth["clientSecret"].to_s
  salt = OpenSSL::Digest::SHA256.digest(secret)[0..15]
  OpenSSL::PKCS5.pbkdf2_hmac_sha1(secret, salt, 20_000, 32)
end

#derive_token_key(refresh_token) ⇒ Object



183
184
185
186
# File 'lib/zoho_analytics_client.rb', line 183

def derive_token_key(refresh_token)
  salt = OpenSSL::Digest::SHA256.digest(refresh_token)[0..15]
  OpenSSL::PKCS5.pbkdf2_hmac_sha1(refresh_token, salt, 20_000, 32)
end

#encrypt_and_write_file(token_map) ⇒ Object



210
211
212
213
214
215
216
217
218
219
220
# File 'lib/zoho_analytics_client.rb', line 210

def encrypt_and_write_file(token_map)
  cipher = OpenSSL::Cipher.new('AES-256-CBC')
  cipher.encrypt
  key = derive_file_key
  iv = cipher.random_iv
  cipher.key = key
  cipher.iv = iv
  data = token_map.to_json
  encrypted = cipher.update(data) + cipher.final
  File.open(token_file_path, "wb", perm: 0600) { |f| f.write(iv + encrypted) }
end

#encrypt_token(token, refresh_token) ⇒ Object



188
189
190
191
192
193
194
195
196
197
# File 'lib/zoho_analytics_client.rb', line 188

def encrypt_token(token, refresh_token)
  cipher = OpenSSL::Cipher.new('AES-256-CBC')
  cipher.encrypt
  key = derive_token_key(refresh_token)
  iv = cipher.random_iv
  cipher.key = key
  cipher.iv = iv
  encrypted = cipher.update(token) + cipher.final
  Base64.strict_encode64(iv + encrypted)
end

#ensure_access_tokenObject

Select and ensure access token using direct, cached, or refresh flow



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/zoho_analytics_client.rb', line 268

def ensure_access_token
  return if @access_token && @use_direct_access_token

  unless @oauth
    raise "OAuth credentials are missing. Provide either an access_token or a refresh_token with client_id and client_secret."
  end

  %w[clientId clientSecret refreshToken].each do |key|
    if @oauth[key].to_s.strip.empty?
      raise "Missing #{key} in OAuth configuration"
    end
  end

  refresh_token = @oauth["refreshToken"]

  if @store_path
    token = load_token_from_store(refresh_token)
    if token && !token.empty?
      @access_token = token
      return
    end
  end

  regenerate_access_token
end

#extract_error_from_response(response) ⇒ Object



562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
# File 'lib/zoho_analytics_client.rb', line 562

def extract_error_from_response(response)
  error_code = response.code.to_i
  error_message = nil
  begin
    body = JSON.parse(response.body)
    if body.is_a?(Hash)
      if body["data"]
        error_code = body["data"]["errorCode"] || error_code
        error_message = body["data"]["errorMessage"] || body["error"]["description"]
      end
      error_code ||= body["errorCode"]
      error_message ||= body["errorMessage"]
    end
  rescue JSON::ParserError
    error_message = response.body
  end
  error_message ||= "Unknown server error"
  [error_code, error_message]
end

#get_accounts_server_urlString

Gets the current accounts server URL

Returns:

  • (String)

    The current accounts server URL



619
620
621
# File 'lib/zoho_analytics_client.rb', line 619

def get_accounts_server_url
  @accounts_server_url
end

#get_analytics_server_urlString

Gets the current analytics server URL

Returns:

  • (String)

    The current analytics server URL



632
633
634
# File 'lib/zoho_analytics_client.rb', line 632

def get_analytics_server_url
  @analytics_server_url
end

#get_bulk_instance(org_id, workspace_id) ⇒ BulkAPI

Returns a new BulkAPI instance.

Parameters:

  • org_id (String)

    The id of the organization.

  • workspace_id (String)

    The id of the workspace.

Returns:



664
665
666
# File 'lib/zoho_analytics_client.rb', line 664

def get_bulk_instance(org_id, workspace_id)
  BulkAPI.new(self, org_id, workspace_id)
end

#get_dashboardsHash

Returns list of all accessible dashboards.

Returns:

  • (Hash)

    Dashboard list.

Raises:

  • (ServerError)

    If the server has received the request but did not process the request due to some error.

  • (ParseError)

    If the server has responded but client was not able to parse the response.



722
723
724
725
726
# File 'lib/zoho_analytics_client.rb', line 722

def get_dashboards
  endpoint = "/restapi/v2/dashboards"
  response = send_api_request("GET", endpoint, nil, nil)
  response
end

#get_org_instance(org_id) ⇒ OrgAPI

Returns a new OrgAPI instance.

Parameters:

  • org_id (String)

    The id of the organization.

Returns:

  • (OrgAPI)

    Organization API instance



639
640
641
# File 'lib/zoho_analytics_client.rb', line 639

def get_org_instance(org_id)
  OrgAPI.new(self, org_id)
end

#get_orgsArray

Returns list of all accessible organizations.

Returns:

  • (Array)

    Organization list.

Raises:

  • (ServerError)

    If the server has received the request but did not process the request due to some error.

  • (ParseError)

    If the server has responded but client was not able to parse the response.



672
673
674
675
676
# File 'lib/zoho_analytics_client.rb', line 672

def get_orgs
  endpoint = "/restapi/v2/orgs"
  response = send_api_request("GET", endpoint, nil, nil)
  response["orgs"]
end

#get_owned_dashboardsArray

Returns list of owned dashboards.

Returns:

  • (Array)

    Dashboard list.

Raises:

  • (ServerError)

    If the server has received the request but did not process the request due to some error.

  • (ParseError)

    If the server has responded but client was not able to parse the response.



732
733
734
735
736
# File 'lib/zoho_analytics_client.rb', line 732

def get_owned_dashboards
  endpoint = "/restapi/v2/dashboards/owned"
  response = send_api_request("GET", endpoint, nil, nil)
  response["views"]
end

#get_owned_workspacesArray

Returns list of owned workspaces.

Returns:

  • (Array)

    Workspace list.

Raises:

  • (ServerError)

    If the server has received the request but did not process the request due to some error.

  • (ParseError)

    If the server has responded but client was not able to parse the response.



692
693
694
695
696
# File 'lib/zoho_analytics_client.rb', line 692

def get_owned_workspaces
  endpoint = "/restapi/v2/workspaces/owned"
  response = send_api_request("GET", endpoint, nil, nil)
  response["workspaces"]
end

#get_recent_viewsArray

Returns list of recently accessed views.

Returns:

  • (Array)

    View list.

Raises:

  • (ServerError)

    If the server has received the request but did not process the request due to some error.

  • (ParseError)

    If the server has responded but client was not able to parse the response.



712
713
714
715
716
# File 'lib/zoho_analytics_client.rb', line 712

def get_recent_views
  endpoint = "/restapi/v2/recentviews"
  response = send_api_request("GET", endpoint, nil, nil)
  response["views"]
end

#get_shared_dashboardsArray

Returns list of shared dashboards.

Returns:

  • (Array)

    Dashboard list.

Raises:

  • (ServerError)

    If the server has received the request but did not process the request due to some error.

  • (ParseError)

    If the server has responded but client was not able to parse the response.



742
743
744
745
746
# File 'lib/zoho_analytics_client.rb', line 742

def get_shared_dashboards
  endpoint = "/restapi/v2/dashboards/shared"
  response = send_api_request("GET", endpoint, nil, nil)
  response["views"]
end

#get_shared_workspacesArray

Returns list of shared workspaces.

Returns:

  • (Array)

    Workspace list.

Raises:

  • (ServerError)

    If the server has received the request but did not process the request due to some error.

  • (ParseError)

    If the server has responded but client was not able to parse the response.



702
703
704
705
706
# File 'lib/zoho_analytics_client.rb', line 702

def get_shared_workspaces
  endpoint = "/restapi/v2/workspaces/shared"
  response = send_api_request("GET", endpoint, nil, nil)
  response["workspaces"]
end

#get_view_details(view_id, config = {}) ⇒ Hash

Returns details of the specified view.

Parameters:

  • view_id (String)

    Id of the view.

  • config (Hash) (defaults to: {})

    Contains any additional control parameters. Can be nil.

Returns:

  • (Hash)

    View details.

Raises:

  • (ServerError)

    If the server has received the request but did not process the request due to some error.

  • (ParseError)

    If the server has responded but client was not able to parse the response.



765
766
767
768
769
# File 'lib/zoho_analytics_client.rb', line 765

def get_view_details(view_id, config = {})
  endpoint = "/restapi/v2/views/#{view_id}"
  response = send_api_request("GET", endpoint, config, nil)
  response["views"]
end

#get_view_instance(org_id, workspace_id, view_id) ⇒ ViewAPI

Returns a new ViewAPI instance.

Parameters:

  • org_id (String)

    The id of the organization.

  • workspace_id (String)

    The id of the workspace.

  • view_id (String)

    The id of the view.

Returns:



656
657
658
# File 'lib/zoho_analytics_client.rb', line 656

def get_view_instance(org_id, workspace_id, view_id)
  ViewAPI.new(self, org_id, workspace_id, view_id)
end

#get_workspace_details(workspace_id) ⇒ Hash

Returns details of the specified workspace.

Parameters:

  • workspace_id (String)

    Id of the workspace.

Returns:

  • (Hash)

    Workspace details.

Raises:

  • (ServerError)

    If the server has received the request but did not process the request due to some error.

  • (ParseError)

    If the server has responded but client was not able to parse the response.



753
754
755
756
757
# File 'lib/zoho_analytics_client.rb', line 753

def get_workspace_details(workspace_id)
  endpoint = "/restapi/v2/workspaces/#{workspace_id}"
  response = send_api_request("GET", endpoint, nil, nil)
  response["workspaces"]
end

#get_workspace_instance(org_id, workspace_id) ⇒ WorkspaceAPI

Returns a new WorkspaceAPI instance.

Parameters:

  • org_id (String)

    The id of the organization.

  • workspace_id (String)

    The id of the workspace.

Returns:



647
648
649
# File 'lib/zoho_analytics_client.rb', line 647

def get_workspace_instance(org_id, workspace_id)
  WorkspaceAPI.new(self, org_id, workspace_id)
end

#get_workspacesHash

Returns list of all accessible workspaces.

Returns:

  • (Hash)

    Workspace list.

Raises:

  • (ServerError)

    If the server has received the request but did not process the request due to some error.

  • (ParseError)

    If the server has responded but client was not able to parse the response.



682
683
684
685
686
# File 'lib/zoho_analytics_client.rb', line 682

def get_workspaces
  endpoint = "/restapi/v2/workspaces"
  response = send_api_request("GET", endpoint, nil, nil)
  response
end

#handle_import_response_errors(response, url, config, headers, form_data) ⇒ Object



423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'lib/zoho_analytics_client.rb', line 423

def handle_import_response_errors(response, url, config, headers, form_data)
  unless response.is_a?(Net::HTTPSuccess)
    if is_oauth_expired(response)
      if @use_direct_access_token
        raise ServerError.new("Direct access token is invalid or expired.", true, response.code.to_i)
      else
        regenerate_access_token
        response = submit_import_request(url, config, headers, @access_token, form_data)
        unless response.is_a?(Net::HTTPSuccess)
          error_code, error_message = extract_error_from_response(response)
          raise ServerError.new("HTTP #{error_code}: #{error_message}", false, error_code)
        end
      end
    else
      error_code, error_message = extract_error_from_response(response)
      raise ServerError.new("HTTP #{error_code}: #{error_message}", false, error_code)
    end
  end
  response
end

#handle_response_errors(response, http:, request:) ⇒ Object



540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
# File 'lib/zoho_analytics_client.rb', line 540

def handle_response_errors(response, http:, request:)
  unless response.is_a?(Net::HTTPSuccess)
    if is_oauth_expired(response)
      if @use_direct_access_token
        raise ServerError.new("Direct access token is invalid or expired.", true, response.code.to_i)
      else
        regenerate_access_token
        request['Authorization'] = "Zoho-oauthtoken #{@access_token}"
        response = http.request(request)
        unless response.is_a?(Net::HTTPSuccess)
          error_code, error_message = extract_error_from_response(response)
          raise ServerError.new("HTTP #{error_code}: #{error_message}", false, error_code)
        end
      end
    else
      error_code, error_message = extract_error_from_response(response)
      raise ServerError.new("HTTP #{error_code}: #{error_message}", false, error_code)
    end
  end
  response
end

#is_oauth_expired(response) ⇒ Object

Check if OAuth token is expired



599
600
601
602
603
604
605
606
607
608
# File 'lib/zoho_analytics_client.rb', line 599

def is_oauth_expired(response)
  return false unless response.body
  begin
    json_response = JSON.parse(response.body)
    errorCode = json_response["data"] && json_response["data"]["errorCode"]
    return errorCode == 8535
  rescue JSON::ParserError
    return false
  end
end

#key_for_refresh_token(refresh_token) ⇒ Object



236
237
238
239
240
# File 'lib/zoho_analytics_client.rb', line 236

def key_for_refresh_token(refresh_token)
  key = derive_token_key(refresh_token)
  digest = OpenSSL::HMAC.digest('sha256', key, refresh_token)
  Base64.strict_encode64(digest)
end

#load_token_from_store(refresh_token) ⇒ Object



250
251
252
253
254
# File 'lib/zoho_analytics_client.rb', line 250

def load_token_from_store(refresh_token)
  content = decrypt_and_read_file rescue {}
  key = key_for_refresh_token(refresh_token)
  content[key] ? decrypt_token(content[key], refresh_token) : nil
end

#regenerate_access_tokenObject

Regenerate access_token from refresh_token, persist in token file encrypted per logic above



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/zoho_analytics_client.rb', line 296

def regenerate_access_token
  unless @oauth
    raise "OAuth credentials are missing. Provide either an access_token or a refresh_token with client_id and client_secret."
  end

  url = "#{@accounts_server_url}/oauth/v2/token"
  uri = URI(url)
  http = create_http_client(uri)
  request = Net::HTTP::Post.new(uri)
  request.set_form_data({
    'refresh_token' => @oauth["refreshToken"],
    'client_id'     => @oauth["clientId"],
    'client_secret' => @oauth["clientSecret"],
    'grant_type'    => 'refresh_token'
  })

  response = http.request(request)

  unless response.is_a?(Net::HTTPSuccess)
    raise "Failed to obtain new Zoho Analytics access token: #{response.code} -- #{response.body}"
  end

  json_response = JSON.parse(response.body)
  access_token = json_response["access_token"]
  unless access_token
    raise "Invalid token response: #{response.body}"
  end

  @access_token = access_token
  save_token_to_store(@oauth["refreshToken"], access_token) if @store_path
  cleanup_obsolete_token_files
  @access_token
end

#save_token_to_store(refresh_token, access_token) ⇒ Object



242
243
244
245
246
247
248
# File 'lib/zoho_analytics_client.rb', line 242

def save_token_to_store(refresh_token, access_token)
  content = decrypt_and_read_file rescue {}
  key = key_for_refresh_token(refresh_token)
  encrypted_access_token = encrypt_token(access_token, refresh_token)
  content[key] = encrypted_access_token
  encrypt_and_write_file(content)
end

#send_api_request(method, endpoint, config, request_headers) ⇒ Object



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/zoho_analytics_client.rb', line 379

def send_api_request(method, endpoint, config, request_headers)
  ensure_access_token
  uri = URI("#{@analytics_server_url}#{endpoint}")
  http = create_http_client(uri)
  request = create_request(method, uri, config)
  add_headers(request, request_headers)
  request['Authorization'] = "Zoho-oauthtoken #{@access_token}"
  request['User-Agent'] = "Analytics Ruby Client v#{CLIENT_VERSION}"
      
  response = http.request(request)
  response = handle_response_errors(response, http: http, request: request)
  
  return nil if response.code.to_i == 204 || response.body.nil? || response.body.strip.empty?

  begin
    result = JSON.parse(response.body)
    return result["data"]
  rescue JSON::ParserError
    raise ParseError.new("Failed to parse response: #{response.body}")
  end
end

#send_batch_import_api_request(endpoint, config, request_headers, file_path, batch_size, tool_config) ⇒ Object

Send batch import API request to the server



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# File 'lib/zoho_analytics_client.rb', line 446

def send_batch_import_api_request(endpoint, config, request_headers, file_path, batch_size, tool_config)
  ensure_access_token
  file_header = File.open(file_path, 'r').readline
  file_lines  = File.readlines(file_path).drop(1) # Skip header
  total_lines = file_lines.size
  total_batch_count = (total_lines.to_f / batch_size).ceil
  config["batchKey"] = "start"
  url = @analytics_server_url + endpoint
  response = nil
  total_batch_count.times do |i|
    batch       = file_lines.slice(i * batch_size, batch_size).join
    batch_content = file_header + batch
    temp_file = Tempfile.new(['batch_data_', '.csv'])
    temp_file.write(batch_content)
    temp_file.rewind

    config["isLastBatch"] = (i == total_batch_count - 1).to_s
    form_data = {
      'CONFIG' => config.to_json,
      'FILE'   => File.open(temp_file.path)
    }

    puts "[DEBUG] Sending batch #{i + 1}/#{total_batch_count}, lines=#{batch.split("\n").size}"

    resp = submit_import_request(url, config.to_json, request_headers, @access_token, form_data)
    resp = handle_import_response_errors(resp, url, config.to_json, request_headers, form_data)

    begin
      response = JSON.parse(resp.body)
      config["batchKey"] = response["data"]["batchKey"]
      sleep(2)
    rescue JSON::ParserError
      raise ParseError.new("Failed to parse response: #{resp.body}")
    ensure
      form_data['FILE'].close if form_data['FILE']
      temp_file.close
      temp_file.unlink
    end
  end
  response["data"]
end

#send_export_api_request(endpoint, config, request_headers, file_path) ⇒ Object

Send export API request to the server



490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
# File 'lib/zoho_analytics_client.rb', line 490

def send_export_api_request(endpoint, config, request_headers, file_path)
  ensure_access_token
  url = @analytics_server_url + endpoint
  uri = URI(url)
  uri.query = URI.encode_www_form('CONFIG' => config.to_json) if config && !config.empty?
  http = create_http_client(uri)
  request = Net::HTTP::Get.new(uri)
  add_headers(request, request_headers)
  request['Authorization'] = "Zoho-oauthtoken #{@access_token}"
  request['User-Agent'] = "Analytics Ruby Client v#{CLIENT_VERSION}"
  response = http.request(request)

  unless response.is_a?(Net::HTTPSuccess)
    if is_oauth_expired(response)
      if @use_direct_access_token
        raise ServerError.new("Direct access token is invalid or expired.", true, response.code.to_i)
      else
        regenerate_access_token
        request['Authorization'] = "Zoho-oauthtoken #{@access_token}"
        response = http.request(request)
        unless response.is_a?(Net::HTTPSuccess)
          error_code, error_message = extract_error_from_response(response)
          raise ServerError.new("HTTP #{error_code}: #{error_message}", false, error_code)
        end
      end
    else
      error_code, error_message = extract_error_from_response(response)
      raise ServerError.new("HTTP #{error_code}: #{error_message}", false, error_code)
    end
  end

  File.open(file_path, 'wb') { |file| file.write(response.body) }
end

#send_import_api_request(endpoint, config, request_headers, file_path, data = nil) ⇒ Object

Send import API request to the server



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
# File 'lib/zoho_analytics_client.rb', line 403

def send_import_api_request(endpoint, config, request_headers, file_path, data = nil)
  ensure_access_token
  url = @analytics_server_url + endpoint
  form_data = { 'CONFIG' => config.to_json }
  form_data['DATA'] = data.to_json if data
  form_data['FILE'] = File.open(file_path) if file_path
  response = submit_import_request(url, config.to_json, request_headers, @access_token, form_data)

  response = handle_import_response_errors(response, url, config, request_headers, form_data)

  begin
    result = JSON.parse(response.body)
    return result["data"]
  rescue JSON::ParserError
    raise ParseError.new("Failed to parse response: #{response.body}")
  ensure
    form_data['FILE'].close if form_data['FILE']
  end
end

#set_access_token(token) ⇒ Object

Sets the access token directly on the AnalyticsClient instance, bypassing the need for OAuth token refresh or regeneration.



260
261
262
263
264
# File 'lib/zoho_analytics_client.rb', line 260

def set_access_token(token)
  @access_token = token
  @use_direct_access_token = true
  self
end

#set_accounts_server_url(url) ⇒ String

Sets the accounts server URL

Parameters:

  • url (String)

    The accounts server URL

Returns:

  • (String)

    The updated accounts server URL



613
614
615
# File 'lib/zoho_analytics_client.rb', line 613

def set_accounts_server_url(url)
  @accounts_server_url = url
end

#set_analytics_server_url(url) ⇒ String

Sets the analytics server URL

Parameters:

  • url (String)

    The analytics server URL

Returns:

  • (String)

    The updated analytics server URL



626
627
628
# File 'lib/zoho_analytics_client.rb', line 626

def set_analytics_server_url(url)
  @analytics_server_url = url
end

#setup_from_builder(builder) ⇒ Object

INSTANCE SETUP ==========

Raises:

  • (ArgumentError)


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/zoho_analytics_client.rb', line 128

def setup_from_builder(builder)
  @data_center = builder.instance_variable_get(:@data_center)
  @oauth = builder.instance_variable_get(:@oauth) || {}

  if @oauth && !@oauth.empty?
    missing_keys = []
    missing_keys << "clientId" unless @oauth.key?("clientId") && !@oauth["clientId"].to_s.strip.empty?
    missing_keys << "clientSecret" unless @oauth.key?("clientSecret") && !@oauth["clientSecret"].to_s.strip.empty?
    missing_keys << "refreshToken" unless @oauth.key?("refreshToken") && !@oauth["refreshToken"].to_s.strip.empty?

    unless missing_keys.empty?
      raise ArgumentError, "OAuth hash is missing required keys or they are empty: #{missing_keys.join(', ')}"
    end
  end

  @proxy_details = builder.instance_variable_get(:@proxy_details)
  @store_path = builder.instance_variable_get(:@token_store_path)
  @access_token = builder.instance_variable_get(:@access_token)
  @use_direct_access_token = builder.instance_variable_get(:@use_direct_access_token)

  @proxy = !!@proxy_details
  if @proxy
    @proxy_host = @proxy_details[:host]
    @proxy_port = @proxy_details[:port]
    @proxy_user_name = @proxy_details[:username]
    @proxy_password = @proxy_details[:password]
  end

  dc = DC_URLS[@data_center]
  raise ArgumentError, "Invalid dataCenter: #{@data_center}. Valid: #{DC_URLS.keys.join(', ')}" unless dc

  @accounts_server_url = dc["accounts"]
  @analytics_server_url = dc["analytics"]
end

#submit_import_request(url, config_data, headers, access_token, files) ⇒ Object

Submit import request (multipart/form-data)



526
527
528
529
530
531
532
533
534
535
536
537
538
# File 'lib/zoho_analytics_client.rb', line 526

def submit_import_request(url, config_data, headers, access_token, files)
  uri = URI(url)
  http = create_http_client(uri)
  request = Net::HTTP::Post.new(uri)
  headers&.each { |key, value| request[key] = value }
  request['Authorization'] = "Zoho-oauthtoken #{access_token}"
  request['User-Agent']   = "Analytics Ruby Client v#{CLIENT_VERSION}"
  form_data = {}
  form_data['CONFIG'] = config_data.is_a?(Hash) ? config_data.to_json : config_data
  files.each { |key, file| form_data[key] = file }
  request.set_form(form_data, 'multipart/form-data')
  http.request(request)
end

#token_file_pathObject

ENCRYPTED TOKEN STORAGE ==========


167
168
169
170
171
172
173
# File 'lib/zoho_analytics_client.rb', line 167

def token_file_path
  client_id = @oauth["clientId"].to_s
  client_secret = @oauth["clientSecret"].to_s
  hash = Digest::SHA256.hexdigest(client_id + client_secret)
  FileUtils.mkdir_p(@store_path) unless Dir.exist?(@store_path)
  File.join(@store_path, "za_tokens_#{hash}.dat")
end