cloudflare-ruby 0.1.2 → 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 +35 -0
- data/lib/cloudflare/connection.rb +9 -3
- data/lib/cloudflare/realtime_kit/README.md +11 -2
- data/lib/cloudflare/realtime_kit/active_session.rb +5 -5
- data/lib/cloudflare/realtime_kit/analytics.rb +11 -5
- 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 +5 -5
- data/lib/cloudflare/realtime_kit/session.rb +3 -3
- data/lib/cloudflare/realtime_kit/summary.rb +1 -1
- data/lib/cloudflare/realtime_kit/webhook.rb +1 -1
- data/lib/cloudflare/realtime_kit.rb +29 -0
- data/lib/cloudflare/resource.rb +59 -10
- 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,40 @@
|
|
|
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
|
+
|
|
20
|
+
## 0.2.0
|
|
21
|
+
|
|
22
|
+
- **Default `app_id` per process.** Set `Cloudflare::RealtimeKit.app_id`
|
|
23
|
+
once at boot and every RealtimeKit call defaults to it; pass `app_id:`
|
|
24
|
+
per-call only to override. Mirrors the existing `Cloudflare.account_id`
|
|
25
|
+
pattern. Eliminates `app_id:` repetition in apps that talk to a single
|
|
26
|
+
RealtimeKit app per process (the common case).
|
|
27
|
+
- `Resource.build_scope` and `extract_scope!` now consult a generic
|
|
28
|
+
`global_default_for(key)` helper that resolves `:account_id` against
|
|
29
|
+
`Cloudflare.account_id` and any other key against a same-named
|
|
30
|
+
accessor on the resource's product namespace
|
|
31
|
+
(e.g., `:app_id` → `Cloudflare::RealtimeKit.app_id`). Generalizes for
|
|
32
|
+
future product surfaces.
|
|
33
|
+
- `Recording.start_track`, `Recording.active_for`,
|
|
34
|
+
`Session.find_participant_by_peer_id`, `Analytics.daywise`, and
|
|
35
|
+
`Analytics.livestreams_overall` now accept `app_id:` as optional with
|
|
36
|
+
the default falling through.
|
|
37
|
+
|
|
3
38
|
## 0.1.2
|
|
4
39
|
|
|
5
40
|
- Add `require "bundler/gem_tasks"` to the Rakefile so `rake release`
|
|
@@ -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,14 +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
|
|
|
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
|
+
|
|
15
20
|
RK = Cloudflare::RealtimeKit # alias to taste
|
|
16
21
|
```
|
|
17
22
|
|
|
18
|
-
After that, `account_id:`
|
|
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.
|
|
19
26
|
|
|
20
27
|
## Apps
|
|
21
28
|
|
|
@@ -28,6 +35,8 @@ RK::App.all # GET /apps
|
|
|
28
35
|
|
|
29
36
|
## Meetings
|
|
30
37
|
|
|
38
|
+
Most calls below show `app_id:` for clarity. With `Cloudflare::RealtimeKit.app_id` set globally (see Setup), you can omit it.
|
|
39
|
+
|
|
31
40
|
```ruby
|
|
32
41
|
meeting = RK::Meeting.create(app_id: app.id, title: "Standup", record_on_start: true)
|
|
33
42
|
meeting.id # => "mtg-…"
|
|
@@ -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
|
|
@@ -18,14 +18,14 @@ module Cloudflare
|
|
|
18
18
|
module Analytics
|
|
19
19
|
class << self
|
|
20
20
|
# GET /analytics/daywise — daily breakdown of meeting activity.
|
|
21
|
-
def daywise(
|
|
21
|
+
def daywise(start_date:, end_date:, app_id: nil, account_id: nil)
|
|
22
22
|
fetch("/accounts/{account_id}/realtime/kit/{app_id}/analytics/daywise",
|
|
23
23
|
app_id: app_id, account_id: account_id,
|
|
24
24
|
params: { start_date: start_date, end_date: end_date })
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# GET /analytics/livestreams/overall — overall livestream metrics.
|
|
28
|
-
def livestreams_overall(
|
|
28
|
+
def livestreams_overall(start_time:, end_time:, app_id: nil, account_id: nil)
|
|
29
29
|
fetch("/accounts/{account_id}/realtime/kit/{app_id}/analytics/livestreams/overall",
|
|
30
30
|
app_id: app_id, account_id: account_id,
|
|
31
31
|
params: { start_time: start_time, end_time: end_time })
|
|
@@ -35,17 +35,23 @@ 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
|
|
|
42
43
|
# Local copies of the two private helpers from Resource — Analytics
|
|
43
44
|
# isn't a Resource subclass (no instances, just a function namespace),
|
|
44
|
-
# so it can't inherit the private class methods.
|
|
45
|
+
# so it can't inherit the private class methods. Falls back to
|
|
46
|
+
# +Cloudflare.account_id+ and +Cloudflare::RealtimeKit.app_id+ for
|
|
47
|
+
# the same per-process default behavior the Resource subclasses
|
|
48
|
+
# get for free.
|
|
45
49
|
def build_scope(account_id:, app_id:)
|
|
46
50
|
account = account_id || Cloudflare.account_id
|
|
51
|
+
app = app_id || RealtimeKit.app_id
|
|
47
52
|
raise ArgumentError, "missing required scope param: account_id" unless account
|
|
48
|
-
|
|
53
|
+
raise ArgumentError, "missing required scope param: app_id" unless app
|
|
54
|
+
{ account_id: account, app_id: app }
|
|
49
55
|
end
|
|
50
56
|
|
|
51
57
|
def interpolate(path, values)
|
|
@@ -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
|
|
@@ -38,21 +38,21 @@ module Cloudflare
|
|
|
38
38
|
# POST /recordings/track — start a multi-file (per-track) recording.
|
|
39
39
|
# Different upstream endpoint than +create+; use this when you need
|
|
40
40
|
# one media file per participant track.
|
|
41
|
-
def start_track(meeting_id:, layers:, app_id
|
|
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
|
|
48
48
|
|
|
49
49
|
# GET /recordings/active-recording/{meeting_id} — fetch the recording
|
|
50
50
|
# currently in progress for a meeting (404 if none).
|
|
51
|
-
def active_for(meeting_id:, app_id
|
|
51
|
+
def active_for(meeting_id:, app_id: nil, account_id: nil)
|
|
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
|
|
@@ -41,11 +41,11 @@ module Cloudflare
|
|
|
41
41
|
# GET /sessions/peer-report/{peer_id} — reverse lookup from a peer id.
|
|
42
42
|
# Returns participant-shaped data, not a session, so we expose it as a
|
|
43
43
|
# SessionParticipant instance.
|
|
44
|
-
def find_participant_by_peer_id(peer_id, app_id
|
|
44
|
+
def find_participant_by_peer_id(peer_id, app_id: nil, account_id: nil, filters: nil)
|
|
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
|
|
@@ -2,7 +2,36 @@ module Cloudflare
|
|
|
2
2
|
# Cloudflare RealtimeKit (formerly Dyte) — managed video/audio infrastructure
|
|
3
3
|
# for meetings, livestreams, recordings, sessions, and analytics. Each
|
|
4
4
|
# resource maps to a hand-written class under this namespace.
|
|
5
|
+
#
|
|
6
|
+
# == Defaults
|
|
7
|
+
#
|
|
8
|
+
# Most resources (Meeting, Participant, Recording, ...) live under an app.
|
|
9
|
+
# Set +app_id+ once at process boot and every call defaults to it; pass
|
|
10
|
+
# +app_id:+ per-call only to override.
|
|
11
|
+
#
|
|
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.
|
|
15
|
+
#
|
|
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
|
|
25
|
+
# Cloudflare::RealtimeKit::Meeting.find(id, app_id: "other-app-id") # override
|
|
5
26
|
module RealtimeKit
|
|
27
|
+
class << self
|
|
28
|
+
# Default app_id used when a resource call doesn't pass one explicitly.
|
|
29
|
+
attr_accessor :app_id
|
|
30
|
+
|
|
31
|
+
# Per-product API token. Falls back to +Cloudflare.api_token+ when nil.
|
|
32
|
+
attr_accessor :api_token
|
|
33
|
+
end
|
|
34
|
+
|
|
6
35
|
autoload :App, "cloudflare/realtime_kit/app"
|
|
7
36
|
autoload :Meeting, "cloudflare/realtime_kit/meeting"
|
|
8
37
|
autoload :Participant, "cloudflare/realtime_kit/participant"
|
data/lib/cloudflare/resource.rb
CHANGED
|
@@ -160,31 +160,49 @@ 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
|
-
# Pulls scope params out of the kwargs hash (mutating).
|
|
184
|
-
#
|
|
200
|
+
# Pulls scope params out of the kwargs hash (mutating). Falls back
|
|
201
|
+
# to product-level defaults (see +global_default_for+) when a key
|
|
202
|
+
# isn't provided.
|
|
185
203
|
def extract_scope!(attrs)
|
|
186
204
|
scope_params.each_with_object({}) do |key, h|
|
|
187
|
-
value = attrs.delete(key) || (key
|
|
205
|
+
value = attrs.delete(key) || global_default_for(key)
|
|
188
206
|
raise ArgumentError, "missing required scope param: #{key}" unless value
|
|
189
207
|
h[key] = value
|
|
190
208
|
end
|
|
@@ -192,12 +210,35 @@ module Cloudflare
|
|
|
192
210
|
|
|
193
211
|
def build_scope(provided)
|
|
194
212
|
scope_params.each_with_object({}) do |key, h|
|
|
195
|
-
value = provided[key] || (key
|
|
213
|
+
value = provided[key] || global_default_for(key)
|
|
196
214
|
raise ArgumentError, "missing required scope param: #{key}" unless value
|
|
197
215
|
h[key] = value
|
|
198
216
|
end
|
|
199
217
|
end
|
|
200
218
|
|
|
219
|
+
# Resolve a scope key to its module-level default:
|
|
220
|
+
# +:account_id+ → +Cloudflare.account_id+ (top-level config), and any
|
|
221
|
+
# other key → a same-named accessor on the resource's product
|
|
222
|
+
# namespace (e.g., +:app_id+ → +Cloudflare::RealtimeKit.app_id+).
|
|
223
|
+
# Lets callers configure once per process and stop repeating
|
|
224
|
+
# +app_id:+ at every call site. Returns nil when no default is set,
|
|
225
|
+
# which lets the caller raise the +missing required scope+ error.
|
|
226
|
+
def global_default_for(key)
|
|
227
|
+
return Cloudflare.account_id if key == :account_id
|
|
228
|
+
namespace = product_namespace
|
|
229
|
+
namespace&.respond_to?(key) ? namespace.public_send(key) : nil
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# +Cloudflare::RealtimeKit::Meeting+ → +Cloudflare::RealtimeKit+.
|
|
233
|
+
# Returns nil for resources without a product namespace (i.e.,
|
|
234
|
+
# +Cloudflare::Resource+ direct subclasses, which we don't have in
|
|
235
|
+
# practice).
|
|
236
|
+
def product_namespace
|
|
237
|
+
parts = name.split("::")
|
|
238
|
+
return nil if parts.size < 3
|
|
239
|
+
parts[0..-2].inject(Object) { |const, seg| const.const_get(seg) }
|
|
240
|
+
end
|
|
241
|
+
|
|
201
242
|
def interpolate(path, values)
|
|
202
243
|
path.gsub(/\{(\w+)\}/) do
|
|
203
244
|
key = $1.to_sym
|
|
@@ -226,18 +267,18 @@ module Cloudflare
|
|
|
226
267
|
def attributes = (ensure_loaded!; @attrs)
|
|
227
268
|
|
|
228
269
|
def update(**changes)
|
|
229
|
-
response =
|
|
270
|
+
response = request(:patch, member_path, body: self.class.to_wire_keys(changes))
|
|
230
271
|
set_attrs_from_response(response)
|
|
231
272
|
self
|
|
232
273
|
end
|
|
233
274
|
|
|
234
275
|
def destroy
|
|
235
|
-
|
|
276
|
+
request(:delete, member_path)
|
|
236
277
|
freeze
|
|
237
278
|
end
|
|
238
279
|
|
|
239
280
|
def reload
|
|
240
|
-
response =
|
|
281
|
+
response = request(:get, member_path)
|
|
241
282
|
set_attrs_from_response(response)
|
|
242
283
|
self
|
|
243
284
|
end
|
|
@@ -268,6 +309,14 @@ module Cloudflare
|
|
|
268
309
|
reload unless @loaded
|
|
269
310
|
end
|
|
270
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
|
+
|
|
271
320
|
def member_path
|
|
272
321
|
self.class.send(:interpolate, self.class._member_path, @scope.merge(id: id))
|
|
273
322
|
end
|
data/lib/cloudflare/version.rb
CHANGED