bencevans / node-sonos

🔈 Sonos Media Player Interface/Client
https://www.npmjs.com/package/sonos
MIT License
703 stars 147 forks source link

Sonos Boost breaks the module #515

Open pierrephi opened 3 years ago

pierrephi commented 3 years ago

I have a Sonos Boost (bridge) in my network and whenever it is plugged in the module breaks with the following error:

[10.06.2021 22:58.30.537] [ERROR] Error: Request failed with status code 405
    at createError (/Users/pxxxx/Dropbox/Development/MagicMirror/modules/MMM-Sonos/node_modules/axios/lib/core/createError.js:16:15)
    at settle (/Users/pxxxx/Dropbox/Development/MagicMirror/modules/MMM-Sonos/node_modules/axios/lib/core/settle.js:17:12)
    at IncomingMessage.handleStreamEnd (/Users/pxxxx/Dropbox/Development/MagicMirror/modules/MMM-Sonos/node_modules/axios/lib/adapters/http.js:237:11)
    at IncomingMessage.emit (node:events:391:22)
    at endReadableNT (node:internal/streams/readable:1307:12)
    at processTicksAndRejections (node:internal/process/task_queues:81:21) {
  config: {
    url: 'http://192.168.1.162:1400/MediaRenderer/AVTransport/Control',
    method: 'post',
    data: '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetTransportInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID></u:GetTransportInfo></s:Body></s:Envelope>',
    headers: {
      Accept: 'application/json, text/plain, */*',
      'Content-Type': 'text/xml; charset=utf8',
      SOAPAction: '"urn:schemas-upnp-org:service:AVTransport:1#GetTransportInfo"',
      'User-Agent': 'axios/0.19.0',
      'Content-Length': 274
    },
    transformRequest: [ [Function: transformRequest] ],
    transformResponse: [ [Function: transformResponse] ],
    timeout: 0,
    adapter: [Function: httpAdapter],
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    validateStatus: [Function: validateStatus]
  },
  request: <ref *1> ClientRequest {
    _events: [Object: null prototype] {
      abort: [Function (anonymous)],
      aborted: [Function (anonymous)],
      error: [Function (anonymous)],
      socket: [Function (anonymous)],
      timeout: [Function (anonymous)],
      prefinish: [Function: requestOnPrefinish]
    },
    _eventsCount: 6,
    _maxListeners: undefined,
    outputData: [],
    outputSize: 0,
    writable: true,
    destroyed: false,
    _last: true,
    chunkedEncoding: false,
    shouldKeepAlive: false,
    _defaultKeepAlive: true,
    useChunkedEncodingByDefault: true,
    sendDate: false,
    _removedConnection: false,
    _removedContLen: false,
    _removedTE: false,
    _contentLength: null,
    _hasBody: true,
    _trailer: '',
    finished: true,
    _headerSent: true,
    _closed: false,
    socket: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 8,
      _maxListeners: undefined,
      _writableState: [WritableState],
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: null,
      _server: null,
      parser: null,
      _httpMessage: [Circular *1],
      write: [Function: writeAfterFIN],
      [Symbol(async_id_symbol)]: 1118,
      [Symbol(kHandle)]: null,
      [Symbol(kSetNoDelay)]: false,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 252,
      [Symbol(kBytesWritten)]: 574,
      [Symbol(RequestTimeout)]: undefined
    },
    _header: 'POST /MediaRenderer/AVTransport/Control HTTP/1.1\r\n' +
      'Accept: application/json, text/plain, */*\r\n' +
      'Content-Type: text/xml; charset=utf8\r\n' +
      'SOAPAction: "urn:schemas-upnp-org:service:AVTransport:1#GetTransportInfo"\r\n' +
      'User-Agent: axios/0.19.0\r\n' +
      'Content-Length: 274\r\n' +
      'Host: 192.168.1.162:1400\r\n' +
      'Connection: close\r\n' +
      '\r\n',
    _keepAliveTimeout: 0,
    _onPendingData: {},
    agent: Agent {
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      defaultPort: 80,
      protocol: 'http:',
      options: [Object],
      requests: {},
      sockets: [Object],
      freeSockets: {},
      keepAliveMsecs: 1000,
      keepAlive: false,
      maxSockets: Infinity,
      maxFreeSockets: 256,
      scheduling: 'lifo',
      maxTotalSockets: Infinity,
      totalSocketCount: 48,
      [Symbol(kCapture)]: false
    },
    socketPath: undefined,
    method: 'POST',
    maxHeaderSize: undefined,
    insecureHTTPParser: undefined,
    path: '/MediaRenderer/AVTransport/Control',
    _ended: true,
    res: IncomingMessage {
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 3,
      _maxListeners: undefined,
      socket: [Socket],
      httpVersionMajor: 1,
      httpVersionMinor: 1,
      httpVersion: '1.1',
      complete: true,
      rawHeaders: [Array],
      rawTrailers: [],
      aborted: false,
      upgrade: false,
      url: '',
      method: null,
      statusCode: 405,
      statusMessage: 'Method Not Allowed',
      client: [Socket],
      _consuming: true,
      _dumped: false,
      req: [Circular *1],
      responseUrl: 'http://192.168.1.162:1400/MediaRenderer/AVTransport/Control',
      redirects: [],
      [Symbol(kCapture)]: false,
      [Symbol(kHeaders)]: [Object],
      [Symbol(kHeadersCount)]: 8,
      [Symbol(kTrailers)]: null,
      [Symbol(kTrailersCount)]: 0,
      [Symbol(RequestTimeout)]: undefined
    },
    aborted: false,
    timeoutCb: null,
    upgradeOrConnect: false,
    parser: null,
    maxHeadersCount: null,
    reusedSocket: false,
    host: '192.168.1.162',
    protocol: 'http:',
    _redirectable: Writable {
      _writableState: [WritableState],
      _events: [Object: null prototype],
      _eventsCount: 2,
      _maxListeners: undefined,
      _options: [Object],
      _redirectCount: 0,
      _redirects: [],
      _requestBodyLength: 274,
      _requestBodyBuffers: [],
      _onNativeResponse: [Function (anonymous)],
      _currentRequest: [Circular *1],
      _currentUrl: 'http://192.168.1.162:1400/MediaRenderer/AVTransport/Control',
      [Symbol(kCapture)]: false
    },
    [Symbol(kCapture)]: false,
    [Symbol(kNeedDrain)]: false,
    [Symbol(corked)]: 0,
    [Symbol(kOutHeaders)]: [Object: null prototype] {
      accept: [Array],
      'content-type': [Array],
      soapaction: [Array],
      'user-agent': [Array],
      'content-length': [Array],
      host: [Array]
    }
  },
  response: {
    status: 405,
    statusText: 'Method Not Allowed',
    headers: {
      allow: 'GET, HEAD',
      'content-type': 'text/html',
      server: 'Linux UPnP/1.0 Sonos/63.2-89260 (BR200)',
      connection: 'close'
    },
    config: {
      url: 'http://192.168.1.162:1400/MediaRenderer/AVTransport/Control',
      method: 'post',
      data: '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetTransportInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID></u:GetTransportInfo></s:Body></s:Envelope>',
      headers: [Object],
      transformRequest: [Array],
      transformResponse: [Array],
      timeout: 0,
      adapter: [Function: httpAdapter],
      xsrfCookieName: 'XSRF-TOKEN',
      xsrfHeaderName: 'X-XSRF-TOKEN',
      maxContentLength: -1,
      validateStatus: [Function: validateStatus]
    },
    request: <ref *1> ClientRequest {
      _events: [Object: null prototype],
      _eventsCount: 6,
      _maxListeners: undefined,
      outputData: [],
      outputSize: 0,
      writable: true,
      destroyed: false,
      _last: true,
      chunkedEncoding: false,
      shouldKeepAlive: false,
      _defaultKeepAlive: true,
      useChunkedEncodingByDefault: true,
      sendDate: false,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      _contentLength: null,
      _hasBody: true,
      _trailer: '',
      finished: true,
      _headerSent: true,
      _closed: false,
      socket: [Socket],
      _header: 'POST /MediaRenderer/AVTransport/Control HTTP/1.1\r\n' +
        'Accept: application/json, text/plain, */*\r\n' +
        'Content-Type: text/xml; charset=utf8\r\n' +
        'SOAPAction: "urn:schemas-upnp-org:service:AVTransport:1#GetTransportInfo"\r\n' +
        'User-Agent: axios/0.19.0\r\n' +
        'Content-Length: 274\r\n' +
        'Host: 192.168.1.162:1400\r\n' +
        'Connection: close\r\n' +
        '\r\n',
      _keepAliveTimeout: 0,
      _onPendingData: {},
      agent: [Agent],
      socketPath: undefined,
      method: 'POST',
      maxHeaderSize: undefined,
      insecureHTTPParser: undefined,
      path: '/MediaRenderer/AVTransport/Control',
      _ended: true,
      res: [IncomingMessage],
      aborted: false,
      timeoutCb: null,
      upgradeOrConnect: false,
      parser: null,
      maxHeadersCount: null,
      reusedSocket: false,
      host: '192.168.1.162',
      protocol: 'http:',
      _redirectable: [Writable],
      [Symbol(kCapture)]: false,
      [Symbol(kNeedDrain)]: false,
      [Symbol(corked)]: 0,
      [Symbol(kOutHeaders)]: [Object: null prototype]
    },
    data: '<HTML><HEAD><TITLE>Error 405</TITLE></HEAD><BODY><H1>Error 405</H1><P>Method Not Allowed</P></BODY></HTML>'
  },
  isAxiosError: true,
  toJSON: [Function (anonymous)]
}

A command is sent to the bridge which is not supported

Current Behavior

Ideally bridges should be ignored as they cannot be piloted, they are just here to manage the Sonos network

Possible Solution

I suggest filtering the bridges in the network in ZoneGroupTopology.prototype.AllZoneGroups

by returning return groups.filter(z => {return !z.ZoneGroupMember[0].hasOwnProperty("IsZoneBridge")})

Versions (and Environment)

Node version: v15.7.0 node-sonos version: 1.12.5 OS: MacOS, Raspberry PI OS

svrooij commented 3 years ago

Why would you control a bridge?

care to create a PR for this? Can the app using this library not just filter out the boost (with the possibly good filter method above).

pierrephi commented 3 years ago

Precisely there is no reason to control a bridge hence my suggestion to filter them out right from within node-sonos. In the interim i did filter it out upstream in the calling application. Happy to submit a PR if you confirm it makes sense and I am not missing another usecase somehow

Le sam. 19 juin 2021 à 22:11, Stephan van Rooij @.***> a écrit :

Why would you control a bridge?

care to create a PR for this? Can the app using this library not just filter out the boost (with the possibly good filter method above).

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/bencevans/node-sonos/issues/515#issuecomment-864457963, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABCACKD5SDW5GON32LNZNDTTT2YHANCNFSM46QFD6NA .

svrooij commented 3 years ago

It would be nice to filter out non-players in most cases. But you can still rename a boost for instance. So maybe optional parameter to filter them out.

pierrephi commented 3 years ago

yep that's why the proposal is not to filter on the name "Boost" but rather the specific property that is only present for Boost and the older brides hasOwnProperty("IsZoneBridge") I'll go ahead a submit a PR then

svrooij commented 3 years ago

I mean, the use case to return the boost is that you can control some things off it. Hiding them always might not be a good idea.

can you make the PR to the alpha branch?