Class: HighLevel::Client

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

Overview

The entry point for the SDK. Construct one with credentials, then reach each API app as a resource: client.contacts, client.calendars, and so on (see RESOURCE_REGISTRY). OAuth flows live on client.oauth.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = nil, **opts) ⇒ Client

Returns a new instance of Client.

Parameters:

  • config (HighLevel::Configuration, Hash, nil) (defaults to: nil)

    a configuration object, a hash of configuration options, or nil to build one entirely from keyword arguments

  • opts (Hash)

    configuration options, merged over config

Raises:



24
25
26
27
28
29
30
# File 'lib/high_level/client.rb', line 24

def initialize(config = nil, **opts)
  @config = coerce_config(config, opts)
  validate!
  initialize_storage
  @oauth = Oauth.new(config: @config)
  @connection = build_connection
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, **kwargs, &block) ⇒ Object

Resolves client.<app> to a memoized resource instance via RESOURCE_REGISTRY.



39
40
41
42
43
44
45
# File 'lib/high_level/client.rb', line 39

def method_missing(name, *args, **kwargs, &block)
  klass = RESOURCE_REGISTRY[name]
  return super unless klass && args.empty? && kwargs.empty? && block.nil?

  @resources ||= {}
  @resources[name] ||= klass.new(self)
end

Instance Attribute Details

#configHighLevel::Configuration (readonly)

Returns the resolved, immutable configuration.

Returns:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/high_level/client.rb', line 16

class Client
  attr_reader :config, :connection, :oauth

  # @param config [HighLevel::Configuration, Hash, nil] a configuration
  #   object, a hash of configuration options, or nil to build one
  #   entirely from keyword arguments
  # @param opts [Hash] configuration options, merged over +config+
  # @raise [ConfigurationError] when no usable credentials are supplied
  def initialize(config = nil, **opts)
    @config = coerce_config(config, opts)
    validate!
    initialize_storage
    @oauth = Oauth.new(config: @config)
    @connection = build_connection
  end

  # @return [Boolean] whether +name+ is a registered resource accessor
  def respond_to_missing?(name, include_private = false)
    RESOURCE_REGISTRY.key?(name) || super
  end

  # Resolves +client.<app>+ to a memoized resource instance via
  # +RESOURCE_REGISTRY+.
  def method_missing(name, *args, **kwargs, &block)
    klass = RESOURCE_REGISTRY[name]
    return super unless klass && args.empty? && kwargs.empty? && block.nil?

    @resources ||= {}
    @resources[name] ||= klass.new(self)
  end

  private

  def coerce_config(config, opts)
    case config
    when Configuration then config
    when Hash          then Configuration.new(**config, **opts)
    when nil           then Configuration.new(**opts)
    else
      raise ConfigurationError,
            "expected HighLevel::Configuration or Hash, got #{config.class}"
    end
  end

  def validate!
    return if any_token? || oauth_client_pair?

    raise ConfigurationError,
          "HighLevel::Client requires one of: private_integration_token, " \
          "agency_access_token, location_access_token, or client_id+client_secret"
  end

  def any_token?
    @config.private_integration_token ||
      @config.agency_access_token ||
      @config.location_access_token
  end

  def oauth_client_pair?
    @config.client_id && @config.client_secret
  end

  def initialize_storage
    storage = @config.session_storage
    return if storage.nil?

    storage.set_client_id(@config.client_id) if @config.client_id
    storage.init
  end

  def build_connection
    resolver = TokenResolver.new(config: @config, storage: @config.session_storage)
    refresher = TokenRefresher.new(config: @config, oauth: @oauth, storage: @config.session_storage)
    build_faraday(resolver, refresher)
  end

  def build_faraday(resolver, refresher)
    Faraday.new(url: @config.base_url) do |f|
      f.use Middleware::Instrumentation, instrumenter: @config.instrumenter
      f.use Middleware::RefreshOn401, refresher: refresher, resolver: resolver
      f.use Middleware::Authentication, config: @config, resolver: resolver
      f.request :json
      f.use Middleware::ErrorHandler
      f.response :json, content_type: /\bjson\b/
      f.adapter Faraday.default_adapter
    end
  end
end

#connectionFaraday::Connection (readonly)

Returns the underlying HTTP connection with the full middleware stack.

Returns:

  • (Faraday::Connection)

    the underlying HTTP connection with the full middleware stack



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/high_level/client.rb', line 16

class Client
  attr_reader :config, :connection, :oauth

  # @param config [HighLevel::Configuration, Hash, nil] a configuration
  #   object, a hash of configuration options, or nil to build one
  #   entirely from keyword arguments
  # @param opts [Hash] configuration options, merged over +config+
  # @raise [ConfigurationError] when no usable credentials are supplied
  def initialize(config = nil, **opts)
    @config = coerce_config(config, opts)
    validate!
    initialize_storage
    @oauth = Oauth.new(config: @config)
    @connection = build_connection
  end

  # @return [Boolean] whether +name+ is a registered resource accessor
  def respond_to_missing?(name, include_private = false)
    RESOURCE_REGISTRY.key?(name) || super
  end

  # Resolves +client.<app>+ to a memoized resource instance via
  # +RESOURCE_REGISTRY+.
  def method_missing(name, *args, **kwargs, &block)
    klass = RESOURCE_REGISTRY[name]
    return super unless klass && args.empty? && kwargs.empty? && block.nil?

    @resources ||= {}
    @resources[name] ||= klass.new(self)
  end

  private

  def coerce_config(config, opts)
    case config
    when Configuration then config
    when Hash          then Configuration.new(**config, **opts)
    when nil           then Configuration.new(**opts)
    else
      raise ConfigurationError,
            "expected HighLevel::Configuration or Hash, got #{config.class}"
    end
  end

  def validate!
    return if any_token? || oauth_client_pair?

    raise ConfigurationError,
          "HighLevel::Client requires one of: private_integration_token, " \
          "agency_access_token, location_access_token, or client_id+client_secret"
  end

  def any_token?
    @config.private_integration_token ||
      @config.agency_access_token ||
      @config.location_access_token
  end

  def oauth_client_pair?
    @config.client_id && @config.client_secret
  end

  def initialize_storage
    storage = @config.session_storage
    return if storage.nil?

    storage.set_client_id(@config.client_id) if @config.client_id
    storage.init
  end

  def build_connection
    resolver = TokenResolver.new(config: @config, storage: @config.session_storage)
    refresher = TokenRefresher.new(config: @config, oauth: @oauth, storage: @config.session_storage)
    build_faraday(resolver, refresher)
  end

  def build_faraday(resolver, refresher)
    Faraday.new(url: @config.base_url) do |f|
      f.use Middleware::Instrumentation, instrumenter: @config.instrumenter
      f.use Middleware::RefreshOn401, refresher: refresher, resolver: resolver
      f.use Middleware::Authentication, config: @config, resolver: resolver
      f.request :json
      f.use Middleware::ErrorHandler
      f.response :json, content_type: /\bjson\b/
      f.adapter Faraday.default_adapter
    end
  end
end

#oauthHighLevel::Oauth (readonly)

Returns the OAuth flow client.

Returns:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/high_level/client.rb', line 16

class Client
  attr_reader :config, :connection, :oauth

  # @param config [HighLevel::Configuration, Hash, nil] a configuration
  #   object, a hash of configuration options, or nil to build one
  #   entirely from keyword arguments
  # @param opts [Hash] configuration options, merged over +config+
  # @raise [ConfigurationError] when no usable credentials are supplied
  def initialize(config = nil, **opts)
    @config = coerce_config(config, opts)
    validate!
    initialize_storage
    @oauth = Oauth.new(config: @config)
    @connection = build_connection
  end

  # @return [Boolean] whether +name+ is a registered resource accessor
  def respond_to_missing?(name, include_private = false)
    RESOURCE_REGISTRY.key?(name) || super
  end

  # Resolves +client.<app>+ to a memoized resource instance via
  # +RESOURCE_REGISTRY+.
  def method_missing(name, *args, **kwargs, &block)
    klass = RESOURCE_REGISTRY[name]
    return super unless klass && args.empty? && kwargs.empty? && block.nil?

    @resources ||= {}
    @resources[name] ||= klass.new(self)
  end

  private

  def coerce_config(config, opts)
    case config
    when Configuration then config
    when Hash          then Configuration.new(**config, **opts)
    when nil           then Configuration.new(**opts)
    else
      raise ConfigurationError,
            "expected HighLevel::Configuration or Hash, got #{config.class}"
    end
  end

  def validate!
    return if any_token? || oauth_client_pair?

    raise ConfigurationError,
          "HighLevel::Client requires one of: private_integration_token, " \
          "agency_access_token, location_access_token, or client_id+client_secret"
  end

  def any_token?
    @config.private_integration_token ||
      @config.agency_access_token ||
      @config.location_access_token
  end

  def oauth_client_pair?
    @config.client_id && @config.client_secret
  end

  def initialize_storage
    storage = @config.session_storage
    return if storage.nil?

    storage.set_client_id(@config.client_id) if @config.client_id
    storage.init
  end

  def build_connection
    resolver = TokenResolver.new(config: @config, storage: @config.session_storage)
    refresher = TokenRefresher.new(config: @config, oauth: @oauth, storage: @config.session_storage)
    build_faraday(resolver, refresher)
  end

  def build_faraday(resolver, refresher)
    Faraday.new(url: @config.base_url) do |f|
      f.use Middleware::Instrumentation, instrumenter: @config.instrumenter
      f.use Middleware::RefreshOn401, refresher: refresher, resolver: resolver
      f.use Middleware::Authentication, config: @config, resolver: resolver
      f.request :json
      f.use Middleware::ErrorHandler
      f.response :json, content_type: /\bjson\b/
      f.adapter Faraday.default_adapter
    end
  end
end

Instance Method Details

#respond_to_missing?(name, include_private = false) ⇒ Boolean

Returns whether name is a registered resource accessor.

Returns:

  • (Boolean)

    whether name is a registered resource accessor



33
34
35
# File 'lib/high_level/client.rb', line 33

def respond_to_missing?(name, include_private = false)
  RESOURCE_REGISTRY.key?(name) || super
end