From 302efd28781f4d8153461a2dfb9c2efeb026bfba Mon Sep 17 00:00:00 2001 From: simojenki Date: Mon, 8 Feb 2021 20:45:17 +1100 Subject: [PATCH] simple sonos smapi soap webservice registered --- package.json | 4 +- src/Sonoswsdl-1.19.4-20190411.142401-3.wsdl | 2066 +++++++++++++++++++ src/server.ts | 84 +- tests/bonob_client.ts | 33 + tests/tsconfig.json | 1 + tests/ws.test.ts | 54 + tsconfig.json | 2 +- yarn.lock | 73 +- 8 files changed, 2298 insertions(+), 19 deletions(-) create mode 100644 src/Sonoswsdl-1.19.4-20190411.142401-3.wsdl create mode 100644 tests/bonob_client.ts create mode 100644 tests/ws.test.ts diff --git a/package.json b/package.json index 8c15e8e..66f850f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "eta": "^1.12.1", "express": "^4.17.1", "node-html-parser": "^2.1.0", + "soap": "^0.36.0", "ts-md5": "^1.2.7", "typescript": "^4.1.3", "underscore":"^1.12.0", @@ -27,6 +28,7 @@ "@types/mocha": "^8.2.0", "@types/supertest": "^2.0.10", "chai": "^4.2.0", + "get-port": "^5.1.1", "jest": "^26.6.3", "nodemon": "^2.0.7", "supertest": "^6.1.3", @@ -38,6 +40,6 @@ "build": "tsc", "dev": "nodemon ./src/app.ts", "test": "jest", - "testw": "mocha --require ts-node/register --watch --watch-files tests/**/* tests/**/*.test.ts" + "client-test": "ts-node tests/bonob_client.ts" } } diff --git a/src/Sonoswsdl-1.19.4-20190411.142401-3.wsdl b/src/Sonoswsdl-1.19.4-20190411.142401-3.wsdl new file mode 100644 index 0000000..e584b0e --- /dev/null +++ b/src/Sonoswsdl-1.19.4-20190411.142401-3.wsdl @@ -0,0 +1,2066 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Extensibility point to add new context header information. + Please refer to Sonos documentation for details + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + It is a token that is specific to a device to allow that device to stream. + If a deviceSessionToken was returned in a previous call, this is provided here. There may be + some tracks that do not include one in the mediaUriResponse; this will not cause Sonos to + remove the previous deviceSessionToken. Sonos will send this element until a new one is returned, always. + This element can be used to avoid having to re-validate the cert by encoding that information. + It can provide a lower cost short cut for encoding session keys. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/server.ts b/src/server.ts index f46207c..767a785 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,6 +1,16 @@ import express, { Express } from "express"; import * as Eta from "eta"; +import { listen } from "soap"; +import { readFileSync } from "fs"; +import path from "path"; + import { Sonos, Service } from "./sonos"; +import logger from "./logger"; + +const WSDL_FILE = path.resolve( + __dirname, + "Sonoswsdl-1.19.4-20190411.142401-3.wsdl" +); function server(sonos: Sonos, bonobService: Service): Express { const app = express(); @@ -12,27 +22,69 @@ function server(sonos: Sonos, bonobService: Service): Express { app.set("views", "./web/views"); app.get("/", (_, res) => { - Promise.all([ - sonos.devices(), - sonos.services() - ]).then(([devices, services]) => { - const registeredBonobService = services.find(it => it.sid == bonobService.sid); - res.render("index", { - devices, - services, - bonobService, - registeredBonobService - }); - }) + Promise.all([sonos.devices(), sonos.services()]).then( + ([devices, services]) => { + const registeredBonobService = services.find( + (it) => it.sid == bonobService.sid + ); + res.render("index", { + devices, + services, + bonobService, + registeredBonobService, + }); + } + ); }); app.post("/register", (_, res) => { - sonos.register(bonobService).then(success => { - if(success) res.send("Yay") - else res.send("boo hoo") - }) + sonos.register(bonobService).then((success) => { + if (success) res.send("Yay"); + else res.send("boo hoo"); + }); }); + const sonosService = { + Sonos: { + SonosSoap: { + getSessionId: ({ + username + }: { + username: string; + password: string; + }) => { + return Promise.resolve({ + username, + sessionId: '123' + }); + }, + }, + }, + }; + + const x = listen( + app, + "/ws", + sonosService, + readFileSync(WSDL_FILE, "utf8") + ); + + x.log = (type, data) => { + switch (type) { + case "info": + logger.info({ data }); + break; + case "warn": + logger.warn({ data }); + break; + case "error": + logger.error({ data }); + break; + default: + logger.debug({ data }); + } + }; + return app; } diff --git a/tests/bonob_client.ts b/tests/bonob_client.ts new file mode 100644 index 0000000..47af65f --- /dev/null +++ b/tests/bonob_client.ts @@ -0,0 +1,33 @@ +import getPort from "get-port"; +import { createClientAsync } from "soap"; + +import sonos, { bonobService } from "../src/sonos"; +import server from "../src/server"; + +import logger from "../src/logger"; + +const bonob = bonobService("bonob-test", 247, "http://localhost:1234"); +const app = server(sonos("disabled"), bonob); + +getPort().then((port) => { + logger.debug(`Starting on port ${port}`); + app.listen(port); + + createClientAsync(`http://localhost:${port}/ws?wsdl`, { + endpoint: `http://localhost:${port}/ws`, + }).then((client) => { + client + .getSessionIdAsync( + { username: "bob", password: "foo" } + ) + .then( + ([{ username, sessionId }]: [ + { username: string; sessionId: string } + ]) => { + console.log(`${username} has sessionId=${sessionId}`); + } + ); + + console.log(`done`); + }); +}); diff --git a/tests/tsconfig.json b/tests/tsconfig.json index a0ecc74..2b329d4 100644 --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../tsconfig.json", "compilerOptions": { + "target": "es2019", "baseUrl": "./", "module": "commonjs", "experimentalDecorators": true, diff --git a/tests/ws.test.ts b/tests/ws.test.ts new file mode 100644 index 0000000..01589a0 --- /dev/null +++ b/tests/ws.test.ts @@ -0,0 +1,54 @@ +import request from "supertest"; +import makeServer from "../src/server"; +import { SONOS_DISABLED } from "../src/sonos"; + +import { aService } from "./builders"; + +import { createClientAsync } from "soap"; + +describe("ws", () => { + describe("can call getSessionId", () => { + it("should do something", async () => { + const server = makeServer(SONOS_DISABLED, aService()); + + const { username, sessionId } = await createClientAsync( + `http://localhost/ws?wsdl`, + { + endpoint: `http://localhost/ws`, + httpClient: { + request: ( + rurl: string, + data: any, + callback: (error: any, res?: any, body?: any) => any, + exheaders?: any + ) => { + const withoutHost = rurl.replace("http://localhost", ""); + const req = + data == null + ? request(server).get(withoutHost).send() + : request(server).post(withoutHost).send(data); + req + .set(exheaders || {}) + .then((response) => callback(null, response, response.text)) + .catch(callback); + }, + }, + } + ).then((client) => + client + .getSessionIdAsync({ username: "bob", password: "foo" }) + .then( + ([{ username, sessionId }]: [ + { username: string; sessionId: string } + ]) => ({ + username, + sessionId, + }) + ) + ); + + expect(username).toEqual("bob"); + expect(sessionId).toEqual("123"); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index cc3070b..97b5763 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "ES6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ "lib": ["es2019"], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ diff --git a/yarn.lock b/yarn.lock index 56010e3..d853287 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2015,6 +2015,11 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-port@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" + integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== + get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -2029,6 +2034,11 @@ get-stream@^5.0.0, get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.0.tgz#3e0012cb6827319da2706e601a1583e8629a6718" + integrity sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg== + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -2233,6 +2243,19 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +httpntlm@^1.5.2: + version "1.7.6" + resolved "https://registry.yarnpkg.com/httpntlm/-/httpntlm-1.7.6.tgz#6991e8352836007d67101b83db8ed0f915f906d0" + integrity sha1-aZHoNSg2AH1nEBuD247Q+RX5BtA= + dependencies: + httpreq ">=0.4.22" + underscore "~1.7.0" + +httpreq@>=0.4.22: + version "0.4.24" + resolved "https://registry.yarnpkg.com/httpreq/-/httpreq-0.4.24.tgz#4335ffd82cd969668a39465c929ac61d6393627f" + integrity sha1-QzX/2CzZaWaKOUZckprGHWOTYn8= + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -3868,7 +3891,7 @@ request-promise-native@^1.0.8: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.88.2: +request@>=2.9.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -3990,6 +4013,11 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" +sax@>=0.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + saxes@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" @@ -4151,6 +4179,21 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +soap@^0.36.0: + version "0.36.0" + resolved "https://registry.yarnpkg.com/soap/-/soap-0.36.0.tgz#80d1a55e89e048f175ae5538c288428d8c649445" + integrity sha512-MEFr2H7+a02fAA2FWyJE1B0xvDE8jiH5Ke6P1WKChHR9TGcJMK7kRAlPK/YR+NNO1pUs6W1FaIz4w8I9kuvybw== + dependencies: + debug "^4.1.1" + get-stream "^6.0.0" + httpntlm "^1.5.2" + lodash "^4.17.19" + request ">=2.9.0" + sax ">=0.6" + strip-bom "^3.0.0" + uuid "^8.3.0" + xml-crypto "^2.0.0" + source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -4332,6 +4375,11 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" @@ -4636,6 +4684,11 @@ underscore@^1.12.0: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.0.tgz#4814940551fc80587cef7840d1ebb0f16453be97" integrity sha512-21rQzss/XPMjolTiIezSu3JAjgagXKROtNrYFEOWK109qY1Uv2tVjPTZ1ci2HgvQDA16gHYSthQIJfB+XId/rQ== +underscore@~1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" + integrity sha1-a7rwh3UA02vjTsqlhODbn+8DUgk= + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -4900,6 +4953,14 @@ xdg-basedir@^4.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== +xml-crypto@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-2.0.0.tgz#54cd268ad9d31930afcf7092cbb664258ca9e826" + integrity sha512-/a04qr7RpONRZHOxROZ6iIHItdsQQjN3sj8lJkYDDss8tAkEaAs0VrFjb3tlhmS5snQru5lTs9/5ISSMdPDHlg== + dependencies: + xmldom "0.1.27" + xpath "0.0.27" + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" @@ -4910,6 +4971,16 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xmldom@0.1.27: + version "0.1.27" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" + integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk= + +xpath@0.0.27: + version "0.0.27" + resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92" + integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ== + y18n@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"