conversant 1.0.17 → 1.0.19
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 +29 -0
- data/README.md +8 -15
- data/lib/conversant/v3/services/lms/job.rb +40 -52
- data/lib/conversant/v3/services/portal.rb +10 -13
- data/lib/conversant/version.rb +1 -1
- data/sig/conversant/v3/services/lms.rbs +3 -4
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0bd892aa072333932ee783013051c4a32fc77875c71b128a080e717bdfca10a
|
|
4
|
+
data.tar.gz: b79962fb9f3d80b38b966f9eb14e9ae9192e2e97daae9f0ba998e7cfb5a422c0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5f4ad35b17ed3670a73f71c0b67dc9f20ed09384e15c0c42895ff611f4817a3a1f2cbb2e4af4b534bf953a2e83e3d7a6b067afa937c5e33ec00c8f5709b1b71a
|
|
7
|
+
data.tar.gz: f47decce579d7da1b863fd8b580b5972970fd266fe623a204ef8c38f96974f6ba87928cfe7d72a7ea5fbcac671d9b72e1a838bf6707b59006c425109aebcab15
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.19] - 2026-02-06
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **LMS Job RBS Type Signatures**: Updated RBS signatures in `sig/conversant/v3/services/lms.rbs` to match actual implementation
|
|
12
|
+
- Fixed `where` return type from `Array[Hash[String, untyped]]` to `Array[Hash[Symbol, untyped]]?` (nilable, symbol keys)
|
|
13
|
+
- Fixed `parse_status` parameter type from `Integer | String | nil` to `Integer` (value is always `.to_i`)
|
|
14
|
+
- Fixed `build_manifest_url` signature to `(String? play_domain, Hash item, String? output_type)` matching actual parameters
|
|
15
|
+
- Removed non-existent `build_profile_url` method signature
|
|
16
|
+
|
|
17
|
+
## [1.0.18] - 2025-10-08
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **Portal Service Authentication**: Fixed SESSION cookie handling to use root sessions directly
|
|
21
|
+
- Portal service now uses root SESSION + SSO_GW_SESSION2 cookies directly instead of attempting to exchange them
|
|
22
|
+
- Removed incorrect cookie extraction from response (Portal doesn't return a new SESSION cookie)
|
|
23
|
+
- Changed from extracting `response.cookies['SESSION']` to using `sessions[:session]` from SSO login
|
|
24
|
+
- Resolves "NO_SESSION_IN_RESPONSE" error when calling `portal.appliances`
|
|
25
|
+
- Matches original implementation behavior where root sessions are used for all Portal API calls
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- Updated `Portal#fetch_new_session` method to return root SESSION cookie directly after verification
|
|
29
|
+
- Test call to `#{portal_endpoint}/?cId=#{customer_id}` now only validates session instead of extracting cookies
|
|
30
|
+
- Log message changed from "REQUESTING_SESSION" to "USING_ROOT_SESSIONS" for clarity
|
|
31
|
+
|
|
32
|
+
### Impact
|
|
33
|
+
- Portal service now works correctly without "Missing SESSION for Portal" errors
|
|
34
|
+
- `portal.appliances` and other Portal API methods now authenticate properly
|
|
35
|
+
- Consistent with other services (CDN, LMS, VMS) that also exchange sessions but Portal uses root sessions directly
|
|
36
|
+
|
|
8
37
|
## [1.0.17] - 2025-10-01
|
|
9
38
|
|
|
10
39
|
### Fixed
|
data/README.md
CHANGED
|
@@ -472,9 +472,9 @@ jobs = lms.job.where(
|
|
|
472
472
|
)
|
|
473
473
|
|
|
474
474
|
jobs.each do |job|
|
|
475
|
-
puts "Job #{job[
|
|
476
|
-
puts " Status: #{job[
|
|
477
|
-
puts " Created: #{job[
|
|
475
|
+
puts "Job #{job[:id]}: #{job[:name]}"
|
|
476
|
+
puts " Status: #{job[:status]}"
|
|
477
|
+
puts " Created: #{job[:created_at]}"
|
|
478
478
|
end
|
|
479
479
|
|
|
480
480
|
# Domain Management
|
|
@@ -834,20 +834,13 @@ end
|
|
|
834
834
|
|
|
835
835
|
## Version Notes
|
|
836
836
|
|
|
837
|
-
### Latest Release: 1.0.
|
|
837
|
+
### Latest Release: 1.0.19 (2026-02-06)
|
|
838
838
|
|
|
839
|
-
**
|
|
839
|
+
**RBS Type Signature Fix**: Updated LMS Job RBS signatures to match actual implementation
|
|
840
840
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
- ✅ Fixed JSESSIONID authentication for LMS service (resolves "Missing JSESSIONID" error)
|
|
846
|
-
- ✅ Fixed JSESSIONID authentication for VMS service
|
|
847
|
-
- ✅ Improved RestClient cookie handling (switched from manual `'Cookie'` header to `:cookies` option)
|
|
848
|
-
- ✅ Fixed method visibility issues in Authorization module
|
|
849
|
-
|
|
850
|
-
**Upgrade recommended** if you're using LMS or VMS services. CDN and Portal services are unaffected.
|
|
841
|
+
- Fixed `where` return type to `Array[Hash[Symbol, untyped]]?`
|
|
842
|
+
- Fixed `build_manifest_url` parameter signature
|
|
843
|
+
- Removed non-existent `build_profile_url` method signature
|
|
851
844
|
|
|
852
845
|
See [CHANGELOG.md](CHANGELOG.md) for complete version history.
|
|
853
846
|
|
|
@@ -4,9 +4,9 @@ module Conversant
|
|
|
4
4
|
module V3
|
|
5
5
|
module Services
|
|
6
6
|
class LMS
|
|
7
|
-
# Job management service for live
|
|
7
|
+
# Job management service for live-streaming operations
|
|
8
8
|
#
|
|
9
|
-
# Provides comprehensive job management functionality for live
|
|
9
|
+
# Provides comprehensive job management functionality for live-streaming including:
|
|
10
10
|
# - Job listing and filtering
|
|
11
11
|
# - Job status tracking
|
|
12
12
|
# - Stream profile information
|
|
@@ -61,9 +61,15 @@ module Conversant
|
|
|
61
61
|
# - :encoder [String] encoder IP address
|
|
62
62
|
# - :server [String] execution server
|
|
63
63
|
# - :manifest [String] manifest URL
|
|
64
|
-
# - :
|
|
64
|
+
# - :profiles_count [Integer] number of stream profiles
|
|
65
65
|
# - :transcoding [Boolean] transcoding enabled
|
|
66
66
|
# - :gpu [Boolean] GPU acceleration enabled
|
|
67
|
+
# - :ha [Boolean] high availability enabled
|
|
68
|
+
# - :dvr [Boolean] DVR enabled
|
|
69
|
+
# - :dai [Boolean] dynamic ad insertion enabled
|
|
70
|
+
# - :encrypted [Boolean] stream encryption enabled
|
|
71
|
+
# - :ingest_url [String] RTMP ingest URL
|
|
72
|
+
# - :play_domain [String] playback domain
|
|
67
73
|
#
|
|
68
74
|
# @example Get streaming jobs
|
|
69
75
|
# jobs = lms.job.where(status: 'streaming', limit: 50)
|
|
@@ -79,19 +85,14 @@ module Conversant
|
|
|
79
85
|
response = JSON.parse(@parent.send(:call, 'GET', "/v4/streams?#{params.to_query}"))
|
|
80
86
|
return nil unless response
|
|
81
87
|
|
|
82
|
-
items = response['list'] || response[:list] || []
|
|
88
|
+
items = [response['list'] || response[:list] || []].flatten.map(&:with_indifferent_access)
|
|
83
89
|
|
|
84
90
|
items.map do |item|
|
|
85
|
-
status = item[
|
|
86
|
-
inbound = item[
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
preset: stream['preset_name'] || stream[:preset_name],
|
|
91
|
-
type: stream['type'] || stream[:type],
|
|
92
|
-
profile: build_profile_url(inbound, item, stream)
|
|
93
|
-
}
|
|
94
|
-
end
|
|
91
|
+
status = item[:status]&.to_i
|
|
92
|
+
inbound = item[:stream_rtmp_inbound]
|
|
93
|
+
play_domain = item[:play_domain] || item['play_domain']
|
|
94
|
+
outbounds = item[:stream_rtmp_outbounds] || item['stream_rtmp_outbounds'] || []
|
|
95
|
+
output_type = outbounds.first && (outbounds.first['type'] || outbounds.first[:type])
|
|
95
96
|
|
|
96
97
|
{
|
|
97
98
|
entity: item['osp_id'] || item[:osp_id],
|
|
@@ -103,12 +104,18 @@ module Conversant
|
|
|
103
104
|
started_at: parse_timestamp(item['started'] || item[:started]),
|
|
104
105
|
ended_at: parse_timestamp(item['ended'] || item[:ended]),
|
|
105
106
|
client: inbound && (inbound['client_ip'] || inbound[:client_ip]),
|
|
107
|
+
ingest_url: inbound&.dig(:push_url) || inbound&.dig('push_url'),
|
|
106
108
|
encoder: extract_encoder(item),
|
|
107
109
|
server: item['exec_ta'] || item[:exec_ta],
|
|
108
|
-
manifest: build_manifest_url(
|
|
109
|
-
|
|
110
|
+
manifest: build_manifest_url(play_domain, item, output_type),
|
|
111
|
+
profiles_count: outbounds.size,
|
|
110
112
|
transcoding: item['transcoding'] || item[:transcoding],
|
|
111
|
-
gpu: (item['gpu'] || item[:gpu]) == 'gpu'
|
|
113
|
+
gpu: (item['gpu'] || item[:gpu]) == 'gpu',
|
|
114
|
+
ha: item['ha'] || item[:ha],
|
|
115
|
+
dvr: item['dvr'] || item[:dvr],
|
|
116
|
+
dai: item['dai'] || item[:dai],
|
|
117
|
+
encrypted: item[:stream_encrypts]&.any? || item['stream_encrypts']&.any?,
|
|
118
|
+
play_domain: play_domain
|
|
112
119
|
}
|
|
113
120
|
end
|
|
114
121
|
rescue StandardError => e
|
|
@@ -121,13 +128,19 @@ module Conversant
|
|
|
121
128
|
# Parse numeric status code to string
|
|
122
129
|
#
|
|
123
130
|
# @param status [Integer] numeric status code
|
|
124
|
-
# @return [String
|
|
131
|
+
# @return [String] status string or 'unknown_N' if unknown
|
|
125
132
|
def parse_status(status)
|
|
126
133
|
case status
|
|
127
134
|
when 1 then 'ready'
|
|
128
135
|
when 2 then 'streaming'
|
|
136
|
+
when 3 then 'stopped'
|
|
137
|
+
when 4 then 'error'
|
|
138
|
+
when 5 then 'starting'
|
|
139
|
+
when 6 then 'stopping'
|
|
140
|
+
when 7 then 'restarting'
|
|
141
|
+
when 8 then 'pending'
|
|
129
142
|
when 12 then 'interrupted'
|
|
130
|
-
else status
|
|
143
|
+
else "unknown_#{status}"
|
|
131
144
|
end
|
|
132
145
|
end
|
|
133
146
|
|
|
@@ -164,48 +177,23 @@ module Conversant
|
|
|
164
177
|
end
|
|
165
178
|
end
|
|
166
179
|
|
|
167
|
-
# Build profile URL for a stream
|
|
168
|
-
#
|
|
169
|
-
# @param inbound [Hash] inbound stream configuration
|
|
170
|
-
# @param item [Hash] job item hash
|
|
171
|
-
# @param stream [Hash] stream profile configuration
|
|
172
|
-
# @return [String, nil] profile URL or nil if inbound is missing
|
|
173
|
-
def build_profile_url(inbound, item, stream)
|
|
174
|
-
return nil unless inbound
|
|
175
|
-
|
|
176
|
-
domain = inbound['domain'] || inbound[:domain]
|
|
177
|
-
app = item['app'] || item[:app]
|
|
178
|
-
stream_name = item['stream'] || item[:stream]
|
|
179
|
-
preset = stream['preset_name'] || stream[:preset_name]
|
|
180
|
-
type = stream['type'] || stream[:type]
|
|
181
|
-
|
|
182
|
-
"http://#{domain}/#{app}/#{stream_name}-#{preset}#{stream_extension(type)}"
|
|
183
|
-
end
|
|
184
|
-
|
|
185
180
|
# Build manifest URL for a job
|
|
186
181
|
#
|
|
187
|
-
# @param
|
|
182
|
+
# @param play_domain [String] playback domain
|
|
188
183
|
# @param item [Hash] job item hash
|
|
189
|
-
# @param
|
|
190
|
-
# @return [String, nil] manifest URL or nil if
|
|
191
|
-
def build_manifest_url(
|
|
192
|
-
return nil unless
|
|
184
|
+
# @param output_type [String] output stream type ('hls', 'dash')
|
|
185
|
+
# @return [String, nil] manifest URL or nil if play_domain is missing
|
|
186
|
+
def build_manifest_url(play_domain, item, output_type)
|
|
187
|
+
return nil unless play_domain
|
|
193
188
|
|
|
194
|
-
domain = inbound['domain'] || inbound[:domain]
|
|
195
189
|
app = item['app'] || item[:app]
|
|
196
190
|
manifest_name = item['manifest_name'] || item[:manifest_name]
|
|
191
|
+
ext = stream_extension(output_type)
|
|
197
192
|
|
|
198
|
-
ext
|
|
199
|
-
type = profiles.first[:type]
|
|
200
|
-
stream_extension(type)
|
|
201
|
-
else
|
|
202
|
-
''
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
"http://#{domain}/#{app}/#{manifest_name}#{ext}"
|
|
193
|
+
"https://#{play_domain}/#{app}/#{manifest_name}#{ext}"
|
|
206
194
|
end
|
|
207
195
|
end
|
|
208
196
|
end
|
|
209
197
|
end
|
|
210
198
|
end
|
|
211
|
-
end
|
|
199
|
+
end
|
|
@@ -146,17 +146,18 @@ module Conversant
|
|
|
146
146
|
# Fetches a new SESSION cookie for Portal API access
|
|
147
147
|
#
|
|
148
148
|
# Authenticates with root portal sessions (SESSION + SSO_GW_SESSION2) and
|
|
149
|
-
#
|
|
150
|
-
#
|
|
149
|
+
# uses them directly for Portal API calls. Makes a test call to verify
|
|
150
|
+
# the sessions are valid.
|
|
151
151
|
#
|
|
152
152
|
# @return [String, nil] The SESSION cookie value, or nil if authentication fails
|
|
153
153
|
def fetch_new_session
|
|
154
154
|
sessions = authenticate
|
|
155
155
|
return nil unless sessions && sessions[:session] && sessions[:sso_gw_session2]
|
|
156
156
|
|
|
157
|
-
logger.debug "#{identifier}.METHOD:authorize.
|
|
157
|
+
logger.debug "#{identifier}.METHOD:authorize.USING_ROOT_SESSIONS"
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
# Make a test call to verify sessions are valid
|
|
160
|
+
RestClient.get(
|
|
160
161
|
"#{portal_endpoint}/?cId=#{customer_id}",
|
|
161
162
|
{
|
|
162
163
|
authority: URI.parse(portal_endpoint).hostname,
|
|
@@ -171,15 +172,11 @@ module Conversant
|
|
|
171
172
|
}
|
|
172
173
|
)
|
|
173
174
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
else
|
|
180
|
-
logger.error "#{identifier}.METHOD:authorize.NO_SESSION_IN_RESPONSE"
|
|
181
|
-
nil
|
|
182
|
-
end
|
|
175
|
+
# Cache and return the root SESSION cookie
|
|
176
|
+
session_cookie = sessions[:session]
|
|
177
|
+
redis.set(session_cache_key, session_cookie, ex: configuration.cache_ttl)
|
|
178
|
+
logger.debug "#{identifier}.METHOD:authorize.SESSION_OBTAINED"
|
|
179
|
+
session_cookie
|
|
183
180
|
rescue RestClient::Unauthorized, RestClient::Forbidden => e
|
|
184
181
|
logger.error "#{identifier}.METHOD:authorize.AUTH_ERROR:#{e.message}"
|
|
185
182
|
nil
|
data/lib/conversant/version.rb
CHANGED
|
@@ -9,16 +9,15 @@ module Conversant
|
|
|
9
9
|
attr_reader parent: LMS
|
|
10
10
|
|
|
11
11
|
def initialize: (LMS parent) -> void
|
|
12
|
-
def where: (**untyped params) -> Array[Hash[
|
|
12
|
+
def where: (**untyped params) -> Array[Hash[Symbol, untyped]]?
|
|
13
13
|
|
|
14
14
|
private
|
|
15
15
|
|
|
16
|
-
def parse_status: (Integer
|
|
16
|
+
def parse_status: (Integer status) -> String
|
|
17
17
|
def parse_timestamp: (Integer | String | nil timestamp) -> DateTime?
|
|
18
18
|
def extract_encoder: (Hash[String | Symbol, untyped] item) -> String
|
|
19
19
|
def stream_extension: (String? type) -> String
|
|
20
|
-
def
|
|
21
|
-
def build_manifest_url: (Hash[String | Symbol, untyped]? inbound, Hash[String | Symbol, untyped] item, Array[Hash[Symbol, untyped]]? profiles) -> String?
|
|
20
|
+
def build_manifest_url: (String? play_domain, Hash[String | Symbol, untyped] item, String? output_type) -> String?
|
|
22
21
|
end
|
|
23
22
|
|
|
24
23
|
# Domain management for LMS
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: conversant
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.19
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tho Nguyen
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-02-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|