npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@achingbrain/ssdp

v4.2.1

Published

Yet another SSDP implementation for node.js

Downloads

37,825

Readme

@achingbrain/ssdp

codecov CI

Yet another SSDP implementation for node.js

About

First, import the module, call the function and set up an error handler:

import ssdp from '@achingbrain/ssdp'

const bus = await ssdp()

// print error messages to the console
bus.on('error', console.error)

Example - Find a service

Pass a serviceType to the discover method - when services are found events will be emitted:

// this is the unique service name we are interested in:
const serviceType = 'urn:schemas-upnp-org:service:ContentDirectory:1'

for await (const service of bus.discover({ serviceType })) {
  // search for instances of a specific service
}

bus.on('service:discover', service => {
  // receive a notification about discovery of a service
})

bus.on('service:update', service => {
  // receive a notification when that service is updated - nb. this will only happen
  // after the service max-age is reached and if the service's device description
  // document has changed
})

Example - Find all services

Don't pass any options to the discover method (n.b. you will also receive protocol related events):

for await (const service of bus.discover()) {
  // receive a notification about all service types
}

Example - Advertise a service

// advertise a service

const advert = await bus.advertise({
  usn: 'urn:schemas-upnp-org:service:ContentDirectory:1',
  details: {
    URLBase: 'https://192.168.0.1:8001'
  }
})

// stop advertising a service
await advert.stop()

For full options, see lib/advertise/parse-options.js

Integrate with existing HTTP servers

By default when you create an advertisement an HTTP server is created to serve the details.xml document that describes your service. To use an existing server instead, do something like:

Example - Hapi

const advert = await bus.advertise({
  usn: 'urn:schemas-upnp-org:service:ContentDirectory:1',
  location: {
    udp4: 'http://192.168.0.1:8000/ssdp/details.xml'
  },
  details: {
    URLBase: 'https://192.168.0.1:8001'
  }
})

server.route({
  method: 'GET',
  path: '/ssdp/details.xml',
  handler: (request, reply) => {
    reply(advert.service.details())
      .type('text/xml')
  }
})

Example - Express

const advert = await bus.advertise({
  usn: 'urn:schemas-upnp-org:service:ContentDirectory:1',
  location: {
    udp4: 'http://192.168.0.1:8000/ssdp/details.xml'
  },
  details: {
    URLBase: 'https://192.168.0.1:8001'
  }
})

app.get('/ssdp/details.xml', async (request, response) => {
  response.set('Content-Type', 'text/xml')

  try {
    const details = await advert.service.details()
    response.send(details)
  } catch (err) {
    response.set('Content-Type', 'text/xml')
    response.send(err)
  }
})

Example - Shutting down gracefully

ssdp opens several ports to communicate with other devices on your network, to shut them down, do something like:

process.on('SIGINT',() => {
  // stop the server(s) from running - this will also send ssdp:byebye messages for all
  // advertised services however they'll only have been sent once the callback is
  // invoked so it won't work with process.on('exit') as you can only perform synchronous
  // operations there
  bus.stop(error => {
    process.exit(error ? 1 : 0)
  })
})

Full API and options

import ssdp from '@achingbrain/ssdp'

// all arguments are optional
var bus = ssdp({
  udn: 'unique-identifier', // defaults to a random UUID
  // a string to identify the server by
  signature: 'node.js/0.12.6 UPnP/1.1 @achingbrain/ssdp/1.0.0',
  retry {
    times: 5, // how many times to attempt joining the UDP multicast group
    interval: 5000 // how long to wait between attempts
  },
  // specify one or more sockets to listen on
  sockets: [{
    type: 'udp4', // or 'udp6'
    broadcast: {
      address: '239.255.255.250', // or 'FF02::C'
      port: 1900 // SSDP broadcast port
    },
    bind: {
      address: '0.0.0.0', // or '0:0:0:0:0:0:0:0'
      port: 1900
    },
    maxHops: 4 // how many network segments packets are allow to travel through (UDP TTL)
  }]
})
bus.on('error', console.error)

// this is the type of service we are interested in
var serviceType = 'urn:schemas-upnp-org:service:ContentDirectory:1'

// search for one type of service
for await (const service of bus.discover({ serviceType })) {

}

bus.on('service:discover', service => {
  // receive a notification when a service of the passed type is discovered
})

bus.on('service:update', service => {
  // receive a notification when that service is updated
})

// search for all types of service
for await (const service of bus.discover()) {

}

// advertise a service
const advert = await bus.advertise({
  usn: 'a-usn', // unique service name
  interval: 10000, // how often to broadcast service adverts in ms
  ttl: 1800000, // how long the advert is valid for in ms
  ipv4: true, // whether or not to broadcast the advert over IPv4
  ipv6: true, // whether or not to broadcast the advert over IPv6
  location: { // where the description document(s) are available - omit to have an http server automatically created
    udp4: 'http://192.168.0.1/details.xml', // where the description document is available over ipv4
    udp6: 'http://FE80::0202:B3FF:FE1E:8329/details.xml' // where the description document is available over ipv6
  },
  details: { // the contents of the description document
    specVersion: {
      major: 1,
      minor: 1
    },
    URLBase: 'http://example.com',
    device: {
      deviceType: 'a-usn',
      friendlyName: 'A friendly device name',
      manufacturer: 'Manufactuer name',
      manufacturerURL: 'http://example.com',
      modelDescription: 'A description of the device',
      modelName: 'A model name',
      modelNumber: 'A vendor specific model number',
      modelURL: 'http://example.com',
      serialNumber: 'A device specific serial number',
      UDN: 'unique-identifier' // should be the same as the bus USN
      presentationURL: 'index.html'
    }
  }
})

// stop advertising a service
advert.stop()

Device description document

During UPnP device discovery, clients can request a description of the various capabilities your service offers. To do this you can either store an xml document and set the location field of your advert to point at that document or have it automatically generated.

E.g., create a document, description.xml and put it on a server at http://server.com/path/to/description.xml:

<root xmlns="urn:schemas-upnp-org:device-1-0">
  <specVersion>
    <major>1</major>
    <minor>0</minor>
  </specVersion>
  <URLBase>http://192.168.1.41:80</URLBase>
  <device>
    <deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>
    <friendlyName>I am a light controller</friendlyName>
    <manufacturer>Royal Philips Electronics</manufacturer>
    <manufacturerURL>http://www.philips.com</manufacturerURL>
    <modelDescription>Philips hue Personal Wireless Lighting</modelDescription>
    <modelName>Philips hue bridge 2012</modelName>
    <modelNumber>23409823049823</modelNumber>
    <modelURL>http://www.meethue.com</modelURL>
    <serialNumber>asd09f8s90832</serialNumber>
    <UDN>uuid:2f402f80-da50-12321-9b23-2131298129</UDN>
    <presentationURL>index.html</presentationURL>
  </device>
</root>

Then create your advert:

bus.advertise({
  usn: 'urn:schemas-upnp-org:device:Basic:1',
  location: {
    udp4: 'http://192.168.1.40/path/to/description.xml'
  }
})

Alternatively provide an descriptor object and let this module do the heavy lifting (n.b. your object will be run through the xml2js Builder):

bus.advertise({
  usn: 'urn:schemas-upnp-org:device:Basic:1',
  details: {
   '$': {
      'xmlns': 'urn:schemas-upnp-org:device-1-0'
    },
    'specVersion': {
      'major': '1',
      'minor': '0'
    },
    'URLBase': 'http://192.168.1.41:80',
    'device': {
      'deviceType': 'urn:schemas-upnp-org:device:Basic:1',
      'friendlyName': 'I am a light controller',
      'manufacturer': 'Royal Philips Electronics',
      'manufacturerURL': 'http://www.philips.com',
      'modelDescription': 'Philips hue Personal Wireless Lighting',
      'modelName': 'Philips hue bridge 2012',
      'modelNumber': '23409823049823',
      'modelURL': 'http://www.meethue.com',
      'serialNumber': 'asd09f8s90832',
      'UDN': 'uuid:2f402f80-da50-12321-9b23-2131298129',
      'presentationURL': 'index.html'
    }
  }
})

A random high port will be chosen, a http server will listen on that port and serve the descriptor and the LOCATION header will be set appropriately in all ssdp messages.

The server will be shut down when you call advert.stop.

I want to see all protocol messages

No problem, try this:

bus.on('transport:outgoing-message', (socket, message, remote) => {
  console.info('-> Outgoing to %s:%s via %s', remote.address, remote.port, socket.type)
  console.info(message.toString('utf8'))
})
bus.on('transport:incoming-message', (message, remote) => {
  console.info('<- Incoming from %s:%s', remote.address, remote.port)
  console.info(message.toString('utf8'))
})

Alternatively see test/fixtures/all.js

References

Install

$ npm i @achingbrain/ssdp

API Docs

License

Licensed under either of

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.