mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-08 00:43:54 +00:00
Reareange everything to match the 5 steps
This commit is contained in:
@@ -9,7 +9,6 @@ import {
|
||||
OscSubscription,
|
||||
ProjectData,
|
||||
Settings,
|
||||
SheetState,
|
||||
UserFields,
|
||||
ViewSettings,
|
||||
} from 'ontime-types';
|
||||
@@ -248,14 +247,13 @@ export async function postNew(initialData: Partial<ProjectData>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sheet Client File
|
||||
* @return {Promise}
|
||||
* @description STEP 1
|
||||
*/
|
||||
export const uploadSheetClientFile = async (file: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append('userFile', file);
|
||||
const res = await axios
|
||||
.post(`${ontimeURL}/sheet-clientsecrect`, formData, {
|
||||
.post(`${ontimeURL}/sheet/clientsecrect`, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
@@ -264,11 +262,50 @@ export const uploadSheetClientFile = async (file: File) => {
|
||||
return res;
|
||||
};
|
||||
|
||||
export const getSheetsAuthUrl = async () => {
|
||||
const response = await axios.get(`${ontimeURL}/sheet-authurl`);
|
||||
/**
|
||||
* @description STEP 1 test
|
||||
*/
|
||||
export const getClientSecrect = async () => {
|
||||
const response = await axios.get(`${ontimeURL}/sheet/clientsecrect`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description STEP 2
|
||||
*/
|
||||
export const getSheetsAuthUrl = async () => {
|
||||
const response = await axios.get(`${ontimeURL}/sheet/authentication/url`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description STEP 2 test
|
||||
*/
|
||||
export const getAuthentication = async () => {
|
||||
const response = await axios.get(`${ontimeURL}/sheet/authentication`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description STEP 3
|
||||
* @returns worksheetOptions
|
||||
*/
|
||||
export const postId = async (id: string) => {
|
||||
const response = await axios.post(`${ontimeURL}/sheet/id`, { id });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description STEP 4
|
||||
*/
|
||||
export const postWorksheet = async (id: string, worksheet: string) => {
|
||||
const response = await axios.post(`${ontimeURL}/sheet/worksheet`, { id, worksheet });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description STEP 5
|
||||
*/
|
||||
export const postPreviewSheet = async (id: string, worksheet: string) => {
|
||||
const response = await axios.post(`${ontimeURL}/sheet-preview`, {
|
||||
id,
|
||||
@@ -277,6 +314,9 @@ export const postPreviewSheet = async (id: string, worksheet: string) => {
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description STEP 5
|
||||
*/
|
||||
export const postPushSheet = async (id: string, worksheet: string) => {
|
||||
const response = await axios.post(`${ontimeURL}/sheet-push`, {
|
||||
id,
|
||||
@@ -284,15 +324,3 @@ export const postPushSheet = async (id: string, worksheet: string) => {
|
||||
});
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description HTTP request to retrieve sheets state
|
||||
* @return {Promise}
|
||||
*/
|
||||
export const getSheetState = async (id: string, worksheet: string): Promise<SheetState> => {
|
||||
const response = await axios.post(`${ontimeURL}/sheet-state`, {
|
||||
id,
|
||||
worksheet,
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@@ -17,16 +17,19 @@ import {
|
||||
Select,
|
||||
} from '@chakra-ui/react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { OntimeRundown, ProjectData, SheetState, UserFields } from 'ontime-types';
|
||||
import { OntimeRundown, ProjectData, UserFields } from 'ontime-types';
|
||||
|
||||
import { PROJECT_DATA, RUNDOWN, USERFIELDS } from '../../../common/api/apiConstants';
|
||||
import { maybeAxiosError } from '../../../common/api/apiUtils';
|
||||
import {
|
||||
getAuthentication,
|
||||
getClientSecrect,
|
||||
getSheetsAuthUrl,
|
||||
getSheetState,
|
||||
patchData,
|
||||
postId,
|
||||
postPreviewSheet,
|
||||
postPushSheet,
|
||||
postWorksheet,
|
||||
uploadSheetClientFile,
|
||||
} from '../../../common/api/ontimeApi';
|
||||
import { projectDataPlaceholder } from '../../../common/models/ProjectData';
|
||||
@@ -51,25 +54,27 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
const [userFields, setUserFields] = useState<UserFields | null>(null);
|
||||
const [project, setProject] = useState<ProjectData | null>(null);
|
||||
|
||||
const [id, setSheetId] = useState('');
|
||||
const [worksheet, setWorksheet] = useState('');
|
||||
const [worksheetOptions, setWorksheetOptions] = useState(new Array<string>());
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const sheetRef = useRef<HTMLInputElement>(null);
|
||||
const worksheetRef = useRef<HTMLSelectElement>(null);
|
||||
|
||||
const [errors, setErrors] = useState({
|
||||
clientSecret: '',
|
||||
authenticate: '',
|
||||
sheetId: '',
|
||||
worksheet: '',
|
||||
pullPush: '',
|
||||
const [state, setState] = useState({
|
||||
clientSecret: { complet: false, message: '' },
|
||||
authenticate: { complet: false, message: '' },
|
||||
id: { complet: false, message: '' },
|
||||
worksheet: { complet: false, message: '' },
|
||||
pullPush: { complet: false, message: '' },
|
||||
});
|
||||
|
||||
const [sheetState, setState] = useState<SheetState>({
|
||||
secret: false,
|
||||
auth: false,
|
||||
id: false,
|
||||
worksheet: false,
|
||||
worksheetOptions: [],
|
||||
});
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
testClientSecrect();
|
||||
if (state.clientSecret.complet) testAuthentication();
|
||||
if (state.authenticate.complet) testSheetId();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClose = () => {
|
||||
setRundown(null);
|
||||
@@ -78,66 +83,127 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
onClose();
|
||||
};
|
||||
|
||||
//SETP-1 Upload Client ID
|
||||
const handleClick = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const updateSheetState = () => {
|
||||
const currentSheetId = sheetRef.current?.value ?? '';
|
||||
const currentWorksheet = worksheetRef.current?.value ?? '';
|
||||
return getSheetState(currentSheetId, currentWorksheet).then((data) => setState(data));
|
||||
};
|
||||
|
||||
const testId = () => {
|
||||
const currentSheetId = sheetRef.current?.value ?? '';
|
||||
const currentWorksheet = worksheetRef.current?.value ?? '';
|
||||
getSheetState(currentSheetId, currentWorksheet)
|
||||
.then((data) => setState(data))
|
||||
.catch((err) => {
|
||||
const message = maybeAxiosError(err);
|
||||
setErrors({ ...errors, sheetId: message });
|
||||
});
|
||||
};
|
||||
|
||||
const handleFile = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!event.target.files?.length) {
|
||||
setErrors({ ...errors, clientSecret: 'Missing file' });
|
||||
setState({
|
||||
clientSecret: { complet: false, message: 'Missing file' },
|
||||
authenticate: { complet: false, message: '' },
|
||||
id: { complet: false, message: '' },
|
||||
worksheet: { complet: false, message: '' },
|
||||
pullPush: { complet: false, message: '' },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedFile = event.target.files[0];
|
||||
uploadSheetClientFile(selectedFile)
|
||||
.then(() => {
|
||||
setErrors({ ...errors, clientSecret: '' });
|
||||
setState({ ...state, clientSecret: { complet: true, message: '' } });
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.response.data.message) {
|
||||
setErrors({ ...errors, clientSecret: err.response.data.message });
|
||||
}
|
||||
const message = maybeAxiosError(err);
|
||||
setState({
|
||||
clientSecret: { complet: false, message },
|
||||
authenticate: { complet: false, message: '' },
|
||||
id: { complet: false, message: '' },
|
||||
worksheet: { complet: false, message: '' },
|
||||
pullPush: { complet: false, message: '' },
|
||||
});
|
||||
});
|
||||
updateSheetState();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
updateSheetState();
|
||||
}
|
||||
}, [isOpen]);
|
||||
const testClientSecrect = () => {
|
||||
getClientSecrect()
|
||||
.then(() => {
|
||||
setState({ ...state, clientSecret: { complet: true, message: '' } });
|
||||
})
|
||||
.catch((err) => {
|
||||
const message = maybeAxiosError(err);
|
||||
setState({
|
||||
clientSecret: { complet: false, message },
|
||||
authenticate: { complet: false, message: '' },
|
||||
id: { complet: false, message: '' },
|
||||
worksheet: { complet: false, message: '' },
|
||||
pullPush: { complet: false, message: '' },
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
//SETP-2 Authenticate
|
||||
const handleAuthenticate = () => {
|
||||
getSheetsAuthUrl()
|
||||
.then((data) => {
|
||||
openLink(data);
|
||||
window.addEventListener('focus', () => updateSheetState(), { once: true });
|
||||
window.addEventListener('focus', () => testAuthentication(), { once: true });
|
||||
})
|
||||
.catch((err) => {
|
||||
const message = maybeAxiosError(err);
|
||||
setErrors({ ...errors, authenticate: message });
|
||||
setState({
|
||||
...state,
|
||||
authenticate: { complet: false, message },
|
||||
id: { complet: false, message: '' },
|
||||
worksheet: { complet: false, message: '' },
|
||||
pullPush: { complet: false, message: '' },
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const testAuthentication = () => {
|
||||
getAuthentication()
|
||||
.then(() => {
|
||||
setState({ ...state, authenticate: { complet: true, message: '' } });
|
||||
})
|
||||
.catch((error) => {
|
||||
const message = maybeAxiosError(error);
|
||||
setState({
|
||||
...state,
|
||||
authenticate: { complet: false, message },
|
||||
id: { complet: false, message: '' },
|
||||
worksheet: { complet: false, message: '' },
|
||||
pullPush: { complet: false, message: '' },
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
//SETP-3 set sheet ID
|
||||
const testSheetId = () => {
|
||||
postId(id)
|
||||
.then((data) => {
|
||||
setState({ ...state, id: { complet: true, message: '' } });
|
||||
setWorksheetOptions(data.worksheetOptions);
|
||||
})
|
||||
.catch((err) => {
|
||||
const message = maybeAxiosError(err);
|
||||
setState({
|
||||
...state,
|
||||
id: { complet: false, message },
|
||||
worksheet: { complet: false, message: '' },
|
||||
pullPush: { complet: false, message: '' },
|
||||
});
|
||||
setWorksheetOptions([]);
|
||||
});
|
||||
};
|
||||
|
||||
//SETP-4 Select Worksheet
|
||||
const testWorksheet = (value: string) => {
|
||||
setWorksheet(value);
|
||||
postWorksheet(id, worksheet)
|
||||
.then(() => {
|
||||
setState({ ...state, worksheet: { complet: true, message: '' } });
|
||||
})
|
||||
.catch((err) => {
|
||||
const message = maybeAxiosError(err);
|
||||
setState({ ...state, worksheet: { complet: false, message }, pullPush: { complet: false, message: '' } });
|
||||
});
|
||||
};
|
||||
|
||||
//SETP-5 Upload / Download
|
||||
const handlePullData = () => {
|
||||
postPreviewSheet(sheetRef.current?.value ?? '', worksheetRef.current?.value ?? '').then((data) => {
|
||||
postPreviewSheet(id, worksheet).then((data) => {
|
||||
setProject(data.project);
|
||||
setRundown(data.rundown);
|
||||
setUserFields(data.userFields);
|
||||
@@ -145,13 +211,11 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
};
|
||||
|
||||
const handlePushData = () => {
|
||||
const currentSheetId = sheetRef.current?.value ?? '';
|
||||
const currentWorksheet = worksheetRef.current?.value ?? '';
|
||||
postPushSheet(currentSheetId, currentWorksheet);
|
||||
postPushSheet(id, worksheet);
|
||||
};
|
||||
|
||||
//GET preview
|
||||
const handleFinalise = async () => {
|
||||
// this step is currently only used for excel files, after preview
|
||||
if (rundown && userFields && project) {
|
||||
let doClose = false;
|
||||
try {
|
||||
@@ -165,8 +229,7 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
doClose = true;
|
||||
} catch (error) {
|
||||
const message = maybeAxiosError(error);
|
||||
console.log(message);
|
||||
// setErrors(`Failed applying changes ${message}`);
|
||||
console.error(message);
|
||||
} finally {
|
||||
if (doClose) {
|
||||
handleClose();
|
||||
@@ -204,9 +267,9 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
<>
|
||||
<Step
|
||||
title='1 - Upload OAuth 2.0 Client ID'
|
||||
completed={Boolean(sheetState?.secret)}
|
||||
completed={state.clientSecret.complet}
|
||||
disabled={false}
|
||||
error={errors.clientSecret}
|
||||
error={state.clientSecret.message}
|
||||
>
|
||||
<Input
|
||||
ref={fileInputRef}
|
||||
@@ -216,43 +279,58 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
accept='.json'
|
||||
data-testid='file-input'
|
||||
/>
|
||||
<Button size='sm' variant='ontime-subtle-on-light' onClick={handleClick}>
|
||||
{sheetState?.secret ? 'Reupload Client ID' : 'Upload Client ID'}
|
||||
</Button>
|
||||
<div style={{ display: 'flex', gap: '1em' }}>
|
||||
<Button size='sm' variant='ontime-subtle-on-light' onClick={handleClick}>
|
||||
{state.clientSecret.complet ? 'Reupload Client ID' : 'Upload Client ID'}
|
||||
</Button>
|
||||
<Button size='sm' variant='ontime-ghosted-on-light' onClick={testClientSecrect}>
|
||||
Retry Client ID
|
||||
</Button>
|
||||
</div>
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title='2 - Authenticate with Google'
|
||||
completed={Boolean(sheetState?.auth)}
|
||||
disabled={!sheetState?.secret}
|
||||
error={errors.authenticate}
|
||||
completed={state.authenticate.complet}
|
||||
disabled={!state.clientSecret.complet}
|
||||
error={state.authenticate.message}
|
||||
>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='ontime-subtle-on-light'
|
||||
onClick={handleAuthenticate}
|
||||
disabled={!sheetState?.secret}
|
||||
>
|
||||
Authenticate
|
||||
</Button>
|
||||
<div style={{ display: 'flex', gap: '1em' }}>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='ontime-subtle-on-light'
|
||||
onClick={handleAuthenticate}
|
||||
isDisabled={!state.clientSecret.complet}
|
||||
>
|
||||
Authenticate
|
||||
</Button>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='ontime-ghosted-on-light'
|
||||
onClick={testAuthentication}
|
||||
isDisabled={!state.clientSecret.complet}
|
||||
>
|
||||
Retry Connection
|
||||
</Button>
|
||||
</div>
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title='3 - Add Document ID'
|
||||
completed={Boolean(sheetState?.id)}
|
||||
disabled={!sheetState?.auth}
|
||||
error={errors.sheetId}
|
||||
completed={state.id.complet}
|
||||
disabled={!state.authenticate.complet}
|
||||
error={state.id.message}
|
||||
>
|
||||
<HStack>
|
||||
<Input
|
||||
type='text'
|
||||
ref={sheetRef}
|
||||
id='sheetid'
|
||||
size='sm'
|
||||
variant='ontime-filled-on-light'
|
||||
disabled={!sheetState?.auth}
|
||||
disabled={!state.authenticate.complet}
|
||||
value={id}
|
||||
onChange={(event) => setSheetId(event.target.value)}
|
||||
/>
|
||||
<Button size='sm' variant='ontime-subtle-on-light' padding='0 2em' onClick={testId}>
|
||||
<Button size='sm' variant='ontime-subtle-on-light' padding='0 2em' onClick={testSheetId}>
|
||||
Connect
|
||||
</Button>
|
||||
</HStack>
|
||||
@@ -260,11 +338,17 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
|
||||
<Step
|
||||
title='4 - Select Worksheet to import'
|
||||
completed={Boolean(sheetState?.worksheet)}
|
||||
disabled={sheetState?.worksheetOptions.length == 0}
|
||||
completed={state.worksheet.complet}
|
||||
disabled={worksheetOptions.length == 0}
|
||||
>
|
||||
<Select ref={worksheetRef} size='sm' id='worksheet' disabled={sheetState?.worksheetOptions.length == 0}>
|
||||
{sheetState?.worksheetOptions?.map((value) => (
|
||||
<Select
|
||||
size='sm'
|
||||
disabled={worksheetOptions.length == 0}
|
||||
placeholder='Select a worksheet'
|
||||
onChange={(event) => testWorksheet(event.target.value)}
|
||||
value={worksheet}
|
||||
>
|
||||
{worksheetOptions.map((value) => (
|
||||
<option key={value} value={value}>
|
||||
{value}
|
||||
</option>
|
||||
@@ -272,10 +356,10 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
</Select>
|
||||
</Step>
|
||||
|
||||
<Step title='5 - Upload / Download rundown' completed={false} disabled={!sheetState?.worksheet}>
|
||||
<Step title='5 - Upload / Download rundown' completed={false} disabled={!state.worksheet.complet}>
|
||||
<div style={{ display: 'flex', gap: '1em' }}>
|
||||
<Button
|
||||
disabled={!sheetState?.worksheet}
|
||||
disabled={!state.worksheet.complet}
|
||||
variant='ontime-subtle-on-light'
|
||||
padding='0 2em'
|
||||
onClick={handlePushData}
|
||||
@@ -283,7 +367,7 @@ export default function SheetsModal(props: SheetsModalProps) {
|
||||
Upload
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!sheetState?.worksheet}
|
||||
disabled={!state.worksheet.complet}
|
||||
variant='ontime-subtle-on-light'
|
||||
padding='0 2em'
|
||||
onClick={handlePullData}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PropsWithChildren, useEffect, useState } from 'react';
|
||||
import { PropsWithChildren, useEffect, useMemo, useState } from 'react';
|
||||
import { IoCheckmarkCircle } from '@react-icons/all-files/io5/IoCheckmarkCircle';
|
||||
import { IoCloseCircle } from '@react-icons/all-files/io5/IoCloseCircle';
|
||||
import { IoRadioButtonOffOutline } from '@react-icons/all-files/io5/IoRadioButtonOffOutline';
|
||||
@@ -18,6 +18,12 @@ export default function Step(props: PropsWithChildren<StepProps>) {
|
||||
|
||||
const handleCollapse = () => setCollapsed((prev) => !prev);
|
||||
|
||||
const icon = useMemo(() => {
|
||||
if (completed) return <IoCheckmarkCircle className={style.step} style={{ color: 'green' }} />;
|
||||
if (error) return <IoCloseCircle className={style.step} style={{ color: 'red' }} />;
|
||||
return <IoRadioButtonOffOutline className={style.step} />;
|
||||
}, [completed, error]);
|
||||
|
||||
useEffect(() => {
|
||||
if (completed) {
|
||||
setCollapsed(true);
|
||||
@@ -27,13 +33,7 @@ export default function Step(props: PropsWithChildren<StepProps>) {
|
||||
return (
|
||||
<div className={style.wrapper}>
|
||||
<div className={style.header} onClick={handleCollapse}>
|
||||
{completed ? (
|
||||
<IoCheckmarkCircle className={style.step} style={{ color: 'green' }} />
|
||||
) : error ? (
|
||||
<IoCloseCircle className={style.step} style={{ color: 'red' }} />
|
||||
) : (
|
||||
<IoRadioButtonOffOutline className={style.step} />
|
||||
)}
|
||||
{icon}
|
||||
<span className={style.title}>{title}</span>
|
||||
</div>
|
||||
{!collapsed && (
|
||||
|
||||
@@ -20,7 +20,7 @@ import { runtimeCacheStore } from '../stores/cachingStore.js';
|
||||
import { delayedRundownCacheKey } from '../services/rundown-service/delayedRundown.utils.js';
|
||||
import { integrationService } from '../services/integration-service/IntegrationService.js';
|
||||
|
||||
import { Sheet } from '../utils/sheetsAuth.js';
|
||||
import { sheet } from '../utils/sheetsAuth.js';
|
||||
|
||||
// Create controller for GET request to '/ontime/poll'
|
||||
// Returns data for current state
|
||||
@@ -458,49 +458,19 @@ export const postNew: RequestHandler = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
//SHEET Functions
|
||||
/**
|
||||
* downloads and parses an sheet
|
||||
* @description SETP-1 POST Client Secrect
|
||||
* @returns parsed result
|
||||
*/
|
||||
export async function previewSheet(req, res) {
|
||||
try {
|
||||
const { id, worksheet } = req.body;
|
||||
const data = await Sheet.pull(id, worksheet);
|
||||
res.status(200).send(data);
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: error.toString() });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* downloads and parses an sheet
|
||||
* @returns parsed result
|
||||
* @method POST
|
||||
*/
|
||||
export async function pushSheet(req, res) {
|
||||
try {
|
||||
const { id, worksheet } = req.data;
|
||||
await Sheet.push(id, worksheet);
|
||||
res.status(200).send('ok');
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: error.toString() });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* uploads Client secrets file
|
||||
* @returns parsed result
|
||||
* @method POST
|
||||
*/
|
||||
export async function uploadSheetClientFile(req, res) {
|
||||
if (!req.file.path) {
|
||||
res.status(400).send({ message: 'File not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const client = JSON.parse(fs.readFileSync(req.file.path as string, 'utf-8'));
|
||||
await Sheet.saveClientSecrets(client);
|
||||
await sheet.saveClientSecrets(client);
|
||||
res.status(200).send('OK');
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: error.toString() });
|
||||
@@ -511,12 +481,27 @@ export async function uploadSheetClientFile(req, res) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns link to sheet auth url
|
||||
* @method GET
|
||||
* @description SETP-1 GET Client Secrect status
|
||||
*/
|
||||
export async function sheetAuthUrl(req, res) {
|
||||
export const getClientSecrect = async (req, res) => {
|
||||
try {
|
||||
const authUrl = await Sheet.openAuthServer();
|
||||
const clientSecrectExists = await sheet.testClientSecrect();
|
||||
if (clientSecrectExists) {
|
||||
res.status(200).send();
|
||||
} else {
|
||||
res.status(500).send({ message: 'The Client ID does not exist' });
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: error.toString() });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description SETP-2 GET sheet authentication url
|
||||
*/
|
||||
export async function getAuthenticationUrl(req, res) {
|
||||
try {
|
||||
const authUrl = await sheet.openAuthServer();
|
||||
res.status(200).send(authUrl);
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: error.toString() });
|
||||
@@ -524,15 +509,70 @@ export async function sheetAuthUrl(req, res) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Get sheet state
|
||||
* @method POST
|
||||
* @description SETP-2 GET sheet authentication status
|
||||
*/
|
||||
export const getSheetState = async (req, res) => {
|
||||
const { id, worksheet } = req.body;
|
||||
export const getAuthentication = async (req, res) => {
|
||||
try {
|
||||
const state = await Sheet.getSheetState(id, worksheet);
|
||||
await sheet.testAuthentication();
|
||||
res.status(200).send();
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: error.toString() });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description SETP-3 POST sheet id
|
||||
* @returns list of worksheets
|
||||
*/
|
||||
export const postId = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.body;
|
||||
if (id.lenght < 40) {
|
||||
res.status(400).send({ message: 'ID is usualy 44 characters long' });
|
||||
}
|
||||
const state = await sheet.testSheetId(id);
|
||||
res.status(200).send(state);
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: error.toString() });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description SETP-4 POST worksheet
|
||||
*/
|
||||
export const postWorksheet = async (req, res) => {
|
||||
try {
|
||||
const { worksheet, id } = req.body;
|
||||
const state = await sheet.testWorksheet(worksheet, id);
|
||||
res.status(200).send(state);
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: error.toString() });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description STEP-5 POST download undown to sheet
|
||||
* @returns parsed result
|
||||
*/
|
||||
export async function previewSheet(req, res) {
|
||||
try {
|
||||
const { id, worksheet } = req.body;
|
||||
const data = await sheet.pull(id, worksheet);
|
||||
res.status(200).send(data);
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: error.toString() });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description STEP-5 POST upload rundown to sheet
|
||||
*/
|
||||
export async function pushSheet(req, res) {
|
||||
try {
|
||||
const { id, worksheet } = req.data;
|
||||
await sheet.push(id, worksheet);
|
||||
res.status(200).send();
|
||||
} catch (error) {
|
||||
res.status(500).send({ message: error.toString() });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,8 +153,16 @@ export const validatePatchProjectFile = [
|
||||
},
|
||||
];
|
||||
|
||||
//TODO: is thise correct
|
||||
export const validateSheetParams = [
|
||||
export const validateSheetid = [
|
||||
body('id').exists().isString(),
|
||||
(req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
export const validateWorksheet = [
|
||||
body('id').exists().isString(),
|
||||
body('worksheet').exists().isString(),
|
||||
(req, res, next) => {
|
||||
|
||||
@@ -21,11 +21,13 @@ import {
|
||||
postViewSettings,
|
||||
previewExcel,
|
||||
postHTTP,
|
||||
sheetAuthUrl,
|
||||
getAuthenticationUrl,
|
||||
uploadSheetClientFile,
|
||||
previewSheet,
|
||||
pushSheet,
|
||||
getSheetState,
|
||||
postId,
|
||||
getAuthentication,
|
||||
getClientSecrect,
|
||||
} from '../controllers/ontimeController.js';
|
||||
|
||||
import {
|
||||
@@ -33,11 +35,12 @@ import {
|
||||
validateOSC,
|
||||
validatePatchProjectFile,
|
||||
validateSettings,
|
||||
validateSheetParams,
|
||||
validateUserFields,
|
||||
viewValidator,
|
||||
validateHTTP,
|
||||
validateOscSubscription,
|
||||
validateSheetid,
|
||||
validateWorksheet,
|
||||
} from '../controllers/ontimeController.validate.js';
|
||||
import { projectSanitiser } from '../controllers/projectController.validate.js';
|
||||
|
||||
@@ -103,17 +106,23 @@ router.post('/http', validateHTTP, postHTTP);
|
||||
// create route between controller and '/ontime/new' endpoint
|
||||
router.post('/new', projectSanitiser, postNew);
|
||||
|
||||
// create route between controller and '/ontime/sheet-client' endpoint
|
||||
router.post('/sheet-clientsecrect', uploadFile, uploadSheetClientFile);
|
||||
|
||||
// create route between controller and '/ontime/sheet-authstatus' endpoint
|
||||
router.get('/sheet-authurl', sheetAuthUrl);
|
||||
//SETP-1
|
||||
router.post('/sheet/clientsecrect', uploadFile, uploadSheetClientFile);
|
||||
router.get('/sheet/clientsecrect', uploadFile, getClientSecrect);
|
||||
|
||||
// create route between controller and '/ontime/preview-sheet' endpoint
|
||||
router.post('/sheet-preview', validateSheetParams, previewSheet);
|
||||
//SETP-2
|
||||
router.get('/sheet/authentication/url', getAuthenticationUrl);
|
||||
router.get('/sheet/authentication', getAuthentication);
|
||||
|
||||
// create route between controller and '/ontime/preview-sheet' endpoint
|
||||
router.post('/sheet-push', validateSheetParams, pushSheet);
|
||||
//STEP-3
|
||||
router.post('/sheet/id', validateSheetid, postId);
|
||||
|
||||
// create route between controller and '/ontime/sheet-state' endpoint
|
||||
router.post('/sheet-state', validateSheetParams, getSheetState);
|
||||
//STEP-4
|
||||
router.post('/sheet/worksheet', validateWorksheet, postId);
|
||||
|
||||
//STEP-5 download and generate preview
|
||||
router.post('/sheet-preview', validateWorksheet, previewSheet);
|
||||
|
||||
//STEP-5 upload
|
||||
router.post('/sheet-push', validateWorksheet, pushSheet);
|
||||
|
||||
+201
-162
@@ -3,7 +3,7 @@ import { writeFile } from 'fs/promises';
|
||||
import { readFileSync } from 'fs';
|
||||
import { OAuth2Client } from 'google-auth-library';
|
||||
import http from 'http';
|
||||
import { DatabaseModel, SheetState, LogOrigin } from 'ontime-types';
|
||||
import { DatabaseModel, LogOrigin } from 'ontime-types';
|
||||
import { join } from 'path';
|
||||
import { URL } from 'url';
|
||||
import { logger } from '../classes/Logger.js';
|
||||
@@ -18,13 +18,24 @@ type ResponseOK = {
|
||||
data: Partial<DatabaseModel>;
|
||||
};
|
||||
|
||||
class sheet {
|
||||
class Sheet {
|
||||
private static client: null | OAuth2Client = null;
|
||||
private readonly scope = 'https://www.googleapis.com/auth/spreadsheets';
|
||||
private readonly sheetsFolder: string;
|
||||
private readonly clientSecretFile: string;
|
||||
private static clientSecret = null;
|
||||
private static authUrl: null | string = null;
|
||||
private authServerTimeout;
|
||||
|
||||
private readonly requiredClientKeys = [
|
||||
'client_id',
|
||||
'project_id',
|
||||
'auth_uri',
|
||||
'token_uri',
|
||||
'auth_provider_x509_cert_url',
|
||||
'client_secret',
|
||||
'redirect_uris',
|
||||
];
|
||||
|
||||
constructor() {
|
||||
const appDataPath = getAppDataPath();
|
||||
@@ -35,37 +46,194 @@ class sheet {
|
||||
this.clientSecretFile = join(this.sheetsFolder, 'client_secret.json');
|
||||
ensureDirectory(this.sheetsFolder);
|
||||
try {
|
||||
sheet.clientSecret = JSON.parse(readFileSync(this.clientSecretFile, 'utf-8'));
|
||||
const secrets = JSON.parse(readFileSync(this.clientSecretFile, 'utf-8'));
|
||||
const isKeyMissing = this.requiredClientKeys.some((key) => !(key in secrets['installed']));
|
||||
if (!isKeyMissing) {
|
||||
Sheet.clientSecret = secrets;
|
||||
}
|
||||
} catch (_) {
|
||||
/* empty - it is ok thet there is no clientSecret */
|
||||
}
|
||||
}
|
||||
|
||||
public async getSheetState(id: string, worksheet: string): Promise<SheetState> {
|
||||
const state: SheetState = {
|
||||
secret: false,
|
||||
auth: false,
|
||||
id: false,
|
||||
worksheet: false,
|
||||
worksheetOptions: [],
|
||||
};
|
||||
/**
|
||||
* @description SETP 1 - saves secrets object to appdata path as client_secret.json
|
||||
* @param {object} secrets
|
||||
* @throws
|
||||
*/
|
||||
public async saveClientSecrets(secrets: object) {
|
||||
Sheet.client = null;
|
||||
Sheet.authUrl = null;
|
||||
Sheet.clientSecret = null;
|
||||
|
||||
state.secret = sheet.clientSecret !== null;
|
||||
state.auth = sheet.client !== null;
|
||||
|
||||
if (id != '' && state.auth) {
|
||||
const spreadsheets = await sheets({ version: 'v4', auth: sheet.client }).spreadsheets.get({
|
||||
spreadsheetId: id,
|
||||
includeGridData: false,
|
||||
});
|
||||
if (!spreadsheets || spreadsheets.status != 200) {
|
||||
return state;
|
||||
}
|
||||
state.id = true;
|
||||
state.worksheetOptions = spreadsheets.data.sheets.map((i) => i.properties.title);
|
||||
state.worksheet = state.worksheetOptions.indexOf(worksheet) >= 0;
|
||||
const isKeyMissing = this.requiredClientKeys.some((key) => !(key in secrets['installed']));
|
||||
if (isKeyMissing) {
|
||||
throw new Error('Client file is missing some keys');
|
||||
}
|
||||
|
||||
await writeFile(this.clientSecretFile, JSON.stringify(secrets), 'utf-8').catch((err) => {
|
||||
throw new Error(`Unable to save client file to disk ${err}`);
|
||||
});
|
||||
Sheet.clientSecret = secrets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description SETP 1 - test that the saved object is pressent
|
||||
*/
|
||||
testClientSecrect() {
|
||||
return Sheet.clientSecret !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description SETP 2 - create server to interact with th OAuth2 request
|
||||
* @returns {Promise<string | null>} - returns url path serve on success
|
||||
* @throws
|
||||
*/
|
||||
async openAuthServer(): Promise<string | null> {
|
||||
//TODO: this only works on local networks
|
||||
|
||||
// if the server is allready running retun it
|
||||
if (Sheet.authUrl) {
|
||||
clearTimeout(this.authServerTimeout);
|
||||
this.authServerTimeout = setTimeout(
|
||||
() => {
|
||||
Sheet.authUrl = null;
|
||||
server.unref;
|
||||
},
|
||||
2 * 60 * 1000,
|
||||
);
|
||||
return Sheet.authUrl;
|
||||
}
|
||||
|
||||
// Check that Secret is valid
|
||||
const keyFile = Sheet.clientSecret;
|
||||
const keys = keyFile.installed || keyFile.web;
|
||||
if (!keys.redirect_uris || keys.redirect_uris.length === 0) {
|
||||
throw new Error('Sheet: Missing redirect URI');
|
||||
}
|
||||
const redirectUri = new URL(keys.redirect_uris[0]);
|
||||
if (redirectUri.hostname !== 'localhost') {
|
||||
throw new Error('Sheet: Invalid redirect URI');
|
||||
}
|
||||
|
||||
// create an oAuth client to authorize the API call
|
||||
const client = new OAuth2Client({
|
||||
clientId: keys.client_id,
|
||||
clientSecret: keys.client_secret,
|
||||
});
|
||||
|
||||
// start the server that will recive the codes
|
||||
const server = http.createServer(async (req, res) => {
|
||||
try {
|
||||
const serverUrl = new URL(req.url, 'http://localhost:3000');
|
||||
if (serverUrl.pathname !== redirectUri.pathname) {
|
||||
res.end('Invalid callback URL');
|
||||
return;
|
||||
}
|
||||
const searchParams = serverUrl.searchParams;
|
||||
if (searchParams.has('error')) {
|
||||
res.end('Authorization rejected.');
|
||||
logger.info(LogOrigin.Server, `Sheet: ${searchParams.get('error')}`);
|
||||
return;
|
||||
}
|
||||
if (!searchParams.has('code')) {
|
||||
res.end('No authentication code provided.');
|
||||
logger.info(LogOrigin.Server, `Sheet: Cannot read authentication code`);
|
||||
return;
|
||||
}
|
||||
const code = searchParams.get('code');
|
||||
const { tokens } = await client.getToken({
|
||||
code,
|
||||
redirect_uri: redirectUri.toString(),
|
||||
});
|
||||
client.credentials = tokens;
|
||||
Sheet.client = client;
|
||||
res.end('Authentication successful! Please close this tab and return to OnTime.');
|
||||
logger.info(LogOrigin.Server, `Sheet: Authentication successful`);
|
||||
} catch (e) {
|
||||
logger.error(LogOrigin.Server, `Sheet: ${e}`);
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
let listenPort = 3000;
|
||||
if (keyFile.installed) {
|
||||
// Use emphemeral port if not a web client
|
||||
listenPort = 0;
|
||||
} else if (redirectUri.port !== '') {
|
||||
listenPort = Number(redirectUri.port);
|
||||
}
|
||||
//TODO: the server might not start correctly
|
||||
server.listen(listenPort);
|
||||
const address = server.address();
|
||||
if (typeof address !== 'string') {
|
||||
redirectUri.port = String(address.port);
|
||||
}
|
||||
// open the browser to the authorize url to start the workflow
|
||||
const authorizeUrl = client.generateAuthUrl({
|
||||
redirect_uri: redirectUri.toString(),
|
||||
access_type: 'offline',
|
||||
scope: this.scope,
|
||||
});
|
||||
Sheet.authUrl = authorizeUrl;
|
||||
this.authServerTimeout = setTimeout(
|
||||
() => {
|
||||
Sheet.authUrl = null;
|
||||
server.unref();
|
||||
},
|
||||
2 * 60 * 1000,
|
||||
);
|
||||
return authorizeUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description SETP 2 - test that the reciveed OAuth2 is still valid
|
||||
* @throws
|
||||
*/
|
||||
async testAuthentication() {
|
||||
if (Sheet.client) {
|
||||
const ref = await Sheet.client.refreshAccessToken();
|
||||
if (ref.credentials.expiry_date > 10000) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error('Unable to use accese token');
|
||||
}
|
||||
} else {
|
||||
throw new Error('Unable to authenticate');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description SETP 3 - test the given sheet id
|
||||
* @throws
|
||||
*/
|
||||
async testSheetId(id: string) {
|
||||
const spreadsheets = await sheets({ version: 'v4', auth: Sheet.client }).spreadsheets.get({
|
||||
spreadsheetId: id,
|
||||
includeGridData: false,
|
||||
});
|
||||
if (spreadsheets.status != 200) {
|
||||
throw new Error(spreadsheets.statusText);
|
||||
}
|
||||
return { worksheetOptions: spreadsheets.data.sheets.map((i) => i.properties.title) };
|
||||
}
|
||||
|
||||
/**
|
||||
* @description SETP 4 - test the given worksheet
|
||||
* @throws
|
||||
*/
|
||||
async testWorksheet(id: string, worksheet: string) {
|
||||
const spreadsheets = await sheets({ version: 'v4', auth: Sheet.client }).spreadsheets.get({
|
||||
spreadsheetId: id,
|
||||
includeGridData: false,
|
||||
});
|
||||
if (spreadsheets.status != 200) {
|
||||
throw new Error(spreadsheets.statusText);
|
||||
}
|
||||
const worksheetExist = spreadsheets.data.sheets.find((i) => i.properties.title === worksheet);
|
||||
if (!worksheetExist) {
|
||||
throw new Error('Unable to find worksheet');
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,7 +244,7 @@ class sheet {
|
||||
* @throws
|
||||
*/
|
||||
private async exist(sheetId: string, worksheet: string): Promise<{ worksheetId: number; range: string }> {
|
||||
const spreadsheets = await sheets({ version: 'v4', auth: sheet.client }).spreadsheets.get({
|
||||
const spreadsheets = await sheets({ version: 'v4', auth: Sheet.client }).spreadsheets.get({
|
||||
spreadsheetId: sheetId,
|
||||
});
|
||||
|
||||
@@ -95,7 +263,7 @@ class sheet {
|
||||
}
|
||||
|
||||
/**
|
||||
* push rundown and project data to sheet
|
||||
* @description SETP 5 - Upload the rundown to sheet
|
||||
* @param {string} id - id of the sheet https://docs.google.com/spreadsheets/d/[[spreadsheetId]]/edit#gid=0
|
||||
* @param {string} worksheet - the name of the worksheet containing ontime data
|
||||
* @throws
|
||||
@@ -103,7 +271,7 @@ class sheet {
|
||||
public async push(id: string, worksheet: string) {
|
||||
const { worksheetId, range } = await this.exist(id, worksheet);
|
||||
|
||||
const rq = await sheets({ version: 'v4', auth: sheet.client }).spreadsheets.values.get({
|
||||
const rq = await sheets({ version: 'v4', auth: Sheet.client }).spreadsheets.values.get({
|
||||
spreadsheetId: id,
|
||||
valueRenderOption: 'FORMATTED_VALUE',
|
||||
majorDimension: 'ROWS',
|
||||
@@ -154,7 +322,7 @@ class sheet {
|
||||
//update project data
|
||||
updateRundown.push(cellRequenstFromProjectData(projectData, worksheetId, projectMetadata));
|
||||
|
||||
const writeResponds = await sheets({ version: 'v4', auth: sheet.client }).spreadsheets.batchUpdate({
|
||||
const writeResponds = await sheets({ version: 'v4', auth: Sheet.client }).spreadsheets.batchUpdate({
|
||||
spreadsheetId: id,
|
||||
requestBody: {
|
||||
includeSpreadsheetInResponse: false,
|
||||
@@ -174,7 +342,7 @@ class sheet {
|
||||
}
|
||||
|
||||
/**
|
||||
* pull rundown and project data to sheet
|
||||
* @description SETP 5 - Downpload the rundown from sheet
|
||||
* @param {string} id - id of the sheet https://docs.google.com/spreadsheets/d/[[spreadsheetId]]/edit#gid=0
|
||||
* @param {string} worksheet - the name of the worksheet containing ontime data
|
||||
* @returns {Promise<Partial<ResponseOK>>}
|
||||
@@ -185,7 +353,7 @@ class sheet {
|
||||
|
||||
const res: Partial<ResponseOK> = {};
|
||||
|
||||
const rq = await sheets({ version: 'v4', auth: sheet.client }).spreadsheets.values.get({
|
||||
const rq = await sheets({ version: 'v4', auth: Sheet.client }).spreadsheets.values.get({
|
||||
spreadsheetId: id,
|
||||
valueRenderOption: 'FORMATTED_VALUE',
|
||||
majorDimension: 'ROWS',
|
||||
@@ -206,135 +374,6 @@ class sheet {
|
||||
throw new Error(`Sheet: read faild: ${rq.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* saves secrets object to appdata path as client_secret.json
|
||||
* @param {object} secrets
|
||||
* @throws
|
||||
*/
|
||||
public async saveClientSecrets(secrets: object) {
|
||||
sheet.client = null;
|
||||
sheet.authUrl = null;
|
||||
sheet.clientSecret = null;
|
||||
if (
|
||||
!('client_id' in secrets['installed']) ||
|
||||
!('project_id' in secrets['installed']) ||
|
||||
!('auth_uri' in secrets['installed']) ||
|
||||
!('token_uri' in secrets['installed']) ||
|
||||
!('auth_provider_x509_cert_url' in secrets['installed']) ||
|
||||
!('client_secret' in secrets['installed']) ||
|
||||
!('redirect_uris' in secrets['installed'])
|
||||
) {
|
||||
throw new Error('Sheet: Client secret is missing some keys');
|
||||
}
|
||||
await writeFile(this.clientSecretFile, JSON.stringify(secrets), 'utf-8').catch((err) =>
|
||||
logger.error(LogOrigin.Server, `${err}`),
|
||||
);
|
||||
sheet.clientSecret = secrets;
|
||||
}
|
||||
|
||||
private authServerTimeout;
|
||||
/**
|
||||
* create local Auth Server
|
||||
* @returns {Promise<string | null>} - returns url path serve on success
|
||||
* @throws
|
||||
*/
|
||||
public async openAuthServer(): Promise<string | null> {
|
||||
//TODO: this only works on local networks
|
||||
|
||||
// if the server is allready running retun it
|
||||
if (sheet.authUrl) {
|
||||
clearTimeout(this.authServerTimeout);
|
||||
this.authServerTimeout = setTimeout(
|
||||
() => {
|
||||
sheet.authUrl = null;
|
||||
server.unref;
|
||||
},
|
||||
2 * 60 * 1000,
|
||||
);
|
||||
return sheet.authUrl;
|
||||
}
|
||||
|
||||
// Check that Secret is valid
|
||||
const keyFile = sheet.clientSecret;
|
||||
const keys = keyFile.installed || keyFile.web;
|
||||
if (!keys.redirect_uris || keys.redirect_uris.length === 0) {
|
||||
throw new Error('Sheet: Missing redirect URI');
|
||||
}
|
||||
const redirectUri = new URL(keys.redirect_uris[0]);
|
||||
if (redirectUri.hostname !== 'localhost') {
|
||||
throw new Error('Sheet: Invalid redirect URI');
|
||||
}
|
||||
|
||||
// create an oAuth client to authorize the API call
|
||||
const client = new OAuth2Client({
|
||||
clientId: keys.client_id,
|
||||
clientSecret: keys.client_secret,
|
||||
});
|
||||
|
||||
// start the server that will recive the codes
|
||||
const server = http.createServer(async (req, res) => {
|
||||
try {
|
||||
const serverUrl = new URL(req.url, 'http://localhost:3000');
|
||||
if (serverUrl.pathname !== redirectUri.pathname) {
|
||||
res.end('Invalid callback URL');
|
||||
return;
|
||||
}
|
||||
const searchParams = serverUrl.searchParams;
|
||||
if (searchParams.has('error')) {
|
||||
res.end('Authorization rejected.');
|
||||
logger.info(LogOrigin.Server, `Sheet: ${searchParams.get('error')}`);
|
||||
return;
|
||||
}
|
||||
if (!searchParams.has('code')) {
|
||||
res.end('No authentication code provided.');
|
||||
logger.info(LogOrigin.Server, `Sheet: Cannot read authentication code`);
|
||||
return;
|
||||
}
|
||||
const code = searchParams.get('code');
|
||||
const { tokens } = await client.getToken({
|
||||
code,
|
||||
redirect_uri: redirectUri.toString(),
|
||||
});
|
||||
client.credentials = tokens;
|
||||
sheet.client = client;
|
||||
res.end('Authentication successful! Please close this tab and return to OnTime.');
|
||||
logger.info(LogOrigin.Server, `Sheet: Authentication successful`);
|
||||
} catch (e) {
|
||||
logger.error(LogOrigin.Server, `Sheet: ${e}`);
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
let listenPort = 3000;
|
||||
if (keyFile.installed) {
|
||||
// Use emphemeral port if not a web client
|
||||
listenPort = 0;
|
||||
} else if (redirectUri.port !== '') {
|
||||
listenPort = Number(redirectUri.port);
|
||||
}
|
||||
//TODO: the server might not start correctly
|
||||
server.listen(listenPort);
|
||||
const address = server.address();
|
||||
if (typeof address !== 'string') {
|
||||
redirectUri.port = String(address.port);
|
||||
}
|
||||
// open the browser to the authorize url to start the workflow
|
||||
const authorizeUrl = client.generateAuthUrl({
|
||||
redirect_uri: redirectUri.toString(),
|
||||
access_type: 'offline',
|
||||
scope: this.scope,
|
||||
});
|
||||
sheet.authUrl = authorizeUrl;
|
||||
this.authServerTimeout = setTimeout(
|
||||
() => {
|
||||
sheet.authUrl = null;
|
||||
server.unref();
|
||||
},
|
||||
2 * 60 * 1000,
|
||||
);
|
||||
return authorizeUrl;
|
||||
}
|
||||
}
|
||||
|
||||
export const Sheet = new sheet();
|
||||
export const sheet = new Sheet();
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export type SheetState = {
|
||||
secret: boolean;
|
||||
auth: boolean;
|
||||
id: boolean;
|
||||
worksheet: boolean;
|
||||
worksheetOptions: string[];
|
||||
} | null;
|
||||
@@ -37,9 +37,6 @@ export type { OSCSettings, OscSubscription, OscSubscriptionOptions } from './def
|
||||
// ---> HTTP
|
||||
export type { HttpSettings, HttpSubscription, HttpSubscriptionOptions } from './definitions/core/HttpSettings.type.js';
|
||||
|
||||
// ---> Sheet
|
||||
export type { SheetState } from './definitions/core/Sheet.type.js';
|
||||
|
||||
// SERVER RESPONSES
|
||||
export type { NetworkInterface, GetInfo } from './api/ontime-controller/BackendResponse.type.js';
|
||||
export type { GetRundownCached } from './api/rundown-controller/BackendResponse.type.js';
|
||||
|
||||
Reference in New Issue
Block a user