@@ -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,27 @@ 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 ()
176
+ local token , channel_error = nil , nil
159
177
local backoff = backoff_builder (60 , 1 , 0.1 )
160
178
while not sonos_conn :is_running () do
161
- local start_success = sonos_conn :start ()
162
- if start_success then return end
179
+ if sonos_conn .driver .waiting_for_token and token_receive_handle then
180
+ token , channel_error = token_receive_handle :receive ()
181
+ if not token then
182
+ log .warn (string.format (" Error requesting token: %s" , channel_error ))
183
+ local _ , get_token_err = sonos_conn .driver :get_oauth_token ()
184
+ if get_token_err then
185
+ log .warn (string.format (" notice: get_oauth_token -> %s" , get_token_err ))
186
+ end
187
+ end
188
+ else
189
+ local start_success = sonos_conn :start ()
190
+ if start_success then return end
191
+ end
163
192
cosock .socket .sleep (backoff ())
164
193
end
165
194
end , string.format (" %s Reconnect Task" , sonos_conn .device .label ))
171
200
--- @return SonosConnection
172
201
function SonosConnection .new (driver , device )
173
202
log .debug (string.format (" Creating new SonosConnection for %s" , device .label ))
174
- local self = setmetatable ({ driver = driver , device = device , _listener_uuids = {}, _initialized = false },
203
+ local self = setmetatable (
204
+ { driver = driver , device = device , _listener_uuids = {}, _initialized = false },
175
205
SonosConnection )
176
206
177
207
-- capture the label here in case something goes wonky like a callback being fired after a
@@ -185,14 +215,16 @@ function SonosConnection.new(driver, device)
185
215
local success = table.remove (json_result , 1 )
186
216
if not success then
187
217
log .error (st_utils .stringify_table (
188
- {response_body = msg .data , json = json_result }, " Couldn't decode JSON in WebSocket callback:" , false
218
+ { response_body = msg .data , json = json_result },
219
+ " Couldn't decode JSON in WebSocket callback:" , false
189
220
))
190
221
return
191
222
end
192
223
local header , body = table.unpack (table.unpack (json_result ))
193
224
if header .type == " groups" then
194
225
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 )
226
+ local household_id , current_coordinator = self .driver .sonos :get_coordinator_for_device (self
227
+ .device )
196
228
local _ , player_id = self .driver .sonos :get_player_for_device (self .device )
197
229
self .driver .sonos :update_household_info (header .householdId , body , self .device )
198
230
local _ , updated_coordinator = self .driver .sonos :get_coordinator_for_device (self .device )
@@ -225,8 +257,9 @@ function SonosConnection.new(driver, device)
225
257
local household = self .driver .sonos :get_household (header .householdId )
226
258
if household == nil or household .groups == nil then
227
259
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
260
+ { response_body = msg .data , household = household or header .householdId },
261
+ " Received groupVolume message for non-existent household or household groups dont exist" ,
262
+ false
230
263
))
231
264
return
232
265
end
@@ -246,8 +279,9 @@ function SonosConnection.new(driver, device)
246
279
local household = self .driver .sonos :get_household (header .householdId )
247
280
if household == nil or household .groups == nil then
248
281
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
282
+ { response_body = msg .data , household = household or header .householdId },
283
+ " Received playbackStatus message for non-existent household or household groups dont exist" ,
284
+ false
251
285
))
252
286
return
253
287
end
@@ -266,8 +300,9 @@ function SonosConnection.new(driver, device)
266
300
local household = self .driver .sonos :get_household (header .householdId )
267
301
if household == nil or household .groups == nil then
268
302
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
303
+ { response_body = msg .data , household = household or header .householdId },
304
+ " Received metadataStatus message for non-existent household or household groups dont exist" ,
305
+ false
271
306
))
272
307
return
273
308
end
@@ -289,19 +324,21 @@ function SonosConnection.new(driver, device)
289
324
local household = self .driver .sonos :get_household (header .householdId ) or { groups = {} }
290
325
291
326
for group_id , group in pairs (household .groups ) do
292
- local coordinator_id = self .driver .sonos :get_coordinator_for_group (header .householdId , group_id )
327
+ local coordinator_id = self .driver .sonos :get_coordinator_for_group (header .householdId ,
328
+ group_id )
293
329
local coordinator_player = household .players [coordinator_id ]
294
330
if coordinator_player == nil then
295
331
log .error (st_utils .stringify_table (
296
- {household = household , coordinator_id = coordinator_id }, " Received message for non-existent coordinator player" , false
332
+ { household = household , coordinator_id = coordinator_id },
333
+ " Received message for non-existent coordinator player" , false
297
334
))
298
335
return
299
336
end
300
337
301
338
local url_ip = lb_utils .force_url_table (coordinator_player .websocketUrl ).host
302
339
303
340
local favorites_response , err , _ =
304
- SonosRestApi .get_favorites (url_ip , SonosApi .DEFAULT_SONOS_PORT , header .householdId )
341
+ SonosRestApi .get_favorites (url_ip , SonosApi .DEFAULT_SONOS_PORT , header .householdId )
305
342
306
343
if err or not favorites_response then
307
344
log .error (" Error querying for favorites: " .. err )
@@ -310,7 +347,10 @@ function SonosConnection.new(driver, device)
310
347
for _ , favorite in ipairs (favorites_response .items or {}) do
311
348
local new_item = { id = favorite .id , name = favorite .name }
312
349
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
350
+ if favorite .service and favorite .service .name then
351
+ new_item .mediaSource = favorite
352
+ .service .name
353
+ end
314
354
table.insert (new_favorites , new_item )
315
355
end
316
356
self .driver .sonos :update_household_favorites (header .householdId , new_favorites )
@@ -329,11 +369,14 @@ function SonosConnection.new(driver, device)
329
369
end
330
370
end
331
371
else
332
- log .warn (string.format (" WebSocket Message for %s did not have a data payload: %s" , device_name , st_utils .stringify_table (msg )))
372
+ log .warn (string.format (" WebSocket Message for %s did not have a data payload: %s" , device_name ,
373
+ st_utils .stringify_table (msg )))
333
374
end
334
375
end
335
376
336
377
self .on_error = function (uuid , err )
378
+ -- TODO: Implement a call to `getToken` here on certain failures, once I actually
379
+ -- know what an auth failure over websocket is going to look like.
337
380
log .error (err or (" unknown websocket error for " .. (self .device .label or " unknown device" )))
338
381
end
339
382
351
394
function SonosConnection :is_running ()
352
395
local self_running = self :self_running ()
353
396
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
397
+ log .debug (string.format (" %s all connections running? %s" , self .device .label ,
398
+ st_utils .stringify_table ({ coordinator = self_running , mine = self_running })))
399
+ return self_running and coord_running
356
400
end
357
401
358
402
--- Whether or not the connection has a live websocket connection
@@ -425,7 +469,26 @@ function SonosConnection:start()
425
469
_open_coordinator_socket (self , household_id , player_id )
426
470
end
427
471
428
- self :refresh_subscriptions ()
472
+
473
+ local reply_tx , reply_rx = cosock .channel .new ()
474
+
475
+ self :refresh_subscriptions (reply_tx )
476
+
477
+ local reply = reply_rx :receive ()
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