clicksign-ruby-sdk 0.1.5 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/REVISION +1 -0
- data/lib/clicksign/client.rb +12 -4
- data/lib/clicksign/configuration.rb +1 -1
- data/lib/clicksign/error_handler.rb +3 -1
- data/lib/clicksign/json_api/atomic_results_parser.rb +3 -2
- data/lib/clicksign/json_api/bulk_operations_client.rb +5 -4
- data/lib/clicksign/json_api/parser.rb +4 -1
- data/lib/clicksign/json_api/serializer.rb +2 -2
- data/lib/clicksign/resource.rb +1 -1
- data/lib/clicksign/resources/folder.rb +3 -1
- data/lib/clicksign/resources/membership.rb +15 -0
- data/lib/clicksign/resources/notarial/bulk_requirement.rb +4 -0
- data/lib/clicksign/resources/notarial/document.rb +7 -1
- data/lib/clicksign/resources/notarial/event.rb +2 -0
- data/lib/clicksign/resources/notarial/requirement.rb +12 -1
- data/lib/clicksign/resources/notarial/signature_watcher.rb +7 -1
- data/lib/clicksign/resources/notarial/signer.rb +7 -1
- data/lib/clicksign/retry_backoff.rb +17 -0
- data/lib/clicksign/webhook.rb +9 -6
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a8730a44f32966cd0d6978150f4c5c5e821de5c168b94f4e69ced112cef211cc
|
|
4
|
+
data.tar.gz: afe6780086fe195e7559470d86674d83b8927c725e96213f1bb930a43808af54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c939a985f4caf49b47d15b2b6637550e20083cf217f077bf0d50c042946b693db885bb8ccad98abc1bfb48a7465d5ebc8ed9c47eef4aac8f32a078c9560fcb12
|
|
7
|
+
data.tar.gz: 80ee966ee546d6b401c7e427afa7bd7b881b64fb342e18e068469bb24f8b043220b1edc3d853bb3b348e1fac95c4e68284bf14a8a8ef1ac4325b25d176ce7f84
|
data/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Cliente Ruby oficial para a [API v3 da Clicksign](https://developers.clicksign.c
|
|
|
9
9
|
|
|
10
10
|
**Requisitos:** Ruby >= 3.0 · dependências de runtime: apenas biblioteca padrão (`net/http`, `json`).
|
|
11
11
|
|
|
12
|
-
**Documentação:** [índice `docs/`](docs/) · [Workflow](docs/WORKFLOW.md) · [Cookbook](docs/
|
|
12
|
+
**Documentação:** [índice `docs/`](docs/) · [Workflow](docs/WORKFLOW.md) · [Cookbook](docs/examples/) · [Troubleshooting](docs/TROUBLESHOOTING.md) · [Arquitetura](docs/ARCHITECTURE.md) · [Observabilidade](docs/OBSERVABILITY.md) · [SPEC](docs/SPEC.md) · API: [Sandbox](https://sandbox.clicksign.com/api/v3) · [Produção](https://app.clicksign.com/api/v3)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
@@ -31,7 +31,7 @@ Cliente Ruby oficial para a [API v3 da Clicksign](https://developers.clicksign.c
|
|
|
31
31
|
|
|
32
32
|
> **Exemplo passo a passo:** [`docs/WORKFLOW.md`](docs/WORKFLOW.md) — fluxo completo de envelope → documento → signatário → requisitos → ativação → notificação.
|
|
33
33
|
|
|
34
|
-
> **Cookbook (receitas por cenário):** [`docs/
|
|
34
|
+
> **Cookbook (receitas por cenário):** [`docs/examples/`](docs/examples/) — [retries](docs/examples/01-retries.md), [bulk requirements](docs/examples/02-bulk-requirements.md), [webhooks](docs/examples/03-webhooks.md), [vários clientes](docs/examples/04-multi-client.md), [list vs filter](docs/examples/07-list-and-filter.md), [limitações de produção](docs/examples/08-production-limitations.md).
|
|
35
35
|
|
|
36
36
|
> **Troubleshooting:** [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) — sintoma → causa → correção (erros HTTP, multi-tenant, bulk parcial, webhooks).
|
|
37
37
|
|
|
@@ -507,7 +507,7 @@ Event.create(
|
|
|
507
507
|
|
|
508
508
|
`list` **não** aceita argumentos. Para filtrar: `Envelope.filter(status: 'draft').to_a` (não `Envelope.list(status: 'draft')`).
|
|
509
509
|
|
|
510
|
-
Guia completo: [`docs/
|
|
510
|
+
Guia completo: [`docs/examples/07-list-and-filter.md`](docs/examples/07-list-and-filter.md).
|
|
511
511
|
|
|
512
512
|
```ruby
|
|
513
513
|
# Sem filtros — retorna Array imediatamente
|
|
@@ -720,7 +720,7 @@ Cada request abre e fecha uma conexão TCP (via `Net::HTTP.start`). Não há reu
|
|
|
720
720
|
- **OK** para jobs sequenciais, integrações moderadas e a maioria dos apps Rails.
|
|
721
721
|
- **Atenção** em Puma com muitas threads e várias chamadas Clicksign por request: overhead de handshake/TLS pode virar gargalo antes do rate limit da API.
|
|
722
722
|
|
|
723
|
-
Mitigações: menos round-trips (`BulkRequirement`, batch na app), filas (Sidekiq), cache de leitura. Detalhes: [`docs/
|
|
723
|
+
Mitigações: menos round-trips (`BulkRequirement`, batch na app), filas (Sidekiq), cache de leitura. Detalhes: [`docs/examples/08-production-limitations.md`](docs/examples/08-production-limitations.md).
|
|
724
724
|
|
|
725
725
|
### `Thread.current` e Fibers
|
|
726
726
|
|
|
@@ -765,7 +765,7 @@ lib/clicksign/
|
|
|
765
765
|
docs/SPEC.md # mapa completo de resources e rotas
|
|
766
766
|
docs/WORKFLOW.md # fluxo notarial ponta a ponta
|
|
767
767
|
docs/README.md # índice da documentação
|
|
768
|
-
docs/
|
|
768
|
+
docs/examples/ # receitas: retries, bulk, webhooks, multi-cliente
|
|
769
769
|
docs/TROUBLESHOOTING.md # diagnóstico e erros comuns
|
|
770
770
|
docs/ARCHITECTURE.md # diagramas e camadas
|
|
771
771
|
docs/OBSERVABILITY.md # logs, métricas, OpenTelemetry
|
data/REVISION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.8
|
data/lib/clicksign/client.rb
CHANGED
|
@@ -14,7 +14,7 @@ module Clicksign
|
|
|
14
14
|
}.freeze
|
|
15
15
|
|
|
16
16
|
def initialize(api_key:, base_url:, open_timeout: 2, read_timeout: 10,
|
|
17
|
-
write_timeout: 10, max_retries:
|
|
17
|
+
write_timeout: 10, max_retries: 3)
|
|
18
18
|
@api_key = api_key
|
|
19
19
|
@base_url = base_url
|
|
20
20
|
@open_timeout = open_timeout
|
|
@@ -42,6 +42,13 @@ module Clicksign
|
|
|
42
42
|
execute_with_retry(request, uri)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
def put(path, body:)
|
|
46
|
+
uri = build_uri(path)
|
|
47
|
+
request = Net::HTTP::Put.new(uri, headers)
|
|
48
|
+
request.body = body.to_json
|
|
49
|
+
execute_with_retry(request, uri)
|
|
50
|
+
end
|
|
51
|
+
|
|
45
52
|
def delete(path, body: nil)
|
|
46
53
|
uri = build_uri(path)
|
|
47
54
|
request = Net::HTTP::Delete.new(uri, headers)
|
|
@@ -70,7 +77,7 @@ module Clicksign
|
|
|
70
77
|
Clicksign::ServerError => e
|
|
71
78
|
raise unless e.retryable? && attempts <= @max_retries
|
|
72
79
|
|
|
73
|
-
delay = RetryBackoff.
|
|
80
|
+
delay = RetryBackoff.retry_delay(attempts, e.response_headers)
|
|
74
81
|
publish_retry(request, uri, attempts, e, delay)
|
|
75
82
|
sleep(delay)
|
|
76
83
|
retry
|
|
@@ -82,8 +89,9 @@ module Clicksign
|
|
|
82
89
|
context = request_context(request, uri, attempt)
|
|
83
90
|
response = http_request(request, uri)
|
|
84
91
|
handle_response(response, context, start)
|
|
85
|
-
rescue Net::OpenTimeout, Net::ReadTimeout,
|
|
86
|
-
|
|
92
|
+
rescue Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout,
|
|
93
|
+
Errno::ECONNREFUSED => e
|
|
94
|
+
handle_network_error(e, context, start)
|
|
87
95
|
end
|
|
88
96
|
|
|
89
97
|
def http_request(request, uri)
|
|
@@ -50,7 +50,9 @@ module Clicksign
|
|
|
50
50
|
errors = body['errors']
|
|
51
51
|
return response.message unless errors.is_a?(Array)
|
|
52
52
|
|
|
53
|
-
result = errors.filter_map
|
|
53
|
+
result = errors.filter_map do |e|
|
|
54
|
+
e.is_a?(Hash) && (e['detail'] || e['title'])
|
|
55
|
+
end.join(', ')
|
|
54
56
|
result.empty? ? response.message : result
|
|
55
57
|
end
|
|
56
58
|
end
|
|
@@ -24,6 +24,7 @@ module Clicksign
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def build_operation_result(slot:, index:, op:, envelope_id:)
|
|
27
|
+
slot ||= {}
|
|
27
28
|
errors = slot['errors']
|
|
28
29
|
requirement = build_requirement(slot['data'], envelope_id: envelope_id)
|
|
29
30
|
|
|
@@ -37,7 +38,7 @@ module Clicksign
|
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def build_requirement(data, envelope_id:)
|
|
40
|
-
return nil if data.nil? || data
|
|
41
|
+
return nil if data.nil? || (!data['id'] && !data['type'])
|
|
41
42
|
|
|
42
43
|
Resources::Notarial::Requirement.send(
|
|
43
44
|
:build_instance,
|
|
@@ -54,7 +55,7 @@ module Clicksign
|
|
|
54
55
|
def format_errors(errors)
|
|
55
56
|
return 'Validation failed' unless errors.is_a?(Array)
|
|
56
57
|
|
|
57
|
-
errors.filter_map { |e| e['detail'] || e['title'] }.join(', ')
|
|
58
|
+
errors.filter_map { |e| e.is_a?(Hash) && (e['detail'] || e['title']) }.join(', ')
|
|
58
59
|
end
|
|
59
60
|
end
|
|
60
61
|
end
|
|
@@ -43,7 +43,8 @@ module Clicksign
|
|
|
43
43
|
begin
|
|
44
44
|
attempts += 1
|
|
45
45
|
execute_once(request, uri, attempt: attempts)
|
|
46
|
-
rescue Clicksign::TimeoutError
|
|
46
|
+
rescue Clicksign::TimeoutError, Clicksign::RateLimitError,
|
|
47
|
+
Clicksign::ServerError => e
|
|
47
48
|
raise unless e.retryable? && attempts <= @max_retries
|
|
48
49
|
|
|
49
50
|
delay = RetryBackoff.delay(attempts)
|
|
@@ -64,8 +65,8 @@ module Clicksign
|
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
def handle_bulk_body(response, context, status, duration)
|
|
67
|
-
parsed = parse_response_body(response)
|
|
68
|
-
return parsed if parsed
|
|
68
|
+
parsed = parse_response_body(response)
|
|
69
|
+
return parsed if parsed&.key?('atomic:results')
|
|
69
70
|
|
|
70
71
|
begin
|
|
71
72
|
ErrorHandler.call(response)
|
|
@@ -73,7 +74,7 @@ module Clicksign
|
|
|
73
74
|
publish_http_error(context, e, status, duration)
|
|
74
75
|
raise
|
|
75
76
|
end
|
|
76
|
-
parsed
|
|
77
|
+
parsed || {}
|
|
77
78
|
end
|
|
78
79
|
|
|
79
80
|
def http_post(request, uri)
|
|
@@ -4,11 +4,14 @@ module Clicksign
|
|
|
4
4
|
module JsonApi
|
|
5
5
|
module Parser
|
|
6
6
|
def self.parse(raw)
|
|
7
|
+
return { data: [], included: [], links: nil } if raw.nil?
|
|
8
|
+
|
|
7
9
|
raw_data = raw['data']
|
|
8
10
|
data = case raw_data
|
|
9
11
|
when Array then raw_data.map { |item| build(item) }
|
|
10
12
|
when Hash then [build(raw_data)]
|
|
11
|
-
|
|
13
|
+
when nil then []
|
|
14
|
+
else raise Error, "Unexpected JSON:API data type: #{raw_data.class}"
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
included = Array(raw['included'])
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module Clicksign
|
|
4
4
|
module JsonApi
|
|
5
5
|
module Serializer
|
|
6
|
-
def self.dump(type:, attributes
|
|
7
|
-
data = { type: type, attributes: attributes }
|
|
6
|
+
def self.dump(type:, attributes: {}, id: nil, relationships: {})
|
|
7
|
+
data = { type: type, attributes: attributes || {} }
|
|
8
8
|
data[:id] = id if id
|
|
9
9
|
data[:relationships] = relationships unless relationships.empty?
|
|
10
10
|
{ data: data }
|
data/lib/clicksign/resource.rb
CHANGED
|
@@ -13,6 +13,21 @@ module Clicksign
|
|
|
13
13
|
)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
def update(**attributes)
|
|
17
|
+
raw = self.class.client.put(
|
|
18
|
+
"#{base_path}/#{@id}",
|
|
19
|
+
body: JsonApi::Serializer.dump(
|
|
20
|
+
type: self.class.resource_type, id: @id, attributes: attributes,
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
parsed = JsonApi::Parser.parse(raw)
|
|
24
|
+
data = parsed[:data].first
|
|
25
|
+
raise NotFoundError, 'API returned null data' if data.nil?
|
|
26
|
+
|
|
27
|
+
load_data(data, parent_id: @_parent_id)
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
16
31
|
def user_id
|
|
17
32
|
relationships.dig('user', 'data', 'id')
|
|
18
33
|
end
|
|
@@ -42,6 +42,10 @@ module Clicksign
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def self.create(envelope_id:, &block)
|
|
45
|
+
if envelope_id.nil? || envelope_id.to_s.empty?
|
|
46
|
+
raise ArgumentError,
|
|
47
|
+
'envelope_id is required'
|
|
48
|
+
end
|
|
45
49
|
raise ArgumentError, 'block is required' unless block
|
|
46
50
|
|
|
47
51
|
ops = JsonApi::Operations::BulkRequirement.new
|
|
@@ -28,7 +28,13 @@ module Clicksign
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def base_path
|
|
31
|
-
|
|
31
|
+
eid = @_parent_id || envelope_id
|
|
32
|
+
if eid.nil?
|
|
33
|
+
raise Clicksign::Error,
|
|
34
|
+
'envelope_id is required for Document operations'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
"/envelopes/#{eid}/documents"
|
|
32
38
|
end
|
|
33
39
|
|
|
34
40
|
def envelope_id
|
|
@@ -6,6 +6,8 @@ module Clicksign
|
|
|
6
6
|
class Event < Clicksign::Resource
|
|
7
7
|
self.resource_type = 'events'
|
|
8
8
|
|
|
9
|
+
# Known custom event kinds. Override or append to extend without SDK update:
|
|
10
|
+
# Clicksign::Resources::Notarial::Event::CUSTOM_KINDS << 'new_kind'
|
|
9
11
|
CUSTOM_KINDS = %w[token_email token_sms].freeze
|
|
10
12
|
|
|
11
13
|
def self.create(envelope_id:, document_id:, **attributes)
|
|
@@ -42,8 +42,19 @@ module Clicksign
|
|
|
42
42
|
end
|
|
43
43
|
private_class_method :list_related
|
|
44
44
|
|
|
45
|
+
def update(**)
|
|
46
|
+
raise NotImplementedError,
|
|
47
|
+
'Requirement does not support update (API does not provide this endpoint)'
|
|
48
|
+
end
|
|
49
|
+
|
|
45
50
|
def base_path
|
|
46
|
-
|
|
51
|
+
eid = @_parent_id || envelope_id
|
|
52
|
+
if eid.nil?
|
|
53
|
+
raise Clicksign::Error,
|
|
54
|
+
'envelope_id is required for Requirement operations'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
"/envelopes/#{eid}/requirements"
|
|
47
58
|
end
|
|
48
59
|
|
|
49
60
|
def envelope_id
|
|
@@ -22,7 +22,13 @@ module Clicksign
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def base_path
|
|
25
|
-
|
|
25
|
+
eid = @_parent_id || envelope_id
|
|
26
|
+
if eid.nil?
|
|
27
|
+
raise Clicksign::Error,
|
|
28
|
+
'envelope_id is required for SignatureWatcher operations'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
"/envelopes/#{eid}/signature_watchers"
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def update(**)
|
|
@@ -44,7 +44,13 @@ module Clicksign
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def base_path
|
|
47
|
-
|
|
47
|
+
eid = @_parent_id || envelope_id
|
|
48
|
+
if eid.nil?
|
|
49
|
+
raise Clicksign::Error,
|
|
50
|
+
'envelope_id is required for Signer operations'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
"/envelopes/#{eid}/signers"
|
|
48
54
|
end
|
|
49
55
|
|
|
50
56
|
def envelope_id
|
|
@@ -18,5 +18,22 @@ module Clicksign
|
|
|
18
18
|
|
|
19
19
|
rng.rand(max)
|
|
20
20
|
end
|
|
21
|
+
|
|
22
|
+
def parse_retry_after(headers)
|
|
23
|
+
return nil unless headers.is_a?(Hash)
|
|
24
|
+
|
|
25
|
+
raw = headers['retry-after'] || headers['Retry-After']
|
|
26
|
+
return nil if raw.nil? || raw.to_s.strip.empty?
|
|
27
|
+
|
|
28
|
+
Float(raw.to_s.strip)
|
|
29
|
+
rescue ArgumentError, TypeError
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def retry_delay(attempt, headers = nil, rng: Random)
|
|
34
|
+
jitter = delay(attempt, rng: rng)
|
|
35
|
+
retry_after = parse_retry_after(headers)
|
|
36
|
+
retry_after ? [jitter, retry_after].max : jitter
|
|
37
|
+
end
|
|
21
38
|
end
|
|
22
39
|
end
|
data/lib/clicksign/webhook.rb
CHANGED
|
@@ -25,18 +25,21 @@ module Clicksign
|
|
|
25
25
|
|
|
26
26
|
# Computes the expected Content-HMAC value for a given payload and secret.
|
|
27
27
|
def self.compute_signature(payload, secret:)
|
|
28
|
+
raise ArgumentError, 'secret must not be empty' if secret.nil? || secret.empty?
|
|
29
|
+
|
|
28
30
|
"#{DIGEST}=#{OpenSSL::HMAC.hexdigest(DIGEST, secret, payload)}"
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
# Constant-time comparison to prevent timing attacks.
|
|
32
34
|
def self.secure_compare?(expected, actual)
|
|
33
|
-
return false if actual.nil?
|
|
35
|
+
return false if actual.nil?
|
|
36
|
+
|
|
37
|
+
actual_str = actual.is_a?(String) ? actual : actual.to_s
|
|
38
|
+
return false if actual_str.empty?
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
digest_a.bytes.zip(digest_b.bytes) { |x, y| result |= x ^ y }
|
|
39
|
-
result.zero?
|
|
40
|
+
OpenSSL.fixed_length_secure_compare(expected, actual_str)
|
|
41
|
+
rescue ArgumentError
|
|
42
|
+
false
|
|
40
43
|
end
|
|
41
44
|
private_class_method :secure_compare?
|
|
42
45
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: clicksign-ruby-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Clicksign
|
|
@@ -17,6 +17,7 @@ extensions: []
|
|
|
17
17
|
extra_rdoc_files: []
|
|
18
18
|
files:
|
|
19
19
|
- README.md
|
|
20
|
+
- REVISION
|
|
20
21
|
- lib/clicksign.rb
|
|
21
22
|
- lib/clicksign/client.rb
|
|
22
23
|
- lib/clicksign/configuration.rb
|