cloudflare-ruby 0.2.0 → 0.3.0
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/CHANGELOG.md +17 -0
- data/lib/cloudflare/connection.rb +9 -3
- data/lib/cloudflare/realtime_kit/README.md +8 -6
- data/lib/cloudflare/realtime_kit/active_session.rb +5 -5
- data/lib/cloudflare/realtime_kit/analytics.rb +2 -1
- data/lib/cloudflare/realtime_kit/app.rb +2 -2
- data/lib/cloudflare/realtime_kit/meeting.rb +5 -5
- data/lib/cloudflare/realtime_kit/participant.rb +1 -1
- data/lib/cloudflare/realtime_kit/recording.rb +3 -3
- data/lib/cloudflare/realtime_kit/session.rb +2 -2
- data/lib/cloudflare/realtime_kit/summary.rb +1 -1
- data/lib/cloudflare/realtime_kit/webhook.rb +1 -1
- data/lib/cloudflare/realtime_kit.rb +16 -4
- data/lib/cloudflare/resource.rb +31 -6
- data/lib/cloudflare/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 230de80c474a3f8441f60687160aba0f2cd53c5db2d9465498866994150cc60f
|
|
4
|
+
data.tar.gz: 470768dc30305293361258880a74be07ea42832449ec4a77cae0079d42ca3cbc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3416c0f5b271e376182525b183324d5ec7e0c11e5437f13194a75f35265c8463ac6d843414386e4fb1954966c340515f6eee2540e10b67bec1d6b50eae98574b
|
|
7
|
+
data.tar.gz: 7c2d7a9a594d06f674af34d26cf9a9570da463ac81e817aa56738d2a0332760c0116550c3adf68b1616cd019f33b2e9ccd24dbcf8ab7e414ef3e222d57e5e68d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- **Per-product API tokens.** Set `Cloudflare::RealtimeKit.api_token` to
|
|
6
|
+
use a token scoped tightly to RealtimeKit (e.g., `Realtime: Edit` only).
|
|
7
|
+
Resolution chain when a request fires: per-call `api_token:` kwarg →
|
|
8
|
+
`Cloudflare::RealtimeKit.api_token` (per-product) → `Cloudflare.api_token`
|
|
9
|
+
(top-level fallback) → raise. Lets each product surface have its own
|
|
10
|
+
credentials so a leaked Realtime token can't touch R2/Workers/DNS.
|
|
11
|
+
- `Connection#request` accepts a new `api_token:` kwarg; resolved token
|
|
12
|
+
flows through one place.
|
|
13
|
+
- `Resource` gains a private `request(method, path, **opts)` helper
|
|
14
|
+
(class + instance) that wraps `Connection.instance.request` and injects
|
|
15
|
+
`configured_api_token` from the resource's product namespace. Subclasses
|
|
16
|
+
call `request(...)` instead of `Connection.instance.request(...)`.
|
|
17
|
+
- Top-level `Cloudflare.api_token` still works as the fallback for
|
|
18
|
+
callers who don't want per-product separation. Backward-compatible.
|
|
19
|
+
|
|
3
20
|
## 0.2.0
|
|
4
21
|
|
|
5
22
|
- **Default `app_id` per process.** Set `Cloudflare::RealtimeKit.app_id`
|
|
@@ -8,11 +8,17 @@ module Cloudflare
|
|
|
8
8
|
def reset! = @instance = nil
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
# Sends a Cloudflare API request. The +api_token+ kwarg is the resolved
|
|
12
|
+
# token chosen by the caller (typically +Resource.configured_api_token+ —
|
|
13
|
+
# per-product token if set, top-level +Cloudflare.api_token+ otherwise).
|
|
14
|
+
# It's a kwarg rather than a hardcoded read so admin tools can override
|
|
15
|
+
# per-call.
|
|
16
|
+
def request(method, path, body: nil, params: nil, api_token: nil)
|
|
17
|
+
token = api_token || Cloudflare.api_token
|
|
18
|
+
raise Error, "Cloudflare API token not configured" if token.nil?
|
|
13
19
|
|
|
14
20
|
faraday.public_send(method, path.delete_prefix("/")) do |req|
|
|
15
|
-
req.headers["Authorization"] = "Bearer #{
|
|
21
|
+
req.headers["Authorization"] = "Bearer #{token}"
|
|
16
22
|
req.headers["User-Agent"] = Cloudflare.configuration.user_agent
|
|
17
23
|
req.params = compact(params) if params
|
|
18
24
|
req.body = compact(body) if body
|
|
@@ -8,19 +8,21 @@ Ruby bindings for [Cloudflare RealtimeKit](https://developers.cloudflare.com/rea
|
|
|
8
8
|
require "cloudflare"
|
|
9
9
|
|
|
10
10
|
Cloudflare.configure do |c|
|
|
11
|
-
c.api_token = ENV.fetch("CLOUDFLARE_API_TOKEN")
|
|
12
11
|
c.account_id = ENV.fetch("CLOUDFLARE_ACCOUNT_ID")
|
|
13
12
|
end
|
|
14
13
|
|
|
15
|
-
#
|
|
16
|
-
# Pass `app_id:` per-call only to override
|
|
17
|
-
# between apps).
|
|
18
|
-
Cloudflare::RealtimeKit.
|
|
14
|
+
# Per-product API token + default app_id for every RealtimeKit call this
|
|
15
|
+
# process makes. Pass `api_token:` / `app_id:` per-call only to override
|
|
16
|
+
# (e.g., admin tools that hop between accounts or apps).
|
|
17
|
+
Cloudflare::RealtimeKit.api_token = ENV.fetch("REALTIMEKIT_API_TOKEN")
|
|
18
|
+
Cloudflare::RealtimeKit.app_id = ENV.fetch("REALTIMEKIT_APP_ID")
|
|
19
19
|
|
|
20
20
|
RK = Cloudflare::RealtimeKit # alias to taste
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
After that,
|
|
23
|
+
After that, `account_id:`, `api_token:`, and `app_id:` are all implicit on every call — pass any per-call to override.
|
|
24
|
+
|
|
25
|
+
If you'd rather use a single token across every Cloudflare product (less scope-tight but simpler), set `Cloudflare.api_token` instead and skip the per-product line — every product surface falls back to it.
|
|
24
26
|
|
|
25
27
|
## Apps
|
|
26
28
|
|
|
@@ -36,31 +36,31 @@ module Cloudflare
|
|
|
36
36
|
|
|
37
37
|
# POST .../active-session/kick — remove specified participants.
|
|
38
38
|
def kick(participant_ids:, custom_participant_ids:)
|
|
39
|
-
|
|
39
|
+
request(:post, "#{member_path}/kick",
|
|
40
40
|
body: { participant_ids: participant_ids, custom_participant_ids: custom_participant_ids })
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# POST .../active-session/kick-all — remove every participant.
|
|
44
44
|
def kick_all
|
|
45
|
-
|
|
45
|
+
request(:post, "#{member_path}/kick-all")
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
# POST .../active-session/mute — mute specified participants.
|
|
49
49
|
def mute(participant_ids:, custom_participant_ids:)
|
|
50
|
-
|
|
50
|
+
request(:post, "#{member_path}/mute",
|
|
51
51
|
body: { participant_ids: participant_ids, custom_participant_ids: custom_participant_ids })
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# POST .../active-session/mute-all — mute every participant.
|
|
55
55
|
# +allow_unmute+ controls whether participants can re-enable their mic.
|
|
56
56
|
def mute_all(allow_unmute:)
|
|
57
|
-
|
|
57
|
+
request(:post, "#{member_path}/mute-all",
|
|
58
58
|
body: { allow_unmute: allow_unmute })
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# POST .../active-session/poll — broadcast a poll to participants.
|
|
62
62
|
def create_poll(question:, options:, anonymous: nil, hide_votes: nil)
|
|
63
|
-
|
|
63
|
+
request(:post, "#{member_path}/poll",
|
|
64
64
|
body: { question: question, options: options, anonymous: anonymous, hide_votes: hide_votes })
|
|
65
65
|
end
|
|
66
66
|
end
|
|
@@ -35,7 +35,8 @@ module Cloudflare
|
|
|
35
35
|
def fetch(path_template, app_id:, account_id:, params:)
|
|
36
36
|
scope = build_scope(account_id: account_id, app_id: app_id)
|
|
37
37
|
path = interpolate(path_template, scope)
|
|
38
|
-
response = Connection.instance.request(:get, path,
|
|
38
|
+
response = Connection.instance.request(:get, path,
|
|
39
|
+
params: params, api_token: RealtimeKit.api_token || Cloudflare.api_token)
|
|
39
40
|
Resource.unwrap_envelope(response)
|
|
40
41
|
end
|
|
41
42
|
|
|
@@ -34,14 +34,14 @@ module Cloudflare
|
|
|
34
34
|
# +{ data: [...] }+ envelope and don't need this dance.
|
|
35
35
|
def create(name:, account_id: nil)
|
|
36
36
|
scope = build_scope(account_id: account_id)
|
|
37
|
-
response =
|
|
37
|
+
response = request(:post, interpolate(_collection_path, scope), body: { "name" => name })
|
|
38
38
|
new(unwrap_create_payload(response), scope: scope)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
# GET /accounts/{account_id}/realtime/kit/apps
|
|
42
42
|
def all(account_id: nil)
|
|
43
43
|
scope = build_scope(account_id: account_id)
|
|
44
|
-
response =
|
|
44
|
+
response = request(:get, interpolate(_collection_path, scope))
|
|
45
45
|
Array(unwrap_envelope(response)).map { new(_1, scope: scope) }
|
|
46
46
|
end
|
|
47
47
|
|
|
@@ -43,14 +43,14 @@ module Cloudflare
|
|
|
43
43
|
# PUT /meetings/{id} — full replacement. Mirrors +update+ but uses PUT
|
|
44
44
|
# semantics, so omitted fields revert to their defaults upstream.
|
|
45
45
|
def replace(**attrs)
|
|
46
|
-
response =
|
|
46
|
+
response = request(:put, member_path, body: self.class.to_wire_keys(attrs))
|
|
47
47
|
set_attrs_from_response(response)
|
|
48
48
|
self
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# POST /meetings/{id}/livestreams — begin broadcasting this meeting.
|
|
52
52
|
def start_livestreaming(name: nil, video_config: nil)
|
|
53
|
-
|
|
53
|
+
request(:post, "#{member_path}/livestreams", body: { name: name, video_config: video_config })
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# GET /meetings/{id}/active-livestream — fetch the currently-running
|
|
@@ -58,13 +58,13 @@ module Cloudflare
|
|
|
58
58
|
# the same app, so you can chain regular livestream operations on it
|
|
59
59
|
# (e.g., +meeting.active_livestream.active_session+).
|
|
60
60
|
def active_livestream
|
|
61
|
-
response =
|
|
61
|
+
response = request(:get, "#{member_path}/active-livestream")
|
|
62
62
|
Livestream.new(response, scope: { account_id: @scope[:account_id], app_id: @scope[:app_id] })
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
# POST /meetings/{id}/active-livestream/stop
|
|
66
66
|
def stop_livestream
|
|
67
|
-
|
|
67
|
+
request(:post, "#{member_path}/active-livestream/stop")
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
# GET /meetings/{id}/livestream — legacy "livestream-session-details"
|
|
@@ -73,7 +73,7 @@ module Cloudflare
|
|
|
73
73
|
# Returned as a raw Hash because the response bundles two distinct
|
|
74
74
|
# shapes; the typed +Livestream+ + per-session lookup live elsewhere.
|
|
75
75
|
def livestream_details(page_no: nil, per_page: nil)
|
|
76
|
-
response =
|
|
76
|
+
response = request(:get, "#{member_path}/livestream",
|
|
77
77
|
params: { page_no: page_no, per_page: per_page })
|
|
78
78
|
self.class.unwrap_envelope(response)
|
|
79
79
|
end
|
|
@@ -31,7 +31,7 @@ module Cloudflare
|
|
|
31
31
|
# Mints a fresh token for an existing participant (e.g., when their
|
|
32
32
|
# previous token expired or was leaked).
|
|
33
33
|
def regenerate_token
|
|
34
|
-
response =
|
|
34
|
+
response = request(:post, "#{member_path}/token")
|
|
35
35
|
set_attrs_from_response(response)
|
|
36
36
|
self
|
|
37
37
|
end
|
|
@@ -41,7 +41,7 @@ module Cloudflare
|
|
|
41
41
|
def start_track(meeting_id:, layers:, app_id: nil, account_id: nil, max_seconds: nil)
|
|
42
42
|
scope = build_scope(account_id: account_id, app_id: app_id)
|
|
43
43
|
path = interpolate("/accounts/{account_id}/realtime/kit/{app_id}/recordings/track", scope)
|
|
44
|
-
response =
|
|
44
|
+
response = request(:post, path,
|
|
45
45
|
body: { meeting_id: meeting_id, layers: layers, max_seconds: max_seconds })
|
|
46
46
|
new(response, scope: scope)
|
|
47
47
|
end
|
|
@@ -52,7 +52,7 @@ module Cloudflare
|
|
|
52
52
|
scope = build_scope(account_id: account_id, app_id: app_id)
|
|
53
53
|
path = interpolate("/accounts/{account_id}/realtime/kit/{app_id}/recordings/active-recording/{meeting_id}",
|
|
54
54
|
scope.merge(meeting_id: meeting_id))
|
|
55
|
-
response =
|
|
55
|
+
response = request(:get, path)
|
|
56
56
|
new(response, scope: scope)
|
|
57
57
|
end
|
|
58
58
|
end
|
|
@@ -69,7 +69,7 @@ module Cloudflare
|
|
|
69
69
|
unless TRANSITIONS.include?(sym)
|
|
70
70
|
raise ArgumentError, "action must be one of #{TRANSITIONS.inspect}, got #{action.inspect}"
|
|
71
71
|
end
|
|
72
|
-
response =
|
|
72
|
+
response = request(:put, member_path, body: { action: sym.to_s })
|
|
73
73
|
set_attrs_from_response(response)
|
|
74
74
|
self
|
|
75
75
|
end
|
|
@@ -45,7 +45,7 @@ module Cloudflare
|
|
|
45
45
|
scope = build_scope(account_id: account_id, app_id: app_id)
|
|
46
46
|
path = interpolate("/accounts/{account_id}/realtime/kit/{app_id}/sessions/peer-report/{peer_id}",
|
|
47
47
|
scope.merge(peer_id: peer_id))
|
|
48
|
-
response =
|
|
48
|
+
response = request(:get, path, params: { filters: filters })
|
|
49
49
|
SessionParticipant.new(response, scope: scope)
|
|
50
50
|
end
|
|
51
51
|
end
|
|
@@ -58,7 +58,7 @@ module Cloudflare
|
|
|
58
58
|
# so the two endpoints don't share scope and Relation can't model them
|
|
59
59
|
# together cleanly.
|
|
60
60
|
def livestream_sessions(page_no: nil, per_page: nil)
|
|
61
|
-
response =
|
|
61
|
+
response = request(:get, "#{member_path}/livestream-sessions",
|
|
62
62
|
params: { page_no: page_no, per_page: per_page })
|
|
63
63
|
Array(self.class.unwrap_envelope(response)).map do |item|
|
|
64
64
|
LivestreamSession.new(item, scope: { account_id: @scope[:account_id], app_id: @scope[:app_id] })
|
|
@@ -19,7 +19,7 @@ module Cloudflare
|
|
|
19
19
|
|
|
20
20
|
# POST /sessions/{session_id}/summary — generate a summary on demand.
|
|
21
21
|
def generate
|
|
22
|
-
response =
|
|
22
|
+
response = request(:post, member_path)
|
|
23
23
|
set_attrs_from_response(response)
|
|
24
24
|
self
|
|
25
25
|
end
|
|
@@ -27,7 +27,7 @@ module Cloudflare
|
|
|
27
27
|
|
|
28
28
|
# PUT /webhooks/{id} — replace every field (vs PATCH +update+).
|
|
29
29
|
def replace(**attrs)
|
|
30
|
-
response =
|
|
30
|
+
response = request(:put, member_path, body: self.class.to_wire_keys(attrs))
|
|
31
31
|
set_attrs_from_response(response)
|
|
32
32
|
self
|
|
33
33
|
end
|
|
@@ -3,21 +3,33 @@ module Cloudflare
|
|
|
3
3
|
# for meetings, livestreams, recordings, sessions, and analytics. Each
|
|
4
4
|
# resource maps to a hand-written class under this namespace.
|
|
5
5
|
#
|
|
6
|
-
# ==
|
|
6
|
+
# == Defaults
|
|
7
7
|
#
|
|
8
8
|
# Most resources (Meeting, Participant, Recording, ...) live under an app.
|
|
9
9
|
# Set +app_id+ once at process boot and every call defaults to it; pass
|
|
10
10
|
# +app_id:+ per-call only to override.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
12
|
+
# +api_token+ is the per-product token. Resolution chain when a request
|
|
13
|
+
# fires: per-call +api_token:+ kwarg → +Cloudflare::RealtimeKit.api_token+
|
|
14
|
+
# → +Cloudflare.api_token+ (top-level fallback) → raise.
|
|
13
15
|
#
|
|
14
|
-
#
|
|
16
|
+
# Per-product tokens let you scope each product's credentials tightly
|
|
17
|
+
# (e.g., a token with +Realtime: Edit+ only) and rotate them
|
|
18
|
+
# independently. Top-level +Cloudflare.api_token+ stays as a fallback
|
|
19
|
+
# for callers who don't want per-product separation.
|
|
20
|
+
#
|
|
21
|
+
# Cloudflare::RealtimeKit.api_token = ENV["REALTIMEKIT_API_TOKEN"]
|
|
22
|
+
# Cloudflare::RealtimeKit.app_id = ENV["REALTIMEKIT_APP_ID"]
|
|
23
|
+
#
|
|
24
|
+
# Cloudflare::RealtimeKit::Meeting.create(title: "Standup") # uses defaults
|
|
15
25
|
# Cloudflare::RealtimeKit::Meeting.find(id, app_id: "other-app-id") # override
|
|
16
26
|
module RealtimeKit
|
|
17
27
|
class << self
|
|
18
28
|
# Default app_id used when a resource call doesn't pass one explicitly.
|
|
19
|
-
# Mirrors how +Cloudflare.account_id+ defaults the account scope.
|
|
20
29
|
attr_accessor :app_id
|
|
30
|
+
|
|
31
|
+
# Per-product API token. Falls back to +Cloudflare.api_token+ when nil.
|
|
32
|
+
attr_accessor :api_token
|
|
21
33
|
end
|
|
22
34
|
|
|
23
35
|
autoload :App, "cloudflare/realtime_kit/app"
|
data/lib/cloudflare/resource.rb
CHANGED
|
@@ -160,25 +160,42 @@ module Cloudflare
|
|
|
160
160
|
def create(**attrs)
|
|
161
161
|
scope = extract_scope!(attrs)
|
|
162
162
|
path = interpolate(_collection_path, scope)
|
|
163
|
-
response =
|
|
163
|
+
response = request(:post, path, body: to_wire_keys(attrs))
|
|
164
164
|
new(response, scope: scope)
|
|
165
165
|
end
|
|
166
166
|
|
|
167
167
|
def find(id, **scope_attrs)
|
|
168
168
|
scope = build_scope(scope_attrs)
|
|
169
169
|
path = interpolate(_member_path, scope.merge(id: id))
|
|
170
|
-
response =
|
|
170
|
+
response = request(:get, path)
|
|
171
171
|
new(response, scope: scope)
|
|
172
172
|
end
|
|
173
173
|
|
|
174
174
|
def all(**params)
|
|
175
175
|
scope = extract_scope!(params)
|
|
176
176
|
path = interpolate(_collection_path, scope)
|
|
177
|
-
response =
|
|
177
|
+
response = request(:get, path, params: to_wire_keys(params))
|
|
178
178
|
items = unwrap_envelope(response)
|
|
179
179
|
Array(items).map { new(_1, scope: scope) }
|
|
180
180
|
end
|
|
181
181
|
|
|
182
|
+
# Wraps +Connection.instance.request+ with the api_token resolved from
|
|
183
|
+
# the resource's product namespace. Subclasses (and instance methods,
|
|
184
|
+
# via the same-named instance helper) call this rather than
|
|
185
|
+
# +Connection.instance.request+ directly so the per-product token chain
|
|
186
|
+
# — per-call → product namespace token → top-level — is enforced
|
|
187
|
+
# uniformly.
|
|
188
|
+
def request(method, path, **opts)
|
|
189
|
+
Connection.instance.request(method, path, api_token: configured_api_token, **opts)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Token resolution: per-product accessor (e.g., +Cloudflare::RealtimeKit.api_token+)
|
|
193
|
+
# if set, else +Cloudflare.api_token+. Connection raises when both are nil.
|
|
194
|
+
def configured_api_token
|
|
195
|
+
ns = product_namespace
|
|
196
|
+
(ns && ns.respond_to?(:api_token) && ns.api_token) || Cloudflare.api_token
|
|
197
|
+
end
|
|
198
|
+
|
|
182
199
|
private
|
|
183
200
|
# Pulls scope params out of the kwargs hash (mutating). Falls back
|
|
184
201
|
# to product-level defaults (see +global_default_for+) when a key
|
|
@@ -250,18 +267,18 @@ module Cloudflare
|
|
|
250
267
|
def attributes = (ensure_loaded!; @attrs)
|
|
251
268
|
|
|
252
269
|
def update(**changes)
|
|
253
|
-
response =
|
|
270
|
+
response = request(:patch, member_path, body: self.class.to_wire_keys(changes))
|
|
254
271
|
set_attrs_from_response(response)
|
|
255
272
|
self
|
|
256
273
|
end
|
|
257
274
|
|
|
258
275
|
def destroy
|
|
259
|
-
|
|
276
|
+
request(:delete, member_path)
|
|
260
277
|
freeze
|
|
261
278
|
end
|
|
262
279
|
|
|
263
280
|
def reload
|
|
264
|
-
response =
|
|
281
|
+
response = request(:get, member_path)
|
|
265
282
|
set_attrs_from_response(response)
|
|
266
283
|
self
|
|
267
284
|
end
|
|
@@ -292,6 +309,14 @@ module Cloudflare
|
|
|
292
309
|
reload unless @loaded
|
|
293
310
|
end
|
|
294
311
|
|
|
312
|
+
# Instance-side wrapper around the class-level +request+. Lets custom
|
|
313
|
+
# action methods (e.g., +active_session.kick+) call +request(:post, ...)+
|
|
314
|
+
# without threading the api_token themselves; the class helper resolves
|
|
315
|
+
# it from the product namespace.
|
|
316
|
+
def request(method, path, **opts)
|
|
317
|
+
self.class.send(:request, method, path, **opts)
|
|
318
|
+
end
|
|
319
|
+
|
|
295
320
|
def member_path
|
|
296
321
|
self.class.send(:interpolate, self.class._member_path, @scope.merge(id: id))
|
|
297
322
|
end
|
data/lib/cloudflare/version.rb
CHANGED