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.7.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



25
26
27
# File 'lib/zoho_analytics_client.rb', line 25

def self.new
  Builder.new
end

Instance Method Details

#add_headers(request, headers) ⇒ Object



580
581
582
# File 'lib/zoho_analytics_client.rb', line 580

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

#cleanup_obsolete_token_filesObject



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/zoho_analytics_client.rb', line 324

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



569
570
571
572
573
574
575
576
577
578
# File 'lib/zoho_analytics_client.rb', line 569

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



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/zoho_analytics_client.rb', line 342

def create_request(method, uri, config)
  case method.upcase
  when 'GET'
    uri.query = URI.encode_www_form({ 'CONFIG' => config.to_json }) if config && !config.empty?
    Net::HTTP::Get.new(uri)
  when 'POST'
    request = Net::HTTP::Post.new(uri)
    request.body = "CONFIG=#{config.to_json}" if config && !config.empty?
    request.content_type = 'application/x-www-form-urlencoded'
    request
  when 'PUT'
    request = Net::HTTP::Put.new(uri)
    request.body = "CONFIG=#{config.to_json}" if config && !config.empty?
    request.content_type = 'application/x-www-form-urlencoded'
    request
  when 'DELETE'
    request = Net::HTTP::Delete.new(uri)
    request.body = "CONFIG=#{config.to_json}" if config && !config.empty?
    request.content_type = 'application/x-www-form-urlencoded'
    request
  else
    raise ArgumentError, "Unsupported HTTP method: #{method}"
  end
end

#decrypt_and_read_fileObject



218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/zoho_analytics_client.rb', line 218

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



195
196
197
198
199
200
201
202
203
204
# File 'lib/zoho_analytics_client.rb', line 195

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



172
173
174
175
176
# File 'lib/zoho_analytics_client.rb', line 172

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



179
180
181
182
# File 'lib/zoho_analytics_client.rb', line 179

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



206
207
208
209
210
211
212
213
214
215
216
# File 'lib/zoho_analytics_client.rb', line 206

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



184
185
186
187
188
189
190
191
192
193
# File 'lib/zoho_analytics_client.rb', line 184

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



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/zoho_analytics_client.rb', line 264

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

  refresh_token = @oauth["refreshToken"]
  if refresh_token.to_s.strip.empty?
    raise "Missing refresh_token in OAuth configuration"
  end

  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



548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
# File 'lib/zoho_analytics_client.rb', line 548

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



605
606
607
# File 'lib/zoho_analytics_client.rb', line 605

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



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

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:



650
651
652
# File 'lib/zoho_analytics_client.rb', line 650

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.



708
709
710
711
712
# File 'lib/zoho_analytics_client.rb', line 708

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



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

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.



658
659
660
661
662
# File 'lib/zoho_analytics_client.rb', line 658

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.



718
719
720
721
722
# File 'lib/zoho_analytics_client.rb', line 718

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.



678
679
680
681
682
# File 'lib/zoho_analytics_client.rb', line 678

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.



698
699
700
701
702
# File 'lib/zoho_analytics_client.rb', line 698

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.



728
729
730
731
732
# File 'lib/zoho_analytics_client.rb', line 728

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.



688
689
690
691
692
# File 'lib/zoho_analytics_client.rb', line 688

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.



751
752
753
754
755
# File 'lib/zoho_analytics_client.rb', line 751

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:



642
643
644
# File 'lib/zoho_analytics_client.rb', line 642

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.



739
740
741
742
743
# File 'lib/zoho_analytics_client.rb', line 739

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:



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

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.



668
669
670
671
672
# File 'lib/zoho_analytics_client.rb', line 668

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



409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'lib/zoho_analytics_client.rb', line 409

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



526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'lib/zoho_analytics_client.rb', line 526

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



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

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



232
233
234
235
236
# File 'lib/zoho_analytics_client.rb', line 232

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



246
247
248
249
250
# File 'lib/zoho_analytics_client.rb', line 246

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



289
290
291
292
293
294
295
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
# File 'lib/zoho_analytics_client.rb', line 289

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



238
239
240
241
242
243
244
# File 'lib/zoho_analytics_client.rb', line 238

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



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/zoho_analytics_client.rb', line 367

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)

  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



432
433
434
435
436
437
438
439
440
441
442
443
444
445
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
# File 'lib/zoho_analytics_client.rb', line 432

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



476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
# File 'lib/zoho_analytics_client.rb', line 476

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



389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/zoho_analytics_client.rb', line 389

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.



256
257
258
259
260
# File 'lib/zoho_analytics_client.rb', line 256

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



599
600
601
# File 'lib/zoho_analytics_client.rb', line 599

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



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

def set_analytics_server_url(url)
  @analytics_server_url = url
end

#setup_from_builder(builder) ⇒ Object

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

Raises:

  • (ArgumentError)


124
125
126
127
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
# File 'lib/zoho_analytics_client.rb', line 124

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)



512
513
514
515
516
517
518
519
520
521
522
523
524
# File 'lib/zoho_analytics_client.rb', line 512

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 ==========


163
164
165
166
167
168
169
# File 'lib/zoho_analytics_client.rb', line 163

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