@@ -40,16 +40,26 @@ local favorites_version = ""
40
40
--- @param sonos_conn SonosConnection
41
41
--- @param namespaces string[]
42
42
--- @param command " subscribe" | " unsubscribe"
43
- local _update_subscriptions_helper = function (sonos_conn , householdId , playerId , groupId , namespaces , command )
43
+ local _update_subscriptions_helper = function (sonos_conn , householdId , playerId , groupId , namespaces ,
44
+ command )
44
45
for _ , namespace in ipairs (namespaces ) do
46
+ local wss_msg_header = {
47
+ namespace = namespace ,
48
+ command = command ,
49
+ householdId = householdId ,
50
+ groupId = groupId ,
51
+ playerId = playerId
52
+ }
53
+ local maybe_token , err = sonos_conn .driver :get_oauth_token ()
54
+ if err then
55
+ log .warn (string.format (" notice: get_oauth_token -> %s" , err ))
56
+ end
57
+
58
+ if maybe_token then
59
+ wss_msg_header .authorization = string.format (" Bearer %s" , maybe_token .access_token )
60
+ end
45
61
local payload_table = {
46
- {
47
- namespace = namespace ,
48
- command = command ,
49
- householdId = householdId ,
50
- groupId = groupId ,
51
- playerId = playerId
52
- },
62
+ wss_msg_header ,
53
63
{}
54
64
}
55
65
local payload = json .encode (payload_table )
82
92
--- @param self_player_id PlayerId
83
93
local function _open_coordinator_socket (sonos_conn , household_id , self_player_id )
84
94
log .debug (" Open coordinator socket for: " .. sonos_conn .device .label )
85
- local _ , coordinator_id , err = sonos_conn .driver .sonos :get_coordinator_for_device (sonos_conn .device )
95
+ local _ , coordinator_id , err = sonos_conn .driver .sonos :get_coordinator_for_device (sonos_conn
96
+ .device )
86
97
if err ~= nil then
87
98
log .error (
88
99
string.format (
@@ -95,14 +106,16 @@ local function _open_coordinator_socket(sonos_conn, household_id, self_player_id
95
106
if coordinator_id ~= self_player_id then
96
107
local household = sonos_conn .driver .sonos :get_household (household_id )
97
108
if household == nil then
98
- log .error (string.format (" Cannot open coordinator socket, houshold doesn't exist: %s" , household_id ))
109
+ log .error (string.format (" Cannot open coordinator socket, houshold doesn't exist: %s" ,
110
+ household_id ))
99
111
return
100
112
end
101
113
102
114
local coordinator = household .players [coordinator_id ]
103
115
if coordinator == nil then
104
116
log .error (st_utils .stringify_table (
105
- {household = sonos_conn .driver .sonos :get_household (household_id )}, string.format (" Coordinator doesn't exist for player: %s" , sonos_conn .device .label ), false
117
+ { household = sonos_conn .driver .sonos :get_household (household_id ) },
118
+ string.format (" Coordinator doesn't exist for player: %s" , sonos_conn .device .label ), false
106
119
))
107
120
return
108
121
end
@@ -155,11 +168,26 @@ end
155
168
--- @param sonos_conn SonosConnection
156
169
local function _spawn_reconnect_task (sonos_conn )
157
170
log .debug (" Spawning reconnect task for " , sonos_conn .device .label )
171
+ local token_receive_handle , err = sonos_conn .driver :get_oauth_token_receive_handle ()
172
+ if not token_receive_handle then
173
+ log .warn (string.format (" error creating oauth token receive handle for respawn task: %s" , err ))
174
+ end
158
175
cosock .spawn (function ()
159
176
local backoff = backoff_builder (60 , 1 , 0.1 )
160
177
while not sonos_conn :is_running () do
161
- local start_success = sonos_conn :start ()
162
- if start_success then return end
178
+ if sonos_conn .driver .waiting_for_token and token_receive_handle then
179
+ local token , channel_error = token_receive_handle :receive ()
180
+ if not token then
181
+ log .warn (string.format (" Error requesting token: %s" , channel_error ))
182
+ local _ , get_token_err = sonos_conn .driver :get_oauth_token ()
183
+ if get_token_err then
184
+ log .warn (string.format (" notice: get_oauth_token -> %s" , get_token_err ))
185
+ end
186
+ end
187
+ else
188
+ local start_success = sonos_conn :start ()
189
+ if start_success then return end
190
+ end
163
191
cosock .socket .sleep (backoff ())
164
192
end
165
193
end , string.format (" %s Reconnect Task" , sonos_conn .device .label ))
171
199
--- @return SonosConnection
172
200
function SonosConnection .new (driver , device )
173
201
log .debug (string.format (" Creating new SonosConnection for %s" , device .label ))
174
- local self = setmetatable ({ driver = driver , device = device , _listener_uuids = {}, _initialized = false },
202
+ local self = setmetatable (
203
+ { driver = driver , device = device , _listener_uuids = {}, _initialized = false },
175
204
SonosConnection )
176
205
177
206
-- capture the label here in case something goes wonky like a callback being fired after a
@@ -185,14 +214,16 @@ function SonosConnection.new(driver, device)
185
214
local success = table.remove (json_result , 1 )
186
215
if not success then
187
216
log .error (st_utils .stringify_table (
188
- {response_body = msg .data , json = json_result }, " Couldn't decode JSON in WebSocket callback:" , false
217
+ { response_body = msg .data , json = json_result },
218
+ " Couldn't decode JSON in WebSocket callback:" , false
189
219
))
190
220
return
191
221
end
192
222
local header , body = table.unpack (table.unpack (json_result ))
193
223
if header .type == " groups" then
194
224
log .trace (string.format (" Groups type message for %s" , device_name ))
195
- local household_id , current_coordinator = self .driver .sonos :get_coordinator_for_device (self .device )
225
+ local household_id , current_coordinator = self .driver .sonos :get_coordinator_for_device (self
226
+ .device )
196
227
local _ , player_id = self .driver .sonos :get_player_for_device (self .device )
197
228
self .driver .sonos :update_household_info (header .householdId , body , self .device )
198
229
local _ , updated_coordinator = self .driver .sonos :get_coordinator_for_device (self .device )
@@ -225,8 +256,9 @@ function SonosConnection.new(driver, device)
225
256
local household = self .driver .sonos :get_household (header .householdId )
226
257
if household == nil or household .groups == nil then
227
258
log .error (st_utils .stringify_table (
228
- {response_body = msg .data , household = household or header .householdId },
229
- " Received groupVolume message for non-existent household or household groups dont exist" , false
259
+ { response_body = msg .data , household = household or header .householdId },
260
+ " Received groupVolume message for non-existent household or household groups dont exist" ,
261
+ false
230
262
))
231
263
return
232
264
end
@@ -246,8 +278,9 @@ function SonosConnection.new(driver, device)
246
278
local household = self .driver .sonos :get_household (header .householdId )
247
279
if household == nil or household .groups == nil then
248
280
log .error (st_utils .stringify_table (
249
- {response_body = msg .data , household = household or header .householdId },
250
- " Received playbackStatus message for non-existent household or household groups dont exist" , false
281
+ { response_body = msg .data , household = household or header .householdId },
282
+ " Received playbackStatus message for non-existent household or household groups dont exist" ,
283
+ false
251
284
))
252
285
return
253
286
end
@@ -266,8 +299,9 @@ function SonosConnection.new(driver, device)
266
299
local household = self .driver .sonos :get_household (header .householdId )
267
300
if household == nil or household .groups == nil then
268
301
log .error (st_utils .stringify_table (
269
- {response_body = msg .data , household = household or header .householdId },
270
- " Received metadataStatus message for non-existent household or household groups dont exist" , false
302
+ { response_body = msg .data , household = household or header .householdId },
303
+ " Received metadataStatus message for non-existent household or household groups dont exist" ,
304
+ false
271
305
))
272
306
return
273
307
end
@@ -289,19 +323,21 @@ function SonosConnection.new(driver, device)
289
323
local household = self .driver .sonos :get_household (header .householdId ) or { groups = {} }
290
324
291
325
for group_id , group in pairs (household .groups ) do
292
- local coordinator_id = self .driver .sonos :get_coordinator_for_group (header .householdId , group_id )
326
+ local coordinator_id = self .driver .sonos :get_coordinator_for_group (header .householdId ,
327
+ group_id )
293
328
local coordinator_player = household .players [coordinator_id ]
294
329
if coordinator_player == nil then
295
330
log .error (st_utils .stringify_table (
296
- {household = household , coordinator_id = coordinator_id }, " Received message for non-existent coordinator player" , false
331
+ { household = household , coordinator_id = coordinator_id },
332
+ " Received message for non-existent coordinator player" , false
297
333
))
298
334
return
299
335
end
300
336
301
337
local url_ip = lb_utils .force_url_table (coordinator_player .websocketUrl ).host
302
338
303
339
local favorites_response , err , _ =
304
- SonosRestApi .get_favorites (url_ip , SonosApi .DEFAULT_SONOS_PORT , header .householdId )
340
+ SonosRestApi .get_favorites (url_ip , SonosApi .DEFAULT_SONOS_PORT , header .householdId )
305
341
306
342
if err or not favorites_response then
307
343
log .error (" Error querying for favorites: " .. err )
@@ -310,7 +346,10 @@ function SonosConnection.new(driver, device)
310
346
for _ , favorite in ipairs (favorites_response .items or {}) do
311
347
local new_item = { id = favorite .id , name = favorite .name }
312
348
if favorite .imageUrl then new_item .imageUrl = favorite .imageUrl end
313
- if favorite .service and favorite .service .name then new_item .mediaSource = favorite .service .name end
349
+ if favorite .service and favorite .service .name then
350
+ new_item .mediaSource = favorite
351
+ .service .name
352
+ end
314
353
table.insert (new_favorites , new_item )
315
354
end
316
355
self .driver .sonos :update_household_favorites (header .householdId , new_favorites )
@@ -329,11 +368,14 @@ function SonosConnection.new(driver, device)
329
368
end
330
369
end
331
370
else
332
- log .warn (string.format (" WebSocket Message for %s did not have a data payload: %s" , device_name , st_utils .stringify_table (msg )))
371
+ log .warn (string.format (" WebSocket Message for %s did not have a data payload: %s" , device_name ,
372
+ st_utils .stringify_table (msg )))
333
373
end
334
374
end
335
375
336
376
self .on_error = function (uuid , err )
377
+ -- TODO: Implement a call to `getToken` here on certain failures, once I actually
378
+ -- know what an auth failure over websocket is going to look like.
337
379
log .error (err or (" unknown websocket error for " .. (self .device .label or " unknown device" )))
338
380
end
339
381
351
393
function SonosConnection :is_running ()
352
394
local self_running = self :self_running ()
353
395
local coord_running = self :coordinator_running ()
354
- log .debug (string.format (" %s all connections running? %s" , self .device .label , st_utils .stringify_table ({coordinator = self_running , mine = self_running })))
355
- return self_running and coord_running
396
+ log .debug (string.format (" %s all connections running? %s" , self .device .label ,
397
+ st_utils .stringify_table ({ coordinator = self_running , mine = self_running })))
398
+ return self_running and coord_running
356
399
end
357
400
358
401
--- Whether or not the connection has a live websocket connection
@@ -425,7 +468,27 @@ function SonosConnection:start()
425
468
_open_coordinator_socket (self , household_id , player_id )
426
469
end
427
470
428
- self :refresh_subscriptions ()
471
+
472
+ local reply_tx , reply_rx = cosock .channel .new ()
473
+
474
+ self :refresh_subscriptions (reply_tx )
475
+
476
+ local reply = reply_rx :receive ()
477
+ log .info (st_utils .stringify_table (reply , " reply from subscription attempt" , true ))
478
+ -- TODO handle logic to abort connection if the refresh is refused
479
+ -- once we know what a forbidden/unauthorized error will look like.
480
+ local connection_successful = true
481
+ if not connection_successful then
482
+ if not self .driver .waiting_for_token then
483
+ local err = self .driver :get_oauth_token ()
484
+ if err then
485
+ log .warn (string.format (" notice: get_oauth_token -> %s" , err ))
486
+ end
487
+ self .driver .waiting_for_token = true
488
+ self .on_close ()
489
+ end
490
+ return false
491
+ end
429
492
local coordinator_id = self .driver .sonos :get_coordinator_for_player (household_id , player_id )
430
493
if Router .is_connected (player_id ) and Router .is_connected (coordinator_id ) then
431
494
self .device :online ()
0 commit comments