Url presets (#807)

This commit is contained in:
Carlos Valente
2024-03-08 23:10:43 +01:00
committed by GitHub
parent 10d7273ec0
commit ad71793cf3
54 changed files with 544 additions and 1109 deletions
-21
View File
@@ -1,21 +0,0 @@
import axios from 'axios';
import { Alias } from 'ontime-types';
import { apiEntryUrl } from './constants';
const aliasesPath = `${apiEntryUrl}/aliases`;
/**
* HTTP request to retrieve aliases
*/
export async function getAliases(): Promise<Alias[]> {
const res = await axios.get(aliasesPath);
return res.data;
}
/**
* HTTP request to mutate aliases
*/
export async function postAliases(data: Alias[]): Promise<Alias[]> {
return axios.post(aliasesPath, data);
}
+2 -2
View File
@@ -1,7 +1,7 @@
// keys in tanstack store
export const ALIASES = ['aliases'];
export const APP_INFO = ['appinfo'];
export const APP_SETTINGS = ['appSettings'];
export const CUSTOM_FIELDS = ['customFields'];
export const HTTP_SETTINGS = ['httpSettings'];
export const OSC_SETTINGS = ['oscSettings'];
export const PROJECT_DATA = ['project'];
@@ -9,7 +9,7 @@ export const PROJECT_LIST = ['projectList'];
export const RUNDOWN = ['rundown'];
export const RUNTIME = ['runtimeStore'];
export const SHEET_STATE = ['sheetState'];
export const CUSTOM_FIELDS = ['customFields'];
export const URL_PRESETS = ['urlpresets'];
export const VIEW_SETTINGS = ['viewSettings'];
// resolve location
+21
View File
@@ -0,0 +1,21 @@
import axios from 'axios';
import { URLPreset } from 'ontime-types';
import { apiEntryUrl } from './constants';
const urlPresetsPath = `${apiEntryUrl}/url-presets`;
/**
* HTTP request to retrieve aliases
*/
export async function getUrlPresets(): Promise<URLPreset[]> {
const res = await axios.get(urlPresetsPath);
return res.data;
}
/**
* HTTP request to mutate aliases
*/
export async function postUrlPresets(data: URLPreset[]): Promise<URLPreset[]> {
return axios.post(urlPresetsPath, data);
}
@@ -1,19 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
import { getAliases } from '../api/aliases';
import { ALIASES } from '../api/constants';
export default function useAliases() {
const { data, status, isFetching, isError, refetch } = useQuery({
queryKey: ALIASES,
queryFn: getAliases,
placeholderData: [],
retry: 5,
retryDelay: (attempt) => attempt * 2500,
refetchInterval: queryRefetchIntervalSlow,
networkMode: 'always',
});
return { data, status, isFetching, isError, refetch };
}
@@ -0,0 +1,19 @@
import { useQuery } from '@tanstack/react-query';
import { queryRefetchIntervalSlow } from '../../ontimeConfig';
import { URL_PRESETS } from '../api/constants';
import { getUrlPresets } from '../api/urlPresets';
export default function useUrlPresets() {
const { data, status, isError, refetch } = useQuery({
queryKey: URL_PRESETS,
queryFn: getUrlPresets,
placeholderData: [],
retry: 5,
retryDelay: (attempt) => attempt * 2500,
refetchInterval: queryRefetchIntervalSlow,
networkMode: 'always',
});
return { data: data ?? [], status, isError, refetch };
}
@@ -1,8 +1,8 @@
import { resolvePath } from 'react-router-dom';
import { generateURLFromAlias, getAliasRoute, validateAlias } from '../aliases';
import { generateUrlFromPreset, getRouteFromPreset, validateUrlPresetPath } from '../urlPresets';
describe('An alias fails if incorrect', () => {
describe('A preset fails if incorrect', () => {
const testsToFail = [
// no empty
'',
@@ -21,11 +21,11 @@ describe('An alias fails if incorrect', () => {
testsToFail.forEach((t) =>
it(`${t}`, () => {
expect(validateAlias(t).status).toBeFalsy();
expect(validateUrlPresetPath(t).isValid).toBeFalsy();
}),
);
});
describe('generateURLFromAlias and getAliasRoute function', () => {
describe('generateUrlFromPreset and getRouteFromPreset function', () => {
test('generate the expected url from an alias', () => {
const testData = [
{
@@ -41,10 +41,10 @@ describe('generateURLFromAlias and getAliasRoute function', () => {
},
];
expect(generateURLFromAlias(testData[0])).toStrictEqual(expected[0].url);
expect(generateUrlFromPreset(testData[0])).toStrictEqual(expected[0].url);
});
test('generate the url to redirect to when the current URL is just the alias', () => {
const aliases = [
const presets = [
{
enabled: true,
alias: 'demopage',
@@ -52,7 +52,7 @@ describe('generateURLFromAlias and getAliasRoute function', () => {
},
];
// let current location be the alias
const location = resolvePath(aliases[0].alias);
const location = resolvePath(presets[0].alias);
const expected = [
{
@@ -60,10 +60,10 @@ describe('generateURLFromAlias and getAliasRoute function', () => {
},
];
expect(getAliasRoute(location, aliases, null)).toStrictEqual(expected[0].url);
expect(getRouteFromPreset(location, presets, null)).toStrictEqual(expected[0].url);
});
test('generate the url to redirect to when the current URL the same url but with a change of params', () => {
const aliases = [
const presets = [
{
enabled: true,
alias: 'demopage',
@@ -71,22 +71,22 @@ describe('generateURLFromAlias and getAliasRoute function', () => {
},
];
// let current location be the actual url with alias attached to it
const location = resolvePath(aliases[0].pathAndParams);
const location = resolvePath(presets[0].pathAndParams);
const urlSearchParams = new URLSearchParams(location.search);
urlSearchParams.append('alias', aliases[0].alias); //
urlSearchParams.append('alias', presets[0].alias); //
// update current alias with extra param
aliases[0].pathAndParams += '&eventId=674';
presets[0].pathAndParams += '&eventId=674';
const expected = [
{
url: '/timer?user=guest&eventId=674&alias=demopage',
},
];
expect(getAliasRoute(location, aliases, urlSearchParams)).toStrictEqual(expected[0].url);
expect(getRouteFromPreset(location, presets, urlSearchParams)).toStrictEqual(expected[0].url);
});
test('generate no url to redirect to when the current URL the same url', () => {
const aliases = [
const presets = [
{
enabled: true,
alias: 'demopage',
@@ -94,10 +94,10 @@ describe('generateURLFromAlias and getAliasRoute function', () => {
},
];
// let current location be the actual url with alias attached to it
const location = resolvePath(aliases[0].pathAndParams);
const location = resolvePath(presets[0].pathAndParams);
const urlSearchParams = new URLSearchParams(location.search);
urlSearchParams.append('alias', aliases[0].alias); //
urlSearchParams.append('alias', presets[0].alias); //
expect(getAliasRoute(location, aliases, urlSearchParams)).toBeNull();
expect(getRouteFromPreset(location, presets, urlSearchParams)).toBeNull();
});
});
-77
View File
@@ -1,77 +0,0 @@
import isEqual from 'react-fast-compare';
import { Location, resolvePath } from 'react-router-dom';
import { Alias } from 'ontime-types';
/**
* Validates an alias against defined parameters
* @param {string} alias
* @returns {{message: string, status: boolean}}
*/
export const validateAlias = (alias: string) => {
const valid = { status: true, message: 'ok' };
if (alias === '' || alias == null) {
// cannot be empty
valid.status = false;
valid.message = 'should not be empty';
} else if (alias.includes('http') || alias.includes('https') || alias.includes('www')) {
// cannot contain http, https or www
valid.status = false;
valid.message = 'should not include http, https, www';
} else if (alias.includes('127.0.0.1') || alias.includes('localhost') || alias.includes('0.0.0.0')) {
// aliases cannot contain hostname
valid.status = false;
valid.message = 'should not include hostname';
} else if (alias.includes('editor')) {
// no editor
valid.status = false;
valid.message = 'No aliases to editor page allowed';
}
return valid;
};
/**
* Gets the URL to send an alias to
* @param location
* @param data
* @param searchParams
*/
export const getAliasRoute = (location: Location, data: Alias[], searchParams: URLSearchParams) => {
const currentURL = location.pathname.substring(1);
// we need to check if the whole url here is an alias, so we can redirect
const foundAlias = data.filter((d) => d.alias === currentURL && d.enabled)[0];
if (foundAlias) {
return generateURLFromAlias(foundAlias);
}
const aliasOnPage = searchParams.get('alias');
for (const d of data) {
if (aliasOnPage) {
// if the alias fits the alias on this page, but the URL is different, we redirect user to the new URL
// if we have the same alias and its enabled and its not empty
if (d.alias !== '' && d.enabled && d.alias === aliasOnPage) {
const newAliasPath = resolvePath(d.pathAndParams);
const urlParams = new URLSearchParams(newAliasPath.search);
urlParams.set('alias', d.alias);
// we confirm either the url parameters does not match or the url path doesnt
if (!isEqual(urlParams, searchParams) || newAliasPath.pathname !== location.pathname) {
// we then redirect to the alias route, since the view listening to this alias has an outdated URL
return `${newAliasPath.pathname}?${urlParams}`;
}
}
}
}
return null;
};
/**
* Generate URL from an alias
* @param aliasData
*/
export const generateURLFromAlias = (aliasData: Alias) => {
const newAliasPath = resolvePath(aliasData.pathAndParams);
const urlParams = new URLSearchParams(newAliasPath.search);
urlParams.set('alias', aliasData.alias);
return `${newAliasPath.pathname}?${urlParams}`;
};
@@ -0,0 +1,76 @@
import isEqual from 'react-fast-compare';
import { Location, resolvePath } from 'react-router-dom';
import { URLPreset } from 'ontime-types';
/**
* Validates a preset against defined parameters
* @param {string} preset
* @returns {{message: string, isValid: boolean}}
*/
export const validateUrlPresetPath = (preset: string): { message: string; isValid: boolean } => {
if (preset === '' || preset == null) {
return { isValid: false, message: 'Path cannot be empty' };
}
if (preset.includes('http') || preset.includes('https') || preset.includes('www')) {
return { isValid: false, message: 'Path should not include http, https, www' };
}
if (preset.includes('127.0.0.1') || preset.includes('localhost') || preset.includes('0.0.0.0')) {
return { isValid: false, message: 'Path should not include hostname' };
}
if (preset.includes('editor')) {
// no editor
return { isValid: false, message: 'No path to editor page allowed' };
}
return { isValid: true, message: 'ok' };
};
/**
* Gets the URL to send a preset to
* @param location
* @param data
* @param searchParams
*/
export const getRouteFromPreset = (location: Location, data: URLPreset[], searchParams: URLSearchParams) => {
const currentURL = location.pathname.substring(1);
// we need to check if the whole url here is an alias, so we can redirect
const foundPreset = data.filter((d) => d.alias === currentURL && d.enabled)[0];
if (foundPreset) {
return generateUrlFromPreset(foundPreset);
}
const presetOnPage = searchParams.get('alias');
for (const d of data) {
if (presetOnPage) {
// if the alias fits the preset on this page, but the URL is different, we redirect user to the new URL
// if we have the same alias and its enabled and its not empty
if (d.alias !== '' && d.enabled && d.alias === presetOnPage) {
const newPath = resolvePath(d.pathAndParams);
const urlParams = new URLSearchParams(newPath.search);
urlParams.set('alias', d.alias);
// we confirm either the url parameters does not match or the url path doesnt
if (!isEqual(urlParams, searchParams) || newPath.pathname !== location.pathname) {
// we then redirect to the alias route, since the view listening to this alias has an outdated URL
return `${newPath.pathname}?${urlParams}`;
}
}
}
}
return null;
};
/**
* Generate URL from an preset
* @param presetData
*/
export const generateUrlFromPreset = (presetData: URLPreset) => {
const newPresetPath = resolvePath(presetData.pathAndParams);
const urlParams = new URLSearchParams(newPresetPath.search);
urlParams.set('alias', presetData.alias);
return `${newPresetPath.pathname}?${urlParams}`;
};