update support 80.0.0

This commit is contained in:
chxx
2025-09-14 23:52:37 +08:00
parent 2961b651d9
commit 419333399d

View File

@@ -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) {