Class: Leash::Transport Private
- Inherits:
-
Object
- Object
- Leash::Transport
- Defined in:
- lib/leash/transport.rb
Overview
This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.
Shared HTTP transport used by every integration POST. Mirrors the ‘_call` / `_post` private methods on the TS `Leash` class and the Python `_Transport`.
Critical platform contract (Critical #1 in the 0.4 plan):
* `X-API-Key` carries the app key (`LEASH_API_KEY`)
* `Cookie: leash-auth=…` forwards the browser session
The user JWT extracted from an inbound ‘Authorization: Bearer …` header is intentionally NOT forwarded — the TS SDK warns that the JWT path causes the platform’s ‘verifyToken()` to reject before the X-API-Key check runs, producing a misleading 401 on every integration call. Bearer tokens are still accepted by `Leash` for other code paths (env.get fallback, future CLI flows) but never sent on integration POSTs. A negative-assertion test covers this.
Constant Summary collapse
- DEFAULT_OPEN_TIMEOUT =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
10- DEFAULT_READ_TIMEOUT =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
30
Instance Attribute Summary collapse
- #api_key ⇒ Object readonly private
- #cookie_value ⇒ Object readonly private
- #platform_url ⇒ Object readonly private
Instance Method Summary collapse
- #build_request(uri, method:, headers:, body:) ⇒ Object private
-
#call(provider, action, params = nil) ⇒ Object
private
POST to ‘/api/integrations/provider/action` and return the parsed response body (after unwrapping `data`).
- #get_json(url, headers: {}) ⇒ Object
- #handle_response(response, docs_url:) ⇒ Object private
-
#initialize(platform_url:, api_key: nil, cookie_value: nil, open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT, http_runner: nil) ⇒ Transport
constructor
private
A new instance of Transport.
- #parse_json(body) ⇒ Object private
- #post_json(url, params, docs_url:) ⇒ Object
- #raise_for_status(status, body, docs_url:) ⇒ Object private
- #run_request(url, method:, headers:, body:) ⇒ Object private
Constructor Details
#initialize(platform_url:, api_key: nil, cookie_value: nil, open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT, http_runner: nil) ⇒ Transport
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns a new instance of Transport.
34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/leash/transport.rb', line 34 def initialize(platform_url:, api_key: nil, cookie_value: nil, open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT, http_runner: nil) @platform_url = platform_url.to_s.sub(%r{/+\z}, "") @api_key = api_key @cookie_value = @open_timeout = open_timeout @read_timeout = read_timeout # Test seam — allow injecting a custom HTTP runner block. # If nil, calls go through `Net::HTTP.start`. @http_runner = http_runner end |
Instance Attribute Details
#api_key ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
32 33 34 |
# File 'lib/leash/transport.rb', line 32 def api_key @api_key end |
#cookie_value ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
32 33 34 |
# File 'lib/leash/transport.rb', line 32 def @cookie_value end |
#platform_url ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
32 33 34 |
# File 'lib/leash/transport.rb', line 32 def platform_url @platform_url end |
Instance Method Details
#build_request(uri, method:, headers:, body:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/leash/transport.rb', line 101 def build_request(uri, method:, headers:, body:) request = case method when :post then Net::HTTP::Post.new(uri.request_uri) when :get then Net::HTTP::Get.new(uri.request_uri) else raise ArgumentError, "Unsupported HTTP method: #{method}" end headers.each { |k, v| request[k] = v } request.body = body if body request end |
#call(provider, action, params = nil) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
POST to ‘/api/integrations/provider/action` and return the parsed response body (after unwrapping `data`).
50 51 52 53 54 |
# File 'lib/leash/transport.rb', line 50 def call(provider, action, params = nil) url = "#{@platform_url}/api/integrations/#{provider}/#{action}" docs_url = "https://leash.build/docs/integrations/#{provider}" post_json(url, params, docs_url: docs_url) end |
#get_json(url, headers: {}) ⇒ Object
57 58 59 |
# File 'lib/leash/transport.rb', line 57 def get_json(url, headers: {}) run_request(url, method: :get, headers: headers, body: nil) end |
#handle_response(response, docs_url:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/leash/transport.rb', line 116 def handle_response(response, docs_url:) status = response.respond_to?(:code) ? response.code.to_i : 0 body_raw = response.respond_to?(:body) ? response.body : nil parsed = parse_json(body_raw) if status >= 400 raise_for_status(status, parsed, docs_url: docs_url) end if parsed.is_a?(Hash) # Platform contract: { success, data } envelope OR raw shape. if parsed["success"] == false = parsed["error"].is_a?(String) ? parsed["error"] : "Integration error" err_code = parsed["code"].is_a?(String) ? parsed["code"] : "INTEGRATION_ERROR" raise Error.new(, code: err_code, action: "Check your integration configuration and try again.", see_also: docs_url, status: status, connect_url: parsed["connectUrl"]) end return parsed["data"] if parsed.key?("data") return parsed end parsed end |
#parse_json(body) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
147 148 149 150 151 152 153 |
# File 'lib/leash/transport.rb', line 147 def parse_json(body) return nil if body.nil? || body.empty? JSON.parse(body) rescue JSON::ParserError nil end |
#post_json(url, params, docs_url:) ⇒ Object
62 63 64 65 66 67 68 69 70 71 |
# File 'lib/leash/transport.rb', line 62 def post_json(url, params, docs_url:) headers = { "Content-Type" => "application/json" } headers["X-API-Key"] = @api_key if @api_key headers["Cookie"] = "leash-auth=#{@cookie_value}" if @cookie_value body = (params.nil? ? {} : params).to_json response = run_request(url, method: :post, headers: headers, body: body) handle_response(response, docs_url: docs_url) end |
#raise_for_status(status, body, docs_url:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/leash/transport.rb', line 156 def raise_for_status(status, body, docs_url:) = "HTTP #{status}" if body.is_a?(Hash) && body["error"].is_a?(String) = body["error"] end case status when 401 raise UnauthorizedError.new(, action: "Ensure the leash-auth cookie is present, or open your app from the Leash dashboard to get a valid session.", see_also: "https://leash.build/docs/sdk", status: status) when 402 msg = (body.is_a?(Hash) && body["message"].is_a?(String) ? body["message"] : ) raise UpgradeRequiredError.new(msg, action: "Upgrade your plan at https://leash.build/dashboard/billing.", see_also: "https://leash.build/pricing", status: status) when 403 raise ConnectionRequiredError.new(, action: "Connect the integration at /dashboard/integrations and make sure this app is on the allow-list.", see_also: "https://leash.build/dashboard/integrations", status: status) else raise Error.new(, code: "INTEGRATION_ERROR", action: "Check your integration configuration and try again — the upstream provider returned an error.", see_also: docs_url, status: status) end end |
#run_request(url, method:, headers:, body:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
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 |
# File 'lib/leash/transport.rb', line 74 def run_request(url, method:, headers:, body:) uri = URI.parse(url) request = build_request(uri, method: method, headers: headers, body: body) if @http_runner return @http_runner.call(uri, request) end use_ssl = uri.scheme == "https" opts = { use_ssl: use_ssl, open_timeout: @open_timeout, read_timeout: @read_timeout } Net::HTTP.start(uri.host, uri.port, **opts) do |http| http.request(request) end rescue Net::OpenTimeout, Net::ReadTimeout => e raise NetworkError.new("Timed out reaching the Leash platform: #{e.}", action: "Check your network connection and that the Leash platform is reachable.", see_also: "https://leash.build/docs/sdk", cause: e) rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, IOError => e raise NetworkError.new(e., action: "Check your network connection and that the Leash platform is reachable.", see_also: "https://leash.build/docs/sdk", cause: e) end |