mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
update support 80.0.0
This commit is contained in:
132
src/smapi.ts
132
src/smapi.ts
@@ -36,7 +36,9 @@ import {
|
||||
SmapiAuthTokens,
|
||||
SMAPI_FAULT_LOGIN_UNAUTHORIZED,
|
||||
ToSmapiFault,
|
||||
SmapiToken,
|
||||
} from "./smapi_auth";
|
||||
import { IncomingHttpHeaders } from "http2";
|
||||
|
||||
export const LOGIN_ROUTE = "/login";
|
||||
export const CREATE_REGISTRATION_ROUTE = "/registration/add";
|
||||
@@ -161,17 +163,20 @@ class SonosSoap {
|
||||
bonobUrl: URLBuilder;
|
||||
smapiAuthTokens: SmapiAuthTokens;
|
||||
clock: Clock;
|
||||
tokens: {[tokenKey:string]:SmapiToken};
|
||||
|
||||
constructor(
|
||||
bonobUrl: URLBuilder,
|
||||
linkCodes: LinkCodes,
|
||||
smapiAuthTokens: SmapiAuthTokens,
|
||||
clock: Clock
|
||||
|
||||
) {
|
||||
this.bonobUrl = bonobUrl;
|
||||
this.linkCodes = linkCodes;
|
||||
this.smapiAuthTokens = smapiAuthTokens;
|
||||
this.clock = clock;
|
||||
this.tokens = {};
|
||||
}
|
||||
|
||||
getAppLink(): GetAppLinkResult {
|
||||
@@ -233,6 +238,16 @@ class SonosSoap {
|
||||
};
|
||||
}
|
||||
}
|
||||
getCredentialsForToken(token: string): SmapiToken {
|
||||
return this.tokens[token]!;
|
||||
}
|
||||
associateCredentialsForToken(token: string, fullSmapiToken: SmapiToken, oldToken?:string) {
|
||||
logger.debug("Adding token: " + token + " " + JSON.stringify(fullSmapiToken));
|
||||
if(oldToken) {
|
||||
delete this.tokens[oldToken];
|
||||
}
|
||||
this.tokens[token] = fullSmapiToken;
|
||||
}
|
||||
}
|
||||
|
||||
export type ContainerType = "container" | "search" | "albumList";
|
||||
@@ -415,7 +430,49 @@ function bindSmapiSoapServiceToExpress(
|
||||
);
|
||||
};
|
||||
|
||||
const login = async (credentials?: Credentials) => {
|
||||
const swapToken = (expiredToken:string) => (newToken:SmapiToken) => {
|
||||
logger.debug("oldToken: "+expiredToken);
|
||||
logger.debug("newToken: "+JSON.stringify(newToken));
|
||||
sonosSoap.associateCredentialsForToken(newToken.token, newToken, expiredToken);
|
||||
return TE.right(newToken);
|
||||
}
|
||||
|
||||
const useHeaderIfPresent = (credentials?: Credentials, headers?: IncomingHttpHeaders) => {
|
||||
logger.debug("useHeaderIfPresent header", headers);
|
||||
|
||||
const headersProvidedWithToken = headers!==null && headers!== undefined && headers["authorization"];
|
||||
if(headersProvidedWithToken) {
|
||||
logger.debug("Will use authorization header");
|
||||
const bearer = headers["authorization"];
|
||||
const token = bearer?.split(" ")[1];
|
||||
if(token) {
|
||||
const credsForToken = sonosSoap.getCredentialsForToken(token);
|
||||
if(credsForToken==undefined) {
|
||||
logger.debug("No creds for "+JSON.stringify(token));
|
||||
} else {
|
||||
credentials = {
|
||||
...credentials!,
|
||||
loginToken: {
|
||||
...credentials?.loginToken!,
|
||||
token: credsForToken.token,
|
||||
key: credsForToken.key,
|
||||
}
|
||||
}
|
||||
logger.debug("Updated credentials to " + JSON.stringify(credentials));
|
||||
}
|
||||
}
|
||||
}
|
||||
return credentials;
|
||||
}
|
||||
|
||||
const login = async (credentials?: Credentials, headers?: IncomingHttpHeaders) => {
|
||||
|
||||
const credentialsProvidedWithoutAuthToken = credentials && credentials.loginToken.token==null;
|
||||
if(credentialsProvidedWithoutAuthToken) {
|
||||
credentials = useHeaderIfPresent(credentials, headers);
|
||||
console.log("headers", headers)
|
||||
console.log("credentials", credentials)
|
||||
}
|
||||
const authOrFail = pipe(
|
||||
auth(credentials),
|
||||
E.getOrElseW((fault) => fault)
|
||||
@@ -431,6 +488,7 @@ function bindSmapiSoapServiceToExpress(
|
||||
throw await pipe(
|
||||
musicService.refreshToken(authOrFail.expiredToken),
|
||||
TE.map((it) => smapiAuthTokens.issue(it.serviceToken)),
|
||||
TE.tap(swapToken(authOrFail.expiredToken)),
|
||||
TE.map((newToken) => ({
|
||||
Fault: {
|
||||
faultcode: "Client.TokenRefreshRequired",
|
||||
@@ -457,8 +515,16 @@ function bindSmapiSoapServiceToExpress(
|
||||
Sonos: {
|
||||
SonosSoap: {
|
||||
getAppLink: () => sonosSoap.getAppLink(),
|
||||
getDeviceAuthToken: ({ linkCode }: { linkCode: string }) =>
|
||||
sonosSoap.getDeviceAuthToken({ linkCode }),
|
||||
getDeviceAuthToken: ({ linkCode }: { linkCode: string }) =>{
|
||||
const deviceAuthTokenResult = sonosSoap.getDeviceAuthToken({ linkCode });
|
||||
const smapiToken:SmapiToken = {
|
||||
token: deviceAuthTokenResult.getDeviceAuthTokenResult.authToken,
|
||||
key: deviceAuthTokenResult.getDeviceAuthTokenResult.privateKey
|
||||
}
|
||||
|
||||
sonosSoap.associateCredentialsForToken(smapiToken.token, smapiToken);
|
||||
return deviceAuthTokenResult;
|
||||
},
|
||||
getLastUpdate: () => ({
|
||||
getLastUpdateResult: {
|
||||
autoRefreshEnabled: true,
|
||||
@@ -467,9 +533,11 @@ function bindSmapiSoapServiceToExpress(
|
||||
pollInterval: 60,
|
||||
},
|
||||
}),
|
||||
refreshAuthToken: async (_, _2, soapyHeaders: SoapyHeaders) => {
|
||||
refreshAuthToken: async (_, _2, soapyHeaders: SoapyHeaders,
|
||||
{ headers }: Pick<Request, "headers">) => {
|
||||
const creds = useHeaderIfPresent(soapyHeaders?.credentials, headers);
|
||||
const serviceToken = pipe(
|
||||
auth(soapyHeaders?.credentials),
|
||||
auth(creds),
|
||||
E.fold(
|
||||
(fault) =>
|
||||
isExpiredTokenError(fault)
|
||||
@@ -484,6 +552,7 @@ function bindSmapiSoapServiceToExpress(
|
||||
return pipe(
|
||||
musicService.refreshToken(serviceToken),
|
||||
TE.map((it) => smapiAuthTokens.issue(it.serviceToken)),
|
||||
TE.tap(swapToken(serviceToken)), // ignores the return value, like a tee or peek
|
||||
TE.map((it) => ({
|
||||
refreshAuthTokenResult: {
|
||||
authToken: it.token,
|
||||
@@ -498,9 +567,10 @@ function bindSmapiSoapServiceToExpress(
|
||||
getMediaURI: async (
|
||||
{ id }: { id: string },
|
||||
_,
|
||||
soapyHeaders: SoapyHeaders
|
||||
soapyHeaders: SoapyHeaders,
|
||||
{ headers }: Pick<Request, "headers">
|
||||
) =>
|
||||
login(soapyHeaders?.credentials)
|
||||
login(soapyHeaders?.credentials, headers)
|
||||
.then(splitId(id))
|
||||
.then(({ musicLibrary, credentials, type, typeId }) => {
|
||||
switch (type) {
|
||||
@@ -537,9 +607,10 @@ function bindSmapiSoapServiceToExpress(
|
||||
getMediaMetadata: async (
|
||||
{ id }: { id: string },
|
||||
_,
|
||||
soapyHeaders: SoapyHeaders
|
||||
soapyHeaders: SoapyHeaders,
|
||||
{ headers }: Pick<Request, "headers">
|
||||
) =>
|
||||
login(soapyHeaders?.credentials)
|
||||
login(soapyHeaders?.credentials, headers)
|
||||
.then(splitId(id))
|
||||
.then(async ({ musicLibrary, apiKey, type, typeId }) => {
|
||||
switch (type) {
|
||||
@@ -558,9 +629,10 @@ function bindSmapiSoapServiceToExpress(
|
||||
search: async (
|
||||
{ id, term }: { id: string; term: string },
|
||||
_,
|
||||
soapyHeaders: SoapyHeaders
|
||||
soapyHeaders: SoapyHeaders,
|
||||
{ headers }: Pick<Request, "headers">
|
||||
) =>
|
||||
login(soapyHeaders?.credentials)
|
||||
login(soapyHeaders?.credentials, headers)
|
||||
.then(splitId(id))
|
||||
.then(async ({ musicLibrary, apiKey }) => {
|
||||
switch (id) {
|
||||
@@ -603,9 +675,10 @@ function bindSmapiSoapServiceToExpress(
|
||||
}: // recursive,
|
||||
{ id: string; index: number; count: number; recursive: boolean },
|
||||
_,
|
||||
soapyHeaders: SoapyHeaders
|
||||
soapyHeaders: SoapyHeaders,
|
||||
{ headers }: Pick<Request, "headers">
|
||||
) =>
|
||||
login(soapyHeaders?.credentials)
|
||||
login(soapyHeaders?.credentials, headers)
|
||||
.then(splitId(id))
|
||||
.then(async ({ musicLibrary, apiKey, type, typeId }) => {
|
||||
const paging = { _index: index, _count: count };
|
||||
@@ -677,7 +750,7 @@ function bindSmapiSoapServiceToExpress(
|
||||
soapyHeaders: SoapyHeaders,
|
||||
{ headers }: Pick<Request, "headers">
|
||||
) =>
|
||||
login(soapyHeaders?.credentials)
|
||||
login(soapyHeaders?.credentials, headers)
|
||||
.then(splitId(id))
|
||||
.then(({ musicLibrary, apiKey, type, typeId }) => {
|
||||
const paging = { _index: index, _count: count };
|
||||
@@ -685,6 +758,7 @@ function bindSmapiSoapServiceToExpress(
|
||||
logger.debug(
|
||||
`Fetching metadata type=${type}, typeId=${typeId}, acceptLanguage=${acceptLanguage}`
|
||||
);
|
||||
console.log("getMetadata headers", headers)
|
||||
const lang = i8n(...asLANGs(acceptLanguage));
|
||||
|
||||
const albums = (q: AlbumQuery): Promise<GetMetadataResponse> =>
|
||||
@@ -1000,9 +1074,10 @@ function bindSmapiSoapServiceToExpress(
|
||||
createContainer: async (
|
||||
{ title, seedId }: { title: string; seedId: string | undefined },
|
||||
_,
|
||||
soapyHeaders: SoapyHeaders
|
||||
soapyHeaders: SoapyHeaders,
|
||||
{ headers }: Pick<Request, "headers">
|
||||
) =>
|
||||
login(soapyHeaders?.credentials)
|
||||
login(soapyHeaders?.credentials, headers)
|
||||
.then(({ musicLibrary }) =>
|
||||
musicLibrary
|
||||
.createPlaylist(title)
|
||||
@@ -1026,17 +1101,19 @@ function bindSmapiSoapServiceToExpress(
|
||||
deleteContainer: async (
|
||||
{ id }: { id: string },
|
||||
_,
|
||||
soapyHeaders: SoapyHeaders
|
||||
soapyHeaders: SoapyHeaders,
|
||||
{ headers }: Pick<Request, "headers">
|
||||
) =>
|
||||
login(soapyHeaders?.credentials)
|
||||
login(soapyHeaders?.credentials, headers)
|
||||
.then(({ musicLibrary }) => musicLibrary.deletePlaylist(id))
|
||||
.then((_) => ({ deleteContainerResult: {} })),
|
||||
addToContainer: async (
|
||||
{ id, parentId }: { id: string; parentId: string },
|
||||
_,
|
||||
soapyHeaders: SoapyHeaders
|
||||
soapyHeaders: SoapyHeaders,
|
||||
{ headers }: Pick<Request, "headers">
|
||||
) =>
|
||||
login(soapyHeaders?.credentials)
|
||||
login(soapyHeaders?.credentials, headers)
|
||||
.then(splitId(id))
|
||||
.then(({ musicLibrary, typeId }) =>
|
||||
musicLibrary.addToPlaylist(parentId.split(":")[1]!, typeId)
|
||||
@@ -1045,9 +1122,10 @@ function bindSmapiSoapServiceToExpress(
|
||||
removeFromContainer: async (
|
||||
{ id, indices }: { id: string; indices: string },
|
||||
_,
|
||||
soapyHeaders: SoapyHeaders
|
||||
soapyHeaders: SoapyHeaders,
|
||||
{ headers }: Pick<Request, "headers">
|
||||
) =>
|
||||
login(soapyHeaders?.credentials)
|
||||
login(soapyHeaders?.credentials, headers)
|
||||
.then(splitId(id))
|
||||
.then((it) => ({
|
||||
...it,
|
||||
@@ -1068,9 +1146,10 @@ function bindSmapiSoapServiceToExpress(
|
||||
rateItem: async (
|
||||
{ id, rating }: { id: string; rating: number },
|
||||
_,
|
||||
soapyHeaders: SoapyHeaders
|
||||
soapyHeaders: SoapyHeaders,
|
||||
{ headers }: Pick<Request, "headers">
|
||||
) =>
|
||||
login(soapyHeaders?.credentials)
|
||||
login(soapyHeaders?.credentials, headers)
|
||||
.then(splitId(id))
|
||||
.then(({ musicLibrary, typeId }) =>
|
||||
musicLibrary.rate(typeId, ratingFromInt(Math.abs(rating)))
|
||||
@@ -1080,9 +1159,10 @@ function bindSmapiSoapServiceToExpress(
|
||||
setPlayedSeconds: async (
|
||||
{ id, seconds }: { id: string; seconds: string },
|
||||
_,
|
||||
soapyHeaders: SoapyHeaders
|
||||
soapyHeaders: SoapyHeaders,
|
||||
{ headers }: Pick<Request, "headers">
|
||||
) =>
|
||||
login(soapyHeaders?.credentials)
|
||||
login(soapyHeaders?.credentials, headers)
|
||||
.then(splitId(id))
|
||||
.then(({ musicLibrary, type, typeId }) => {
|
||||
switch (type) {
|
||||
|
||||
Reference in New Issue
Block a user