2023-04-23 02:09:25 -07:00
|
|
|
import { AxiosHeaders } from 'axios';
|
2024-07-03 15:51:41 +03:00
|
|
|
import isElectron from 'is-electron';
|
2024-04-22 19:44:10 -07:00
|
|
|
import semverCoerce from 'semver/functions/coerce';
|
|
|
|
|
import semverGte from 'semver/functions/gte';
|
2023-04-23 02:09:25 -07:00
|
|
|
import { z } from 'zod';
|
2025-05-18 14:03:18 -07:00
|
|
|
|
|
|
|
|
import { ServerFeature } from '/@/renderer/api/features-types';
|
|
|
|
|
import { ServerListItem } from '/@/renderer/api/types';
|
2024-09-01 08:26:30 -07:00
|
|
|
import { toast } from '/@/renderer/components/toast';
|
2023-06-13 17:52:51 +00:00
|
|
|
import { useAuthStore } from '/@/renderer/store';
|
2023-04-23 02:09:25 -07:00
|
|
|
|
|
|
|
|
// Since ts-rest client returns a strict response type, we need to add the headers to the body object
|
|
|
|
|
export const resultWithHeaders = <ItemType extends z.ZodTypeAny>(itemSchema: ItemType) => {
|
2023-07-01 19:10:05 -07:00
|
|
|
return z.object({
|
|
|
|
|
data: itemSchema,
|
|
|
|
|
headers: z.instanceof(AxiosHeaders),
|
|
|
|
|
});
|
2023-04-23 02:09:25 -07:00
|
|
|
};
|
2023-04-24 01:21:29 -07:00
|
|
|
|
|
|
|
|
export const resultSubsonicBaseResponse = <ItemType extends z.ZodRawShape>(
|
2023-07-01 19:10:05 -07:00
|
|
|
itemSchema: ItemType,
|
2023-04-24 01:21:29 -07:00
|
|
|
) => {
|
2023-07-01 19:10:05 -07:00
|
|
|
return z.object({
|
|
|
|
|
'subsonic-response': z
|
|
|
|
|
.object({
|
|
|
|
|
status: z.string(),
|
|
|
|
|
version: z.string(),
|
|
|
|
|
})
|
|
|
|
|
.extend(itemSchema),
|
|
|
|
|
});
|
2023-04-24 01:21:29 -07:00
|
|
|
};
|
2023-06-13 17:52:51 +00:00
|
|
|
|
2025-05-18 14:03:18 -07:00
|
|
|
export const authenticationFailure = (currentServer: null | ServerListItem) => {
|
2023-07-01 19:10:05 -07:00
|
|
|
toast.error({
|
|
|
|
|
message: 'Your session has expired.',
|
|
|
|
|
});
|
2023-06-13 17:52:51 +00:00
|
|
|
|
2023-07-01 19:10:05 -07:00
|
|
|
if (currentServer) {
|
|
|
|
|
const serverId = currentServer.id;
|
|
|
|
|
const token = currentServer.ndCredential;
|
|
|
|
|
console.log(`token is expired: ${token}`);
|
|
|
|
|
useAuthStore.getState().actions.updateServer(serverId, { ndCredential: undefined });
|
|
|
|
|
useAuthStore.getState().actions.setCurrentServer(null);
|
|
|
|
|
}
|
2023-06-13 17:52:51 +00:00
|
|
|
};
|
2024-02-03 22:47:57 -08:00
|
|
|
|
2025-05-18 14:03:18 -07:00
|
|
|
export const hasFeature = (server: null | ServerListItem, feature: ServerFeature): boolean => {
|
2024-02-03 22:47:57 -08:00
|
|
|
if (!server || !server.features) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-18 09:23:52 -07:00
|
|
|
return (server.features[feature]?.length || 0) > 0;
|
2024-02-03 22:47:57 -08:00
|
|
|
};
|
2024-04-14 21:58:25 -07:00
|
|
|
|
2024-04-22 19:44:10 -07:00
|
|
|
export type VersionInfo = ReadonlyArray<[string, Record<string, readonly number[]>]>;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the available server features given the version string.
|
|
|
|
|
* @param versionInfo a list, in DECREASING VERSION order, of the features supported by the server.
|
|
|
|
|
* The first version match will automatically consider the rest matched.
|
|
|
|
|
* @example
|
|
|
|
|
* ```
|
|
|
|
|
* // The CORRECT way to order
|
|
|
|
|
* const VERSION_INFO: VersionInfo = [
|
|
|
|
|
* ['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }],
|
|
|
|
|
* ['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }],
|
|
|
|
|
* ];
|
|
|
|
|
* // INCORRECT way to order
|
|
|
|
|
* const VERSION_INFO: VersionInfo = [
|
|
|
|
|
* ['0.48.0', { [ServerFeature.PLAYLISTS_SMART]: [1] }],
|
|
|
|
|
* ['0.49.3', { [ServerFeature.SHARING_ALBUM_SONG]: [1] }],
|
|
|
|
|
* ];
|
|
|
|
|
* ```
|
|
|
|
|
* @param version the version string (SemVer)
|
|
|
|
|
* @returns a Record containing the matched features (if any) and their versions
|
|
|
|
|
*/
|
|
|
|
|
export const getFeatures = (
|
|
|
|
|
versionInfo: VersionInfo,
|
|
|
|
|
version: string,
|
|
|
|
|
): Record<string, number[]> => {
|
|
|
|
|
const cleanVersion = semverCoerce(version);
|
|
|
|
|
const features: Record<string, number[]> = {};
|
|
|
|
|
let matched = cleanVersion === null;
|
|
|
|
|
|
|
|
|
|
for (const [version, supportedFeatures] of versionInfo) {
|
|
|
|
|
if (!matched) {
|
|
|
|
|
matched = semverGte(cleanVersion!, version);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (matched) {
|
|
|
|
|
for (const [feature, feat] of Object.entries(supportedFeatures)) {
|
|
|
|
|
if (feature in features) {
|
|
|
|
|
features[feature].push(...feat);
|
|
|
|
|
} else {
|
|
|
|
|
features[feature] = [...feat];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return features;
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-03 15:51:41 +03:00
|
|
|
export const getClientType = (): string => {
|
|
|
|
|
if (isElectron()) {
|
|
|
|
|
return 'Desktop Client';
|
|
|
|
|
}
|
|
|
|
|
const agent = navigator.userAgent;
|
|
|
|
|
switch (true) {
|
|
|
|
|
case agent.toLowerCase().indexOf('edge') > -1:
|
|
|
|
|
return 'Microsoft Edge';
|
|
|
|
|
case agent.toLowerCase().indexOf('edg/') > -1:
|
|
|
|
|
return 'Edge Chromium'; // Match also / to avoid matching for the older Edge
|
|
|
|
|
case agent.toLowerCase().indexOf('opr') > -1:
|
|
|
|
|
return 'Opera';
|
|
|
|
|
case agent.toLowerCase().indexOf('chrome') > -1:
|
|
|
|
|
return 'Chrome';
|
|
|
|
|
case agent.toLowerCase().indexOf('trident') > -1:
|
|
|
|
|
return 'Internet Explorer';
|
|
|
|
|
case agent.toLowerCase().indexOf('firefox') > -1:
|
|
|
|
|
return 'Firefox';
|
|
|
|
|
case agent.toLowerCase().indexOf('safari') > -1:
|
|
|
|
|
return 'Safari';
|
|
|
|
|
default:
|
|
|
|
|
return 'PC';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-14 21:58:25 -07:00
|
|
|
export const SEPARATOR_STRING = ' · ';
|