mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Part of AppLink login process
This commit is contained in:
@@ -1,16 +1,19 @@
|
||||
import sonos, { bonobService } from "./sonos";
|
||||
import server from "./server";
|
||||
import logger from "./logger";
|
||||
import { Navidrome } from './music_service';
|
||||
|
||||
const PORT = process.env["BONOB_PORT"] || 4534;
|
||||
|
||||
const PORT = +(process.env["BONOB_PORT"] || 4534);
|
||||
const WEB_ADDRESS = process.env["BONOB_WEB_ADDRESS"] || `http://localhost:${PORT}`;
|
||||
|
||||
const bonob = bonobService(
|
||||
process.env["BONOB_SONOS_SERVICE_NAME"] || "bonob",
|
||||
Number(process.env["BONOS_SONOS_SERVICE_ID"] || "246"),
|
||||
WEB_ADDRESS
|
||||
WEB_ADDRESS,
|
||||
'AppLink'
|
||||
);
|
||||
const app = server(sonos(process.env["BONOB_SONOS_SEED_HOST"]), bonob);
|
||||
const app = server(sonos(process.env["BONOB_SONOS_SEED_HOST"]), bonob, WEB_ADDRESS, new Navidrome());
|
||||
|
||||
app.listen(PORT, () => {
|
||||
logger.info(`Listening on ${PORT} available @ ${WEB_ADDRESS}`);
|
||||
|
||||
20
src/link_codes.ts
Normal file
20
src/link_codes.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
export interface LinkCodes {
|
||||
mint(): string
|
||||
clear(): any
|
||||
count(): Number
|
||||
}
|
||||
|
||||
export class InMemoryLinkCodes implements LinkCodes {
|
||||
linkCodes: Record<string, string> = {}
|
||||
|
||||
mint() {
|
||||
const linkCode = uuid();
|
||||
this.linkCodes[linkCode] = ""
|
||||
return linkCode
|
||||
}
|
||||
clear = () => { this.linkCodes = {} }
|
||||
count = () => Object.keys(this.linkCodes).length
|
||||
}
|
||||
|
||||
@@ -6,7 +6,17 @@ const navidrome = process.env["BONOB_NAVIDROME_URL"];
|
||||
const u = process.env["BONOB_USER"];
|
||||
const t = Md5.hashStr(`${process.env["BONOB_PASSWORD"]}${s}`);
|
||||
|
||||
export class Navidrome {
|
||||
export type Credentials = { username: string, password: string }
|
||||
|
||||
export interface MusicService {
|
||||
login(credentials: Credentials): boolean
|
||||
}
|
||||
|
||||
export class Navidrome implements MusicService {
|
||||
login(_: Credentials) {
|
||||
return false
|
||||
}
|
||||
|
||||
ping = (): Promise<boolean> =>
|
||||
axios
|
||||
.get(
|
||||
@@ -18,3 +28,6 @@ export class Navidrome {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,17 @@ import * as Eta from "eta";
|
||||
import { listen } from "soap";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import morgan from "morgan";
|
||||
|
||||
import { Sonos, Service } from "./sonos";
|
||||
import {
|
||||
Sonos,
|
||||
Service,
|
||||
SOAP_PATH,
|
||||
STRINGS_PATH,
|
||||
PRESENTATION_MAP_PATH,
|
||||
} from "./sonos";
|
||||
import { LinkCodes, InMemoryLinkCodes } from './link_codes'
|
||||
import { MusicService } from './music_service'
|
||||
import logger from "./logger";
|
||||
|
||||
const WSDL_FILE = path.resolve(
|
||||
@@ -12,9 +21,18 @@ const WSDL_FILE = path.resolve(
|
||||
"Sonoswsdl-1.19.4-20190411.142401-3.wsdl"
|
||||
);
|
||||
|
||||
function server(sonos: Sonos, bonobService: Service): Express {
|
||||
function server(
|
||||
sonos: Sonos,
|
||||
bonobService: Service,
|
||||
webAddress: string | "http://localhost:1234",
|
||||
musicService: MusicService,
|
||||
linkCodes: LinkCodes = new InMemoryLinkCodes()
|
||||
): Express {
|
||||
const app = express();
|
||||
|
||||
app.use(morgan("combined"));
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
|
||||
app.use(express.static("./web/public"));
|
||||
app.engine("eta", Eta.renderFile);
|
||||
|
||||
@@ -44,18 +62,62 @@ function server(sonos: Sonos, bonobService: Service): Express {
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/login", (req, res) => {
|
||||
res.render("login", {
|
||||
bonobService,
|
||||
linkCode: req.query.linkCode
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/login", (req, res) => {
|
||||
const canLogIn = musicService.login({ username: req.body.username, password: req.body.password})
|
||||
if(canLogIn)
|
||||
res.send("ok")
|
||||
else
|
||||
res.send("boo")
|
||||
});
|
||||
|
||||
app.get(STRINGS_PATH, (_, res) => {
|
||||
res.type("application/xml").send(`<?xml version="1.0" encoding="utf-8" ?>
|
||||
<stringtables xmlns="http://sonos.com/sonosapi">
|
||||
<stringtable rev="1" xml:lang="en-US">
|
||||
<string stringId="AppLinkMessage">Linking sonos with bonob</string>
|
||||
</stringtable>
|
||||
</stringtables>
|
||||
`);
|
||||
});
|
||||
|
||||
app.get(PRESENTATION_MAP_PATH, (_, res) => {
|
||||
res.send("");
|
||||
});
|
||||
|
||||
const sonosService = {
|
||||
Sonos: {
|
||||
SonosSoap: {
|
||||
getAppLink: () => {
|
||||
const linkCode = linkCodes.mint();
|
||||
return {
|
||||
getAppLinkResult: {
|
||||
authorizeAccount: {
|
||||
appUrlStringId: 'AppLinkMessage',
|
||||
deviceLink: {
|
||||
regUrl: `${webAddress}/login?linkCode=${linkCode}`,
|
||||
linkCode: linkCode,
|
||||
showLinkCode: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
getSessionId: ({
|
||||
username
|
||||
username,
|
||||
}: {
|
||||
username: string;
|
||||
password: string;
|
||||
}) => {
|
||||
return Promise.resolve({
|
||||
username,
|
||||
sessionId: '123'
|
||||
sessionId: "123",
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -64,7 +126,7 @@ function server(sonos: Sonos, bonobService: Service): Express {
|
||||
|
||||
const x = listen(
|
||||
app,
|
||||
"/ws",
|
||||
SOAP_PATH,
|
||||
sonosService,
|
||||
readFileSync(WSDL_FILE, "utf8")
|
||||
);
|
||||
@@ -72,16 +134,16 @@ function server(sonos: Sonos, bonobService: Service): Express {
|
||||
x.log = (type, data) => {
|
||||
switch (type) {
|
||||
case "info":
|
||||
logger.info({ data });
|
||||
logger.info({ level:"info", data });
|
||||
break;
|
||||
case "warn":
|
||||
logger.warn({ data });
|
||||
logger.warn({ level:"warn", data });
|
||||
break;
|
||||
case "error":
|
||||
logger.error({ data });
|
||||
logger.error({ level:"error", data });
|
||||
break;
|
||||
default:
|
||||
logger.debug({ data });
|
||||
logger.debug({ level:"debug", data });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
2
src/smapi.ts
Normal file
2
src/smapi.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
export type GetAppLinkResult = { getAppLinkResult: { authorizeAccount: { appUrlStringId: string, deviceLink: { regUrl: string, linkCode:string,showLinkCode:boolean }} } }
|
||||
23
src/sonos.ts
23
src/sonos.ts
@@ -4,6 +4,11 @@ import { parse } from "node-html-parser";
|
||||
import { MusicService } from "@svrooij/sonos/lib/services";
|
||||
import { head } from "underscore";
|
||||
import logger from "./logger";
|
||||
import STRINGS from './strings';
|
||||
|
||||
export const SOAP_PATH = "/ws/sonos";
|
||||
export const STRINGS_PATH = "/sonos/strings.xml";
|
||||
export const PRESENTATION_MAP_PATH = "/sonos/presentationMap.xml";
|
||||
|
||||
export type Device = {
|
||||
name: string;
|
||||
@@ -20,7 +25,7 @@ export type Service = {
|
||||
strings: { uri?: string; version?: string };
|
||||
presentation: { uri?: string; version?: string };
|
||||
pollInterval?: number;
|
||||
authType: string;
|
||||
authType: 'Anonymous' | 'AppLink' | 'DeviceLink' | 'UserId';
|
||||
};
|
||||
|
||||
const stripTailingSlash = (url: string) =>
|
||||
@@ -29,22 +34,23 @@ const stripTailingSlash = (url: string) =>
|
||||
export const bonobService = (
|
||||
name: string,
|
||||
sid: number,
|
||||
bonobRoot: string
|
||||
bonobRoot: string,
|
||||
authType: 'Anonymous' | 'AppLink' | 'DeviceLink' | 'UserId' = 'AppLink'
|
||||
): Service => ({
|
||||
name,
|
||||
sid,
|
||||
uri: `${stripTailingSlash(bonobRoot)}/ws/sonos`,
|
||||
secureUri: `${stripTailingSlash(bonobRoot)}/ws/sonos`,
|
||||
uri: `${stripTailingSlash(bonobRoot)}${SOAP_PATH}`,
|
||||
secureUri: `${stripTailingSlash(bonobRoot)}${SOAP_PATH}`,
|
||||
strings: {
|
||||
uri: `${stripTailingSlash(bonobRoot)}/sonos/strings.xml`,
|
||||
version: "1",
|
||||
uri: `${stripTailingSlash(bonobRoot)}${STRINGS_PATH}`,
|
||||
version: STRINGS.version,
|
||||
},
|
||||
presentation: {
|
||||
uri: `${stripTailingSlash(bonobRoot)}/sonos/presentationMap.xml`,
|
||||
uri: `${stripTailingSlash(bonobRoot)}${PRESENTATION_MAP_PATH}`,
|
||||
version: "1",
|
||||
},
|
||||
pollInterval: 1200,
|
||||
authType: "Anonymous",
|
||||
authType,
|
||||
});
|
||||
|
||||
export interface Sonos {
|
||||
@@ -119,7 +125,6 @@ export function autoDiscoverySonos(sonosSeedHost?: string): Sonos {
|
||||
return setupDiscovery(manager, sonosSeedHost)
|
||||
.then((success) => {
|
||||
if (success) {
|
||||
console.log("had success");
|
||||
return manager.Devices;
|
||||
} else {
|
||||
logger.warn("Didn't find any sonos devices!");
|
||||
|
||||
10
src/strings.ts
Normal file
10
src/strings.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
|
||||
const STRINGS = {
|
||||
version: "1",
|
||||
values: {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
|
||||
export default STRINGS;
|
||||
Reference in New Issue
Block a user