Class: ShelfWatch::Client

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

Overview

Synchronous client for ShelfWatch APIs v2.

Authenticate with either an API key or OAuth client credentials. Credentials are project-scoped (create them in Console → Integrations).

Example (API key):

client = ShelfWatch::Client.new(
  api_key: "swpk_…",
  project_id: "PROJECT_UUID",
)
visits = client.visits.list(
  start_date: "2026-07-01",
  end_date: "2026-07-31",
)

Example (OAuth):

client = ShelfWatch::Client.new(
  client_id: "swoc_…",
  client_secret: "swocs_…",
  project_id: "PROJECT_UUID",
)

Constant Summary collapse

DEFAULT_BASE_URL =
"https://api.shelfwatch.io"
DEFAULT_TIMEOUT =
60.0
TOKEN_SKEW_SECONDS =

Refresh OAuth tokens this many seconds before expires_in.

60

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key: nil, client_id: nil, client_secret: nil, project_id: nil, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT, connection: nil) ⇒ Client

Returns a new instance of Client.



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
# File 'lib/shelfwatch/client.rb', line 39

def initialize(
  api_key: nil,
  client_id: nil,
  client_secret: nil,
  project_id: nil,
  base_url: DEFAULT_BASE_URL,
  timeout: DEFAULT_TIMEOUT,
  connection: nil
)
  has_key = !api_key.nil? && api_key != ""
  has_oauth = (!client_id.nil? && client_id != "") ||
              (!client_secret.nil? && client_secret != "")

  if has_key && has_oauth
    raise ValidationError, "Provide either api_key or client_id/client_secret, not both"
  end
  if has_oauth && (client_id.nil? || client_id == "" || client_secret.nil? || client_secret == "")
    raise ValidationError, "Both client_id and client_secret are required"
  end
  unless has_key || has_oauth
    raise ValidationError, "Provide api_key or client_id and client_secret"
  end

  @project_id = project_id
  @base_url = base_url.to_s.sub(%r{/+\z}, "")
  @api_key = has_key ? api_key : nil
  @client_id = has_oauth ? client_id : nil
  @client_secret = has_oauth ? client_secret : nil
  @access_token = nil
  @token_expires_at = 0.0

  @connection = connection || Faraday.new(url: @base_url) do |f|
    f.options.timeout = timeout
    f.options.open_timeout = timeout
    f.headers["Accept"] = "application/json"
    f.headers["User-Agent"] = "shelfwatch-ruby/#{VERSION}"
    f.adapter Faraday.default_adapter
  end

  @visits = Resources::Visits.new(self)
  @mdm = Resources::Mdm.new(self)
  @reports = Resources::Reports.new(self)

  return unless block_given?

  begin
    yield self
  ensure
    close
  end
end

Instance Attribute Details

#base_urlObject (readonly)

Returns the value of attribute base_url.



37
38
39
# File 'lib/shelfwatch/client.rb', line 37

def base_url
  @base_url
end

#mdmObject (readonly)

Returns the value of attribute mdm.



37
38
39
# File 'lib/shelfwatch/client.rb', line 37

def mdm
  @mdm
end

#project_idObject (readonly)

Returns the value of attribute project_id.



37
38
39
# File 'lib/shelfwatch/client.rb', line 37

def project_id
  @project_id
end

#reportsObject (readonly)

Returns the value of attribute reports.



37
38
39
# File 'lib/shelfwatch/client.rb', line 37

def reports
  @reports
end

#visitsObject (readonly)

Returns the value of attribute visits.



37
38
39
# File 'lib/shelfwatch/client.rb', line 37

def visits
  @visits
end

Instance Method Details

#closeObject

Close the underlying HTTP connection.



146
147
148
# File 'lib/shelfwatch/client.rb', line 146

def close
  @connection.close if @connection.respond_to?(:close)
end

#connectionObject

Expose the underlying Faraday connection (useful for tests).



141
142
143
# File 'lib/shelfwatch/client.rb', line 141

def connection
  @connection
end

#healthObject

Call GET /health (no auth).



131
132
133
134
135
136
137
138
# File 'lib/shelfwatch/client.rb', line 131

def health
  response = @connection.get("/health")
  Http.raise_for_status(response.status, response.body, response.reason_phrase)
  payload = JSON.parse(response.body)
  return { "data" => payload } unless payload.is_a?(Hash)

  payload
end

#project(project_id = nil) ⇒ Object

Resolve project_id from the call or the client default.



92
93
94
95
96
97
98
99
# File 'lib/shelfwatch/client.rb', line 92

def project(project_id = nil)
  value = project_id || @project_id
  if value.nil? || value == ""
    raise ValidationError,
          "project_id is required (pass it to ShelfWatch::Client.new(...) or to this method)"
  end
  value
end

#request(method, path, params: nil, data: nil, json: nil) ⇒ Object

Send an authenticated request and return the JSON body.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/shelfwatch/client.rb', line 102

def request(method, path, params: nil, data: nil, json: nil)
  token = ensure_token
  response = @connection.run_request(method.downcase.to_sym, path, nil, {}) do |req|
    req.headers["Authorization"] = "Bearer #{token}"
    req.params.update(stringify_keys(params)) if params
    if data
      req.headers["Content-Type"] = "application/x-www-form-urlencoded"
      req.body = URI.encode_www_form(stringify_keys(data))
    elsif !json.nil?
      req.headers["Content-Type"] = "application/json"
      req.body = JSON.generate(json)
    end
  end

  Http.raise_for_status(response.status, response.body, response.reason_phrase)

  return {} if response.status == 204 || response.body.nil? || response.body.empty?

  begin
    payload = JSON.parse(response.body)
  rescue JSON::ParserError
    return { "data" => response.body }
  end
  return { "data" => payload } unless payload.is_a?(Hash)

  payload
end