Merge pull request #300 from Monadical-SAS/sara/fix-api-auth

fix api auth
This commit is contained in:
Sara
2023-11-03 14:14:02 +01:00
committed by GitHub
15 changed files with 184 additions and 123 deletions

View File

@@ -1,43 +1,17 @@
"use client";
import React, { useState, useEffect } from "react";
import getApi from "../../lib/getApi";
import {
PageGetTranscript,
GetTranscript,
GetTranscriptFromJSON,
} from "../../api";
import React, { useState } from "react";
import { GetTranscript } from "../../api";
import { Title } from "../../lib/textComponents";
import Pagination from "./pagination";
import Link from "next/link";
import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faGear } from "@fortawesome/free-solid-svg-icons";
import useTranscriptList from "../transcripts/useTranscriptList";
export default function TranscriptBrowser() {
const api = getApi();
const [results, setResults] = useState<PageGetTranscript | null>(null);
const [page, setPage] = useState<number>(1);
const [isLoading, setIsLoading] = useState<boolean>(false);
const isAuthenticated = useFiefIsAuthenticated();
useEffect(() => {
if (!isAuthenticated) return;
setIsLoading(true);
api
.v1TranscriptsList({ page })
.then((response) => {
// issue with API layer, conversion for items is not happening
response.items = response.items.map((item) =>
GetTranscriptFromJSON(item),
);
setResults(response);
setIsLoading(false);
})
.catch(() => {
setResults(null);
setIsLoading(false);
});
}, [page, isAuthenticated]);
const { loading, response } = useTranscriptList(page);
return (
<div>
@@ -52,12 +26,12 @@ export default function TranscriptBrowser() {
<Pagination
page={page}
setPage={setPage}
total={results?.total || 0}
size={results?.size || 0}
total={response?.total || 0}
size={response?.size || 0}
/>
</div>
{isLoading && (
{loading && (
<div className="full-screen flex flex-col items-center justify-center">
<FontAwesomeIcon
icon={faGear}
@@ -65,7 +39,7 @@ export default function TranscriptBrowser() {
/>
</div>
)}
{!isLoading && !results ? (
{!loading && !response && (
<div className="text-gray-500">
No transcripts found, but you can&nbsp;
<Link href="/transcripts/new" className="underline">
@@ -73,12 +47,10 @@ export default function TranscriptBrowser() {
</Link>
&nbsp;to get started.
</div>
) : (
<></>
)}
<div /** center and max 900px wide */ className="mx-auto max-w-[900px]">
<div className="grid grid-cols-1 gap-2 lg:gap-4 h-full">
{results?.items.map((item: GetTranscript) => (
{response?.items.map((item: GetTranscript) => (
<div
key={item.id}
className="flex flex-col bg-blue-400/20 rounded-lg md:rounded-xl p-2 md:px-4"

View File

@@ -1,20 +1,18 @@
"use client";
import Modal from "../modal";
import getApi from "../../../lib/getApi";
import useTranscript from "../useTranscript";
import useTopics from "../useTopics";
import useWaveform from "../useWaveform";
import useMp3 from "../useMp3";
import { TopicList } from "../topicList";
import Recorder from "../recorder";
import { Topic } from "../webSocketTypes";
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import "../../../styles/button.css";
import FinalSummary from "../finalSummary";
import ShareLink from "../shareLink";
import QRCode from "react-qr-code";
import TranscriptTitle from "../transcriptTitle";
import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react";
import { featureEnabled } from "../../domainContext";
type TranscriptDetails = {
params: {
@@ -22,20 +20,16 @@ type TranscriptDetails = {
};
};
export default function TranscriptDetails(details: TranscriptDetails) {
const isAuthenticated = useFiefIsAuthenticated();
const api = getApi();
const [transcriptId, setTranscriptId] = useState<string>("");
const transcript = useTranscript(api, transcriptId);
const topics = useTopics(api, transcriptId);
const waveform = useWaveform(api, transcriptId);
const useActiveTopic = useState<Topic | null>(null);
const requireLogin = featureEnabled("requireLogin");
const protectedPath = true;
useEffect(() => {
if (requireLogin && !isAuthenticated) return;
setTranscriptId(details.params.transcriptId);
}, [api, details.params.transcriptId, isAuthenticated]);
export default function TranscriptDetails(details: TranscriptDetails) {
const transcriptId = details.params.transcriptId;
const transcript = useTranscript(protectedPath, transcriptId);
const topics = useTopics(protectedPath, transcriptId);
const waveform = useWaveform(protectedPath, transcriptId);
const useActiveTopic = useState<Topic | null>(null);
const mp3 = useMp3(protectedPath, transcriptId);
if (transcript?.error /** || topics?.error || waveform?.error **/) {
return (
@@ -70,6 +64,7 @@ export default function TranscriptDetails(details: TranscriptDetails) {
waveform={waveform?.waveform}
isPastMeeting={true}
transcriptId={transcript?.response?.id}
mp3Blob={mp3.blob}
/>
)}
</div>

View File

@@ -36,9 +36,8 @@ const TranscriptRecord = (details: TranscriptDetails) => {
}
}, []);
const api = getApi();
const transcript = useTranscript(api, details.params.transcriptId);
const webRTC = useWebRTC(stream, details.params.transcriptId, api);
const transcript = useTranscript(true, details.params.transcriptId);
const webRTC = useWebRTC(stream, details.params.transcriptId, true);
const webSockets = useWebSockets(details.params.transcriptId);
const { audioDevices, getAudioStream } = useAudioDevice();

View File

@@ -19,10 +19,10 @@ const useCreateTranscript = (): CreateTranscript => {
const [loading, setLoading] = useState<boolean>(false);
const [error, setErrorState] = useState<Error | null>(null);
const { setError } = useError();
const api = getApi();
const api = getApi(true);
const create = (params: V1TranscriptsCreateRequest["createTranscript"]) => {
if (loading) return;
if (loading || !api) return;
setLoading(true);
const requestParameters: V1TranscriptsCreateRequest = {

View File

@@ -15,6 +15,7 @@ import AudioInputsDropdown from "./audioInputsDropdown";
import { Option } from "react-dropdown";
import { useError } from "../../(errors)/errorContext";
import { waveSurferStyles } from "../../styles/recorder";
import useMp3 from "./useMp3";
type RecorderProps = {
setStream?: React.Dispatch<React.SetStateAction<MediaStream | null>>;
@@ -29,6 +30,7 @@ type RecorderProps = {
waveform?: AudioWaveform | null;
isPastMeeting: boolean;
transcriptId?: string | null;
mp3Blob?: Blob | null;
};
export default function Recorder(props: RecorderProps) {
@@ -107,11 +109,7 @@ export default function Recorder(props: RecorderProps) {
if (waveformRef.current) {
const _wavesurfer = WaveSurfer.create({
container: waveformRef.current,
url: props.transcriptId
? `${process.env.NEXT_PUBLIC_API_URL}/v1/transcripts/${props.transcriptId}/audio/mp3`
: undefined,
peaks: props.waveform?.data,
hideScrollbar: true,
autoCenter: true,
barWidth: 2,
@@ -145,6 +143,10 @@ export default function Recorder(props: RecorderProps) {
if (props.isPastMeeting) _wavesurfer.toggleInteraction(true);
if (props.mp3Blob) {
_wavesurfer.loadBlob(props.mp3Blob);
}
setWavesurfer(_wavesurfer);
return () => {
@@ -156,6 +158,12 @@ export default function Recorder(props: RecorderProps) {
}
}, []);
useEffect(() => {
if (!wavesurfer) return;
if (!props.mp3Blob) return;
wavesurfer.loadBlob(props.mp3Blob);
}, [props.mp3Blob]);
useEffect(() => {
topicsRef.current = props.topics;
if (!isRecording) renderMarkers();

View File

@@ -1,36 +1,68 @@
import { useEffect, useState } from "react";
import { useContext, useEffect, useState } from "react";
import {
DefaultApi,
V1TranscriptGetAudioMp3Request,
// V1TranscriptGetAudioMp3Request,
} from "../../api/apis/DefaultApi";
import {} from "../../api";
import { useError } from "../../(errors)/errorContext";
import { DomainContext } from "../domainContext";
import getApi from "../../lib/getApi";
import { useFiefAccessTokenInfo } from "@fief/fief/build/esm/nextjs/react";
type Mp3Response = {
url: string | null;
blob: Blob | null;
loading: boolean;
error: Error | null;
};
const useMp3 = (api: DefaultApi, id: string): Mp3Response => {
const useMp3 = (protectedPath: boolean, id: string): Mp3Response => {
const [url, setUrl] = useState<string | null>(null);
const [blob, setBlob] = useState<Blob | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setErrorState] = useState<Error | null>(null);
const { setError } = useError();
const api = getApi(protectedPath);
const { api_url } = useContext(DomainContext);
const accessTokenInfo = useFiefAccessTokenInfo();
const getMp3 = (id: string) => {
if (!id) throw new Error("Transcript ID is required to get transcript Mp3");
if (!id || !api) return;
setLoading(true);
const requestParameters: V1TranscriptGetAudioMp3Request = {
transcriptId: id,
};
api
.v1TranscriptGetAudioMp3(requestParameters)
.then((result) => {
setUrl(result);
setLoading(false);
console.debug("Transcript Mp3 loaded:", result);
// XXX Current API interface does not output a blob, we need to to is manually
// const requestParameters: V1TranscriptGetAudioMp3Request = {
// transcriptId: id,
// };
// api
// .v1TranscriptGetAudioMp3(requestParameters)
// .then((result) => {
// setUrl(result);
// setLoading(false);
// console.debug("Transcript Mp3 loaded:", result);
// })
// .catch((err) => {
// setError(err);
// setErrorState(err);
// });
const localUrl = `${api_url}/v1/transcripts/${id}/audio/mp3`;
if (localUrl == url) return;
const headers = new Headers();
if (accessTokenInfo) {
headers.set("Authorization", "Bearer " + accessTokenInfo.access_token);
}
fetch(localUrl, {
method: "GET",
headers,
})
.then((response) => {
setUrl(localUrl);
response.blob().then((blob) => {
setBlob(blob);
setLoading(false);
});
})
.catch((err) => {
setError(err);
@@ -40,9 +72,9 @@ const useMp3 = (api: DefaultApi, id: string): Mp3Response => {
useEffect(() => {
getMp3(id);
}, [id]);
}, [id, api]);
return { url, loading, error };
return { url, blob, loading, error };
};
export default useMp3;

View File

@@ -5,6 +5,7 @@ import {
} from "../../api/apis/DefaultApi";
import { useError } from "../../(errors)/errorContext";
import { Topic } from "./webSocketTypes";
import getApi from "../../lib/getApi";
type TranscriptTopics = {
topics: Topic[] | null;
@@ -12,14 +13,15 @@ type TranscriptTopics = {
error: Error | null;
};
const useTopics = (api: DefaultApi, id: string): TranscriptTopics => {
const useTopics = (protectedPath, id: string): TranscriptTopics => {
const [topics, setTopics] = useState<Topic[] | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [error, setErrorState] = useState<Error | null>(null);
const { setError } = useError();
const api = getApi(protectedPath);
const getTopics = (id: string) => {
if (!id) return;
useEffect(() => {
if (!id || !api) return;
setLoading(true);
const requestParameters: V1TranscriptGetTopicsRequest = {
@@ -36,11 +38,7 @@ const useTopics = (api: DefaultApi, id: string): TranscriptTopics => {
setError(err);
setErrorState(err);
});
};
useEffect(() => {
getTopics(id);
}, [id]);
}, [id, api]);
return { topics, loading, error };
};

View File

@@ -1,7 +1,8 @@
import { useEffect, useState } from "react";
import { DefaultApi, V1TranscriptGetRequest } from "../../api/apis/DefaultApi";
import { V1TranscriptGetRequest } from "../../api/apis/DefaultApi";
import { GetTranscript } from "../../api";
import { useError } from "../../(errors)/errorContext";
import getApi from "../../lib/getApi";
type Transcript = {
response: GetTranscript | null;
@@ -9,14 +10,18 @@ type Transcript = {
error: Error | null;
};
const useTranscript = (api: DefaultApi, id: string | null): Transcript => {
const useTranscript = (
protectedPath: boolean,
id: string | null,
): Transcript => {
const [response, setResponse] = useState<GetTranscript | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(true);
const [error, setErrorState] = useState<Error | null>(null);
const { setError } = useError();
const api = getApi(protectedPath);
const getTranscript = (id: string | null) => {
if (!id) return;
useEffect(() => {
if (!id || !api) return;
setLoading(true);
const requestParameters: V1TranscriptGetRequest = {
@@ -33,11 +38,7 @@ const useTranscript = (api: DefaultApi, id: string | null): Transcript => {
setError(err);
setErrorState(err);
});
};
useEffect(() => {
getTranscript(id);
}, [id]);
}, [id, !api]);
return { response, loading, error };
};

View File

@@ -0,0 +1,44 @@
import { useEffect, useState } from "react";
import { GetTranscriptFromJSON, PageGetTranscript } from "../../api";
import { useError } from "../../(errors)/errorContext";
import getApi from "../../lib/getApi";
type TranscriptList = {
response: PageGetTranscript | null;
loading: boolean;
error: Error | null;
};
//always protected
const useTranscriptList = (page: number): TranscriptList => {
const [response, setResponse] = useState<PageGetTranscript | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setErrorState] = useState<Error | null>(null);
const { setError } = useError();
const api = getApi(true);
useEffect(() => {
if (!api) return;
setLoading(true);
api
.v1TranscriptsList({ page })
.then((response) => {
// issue with API layer, conversion for items is not happening
response.items = response.items.map((item) =>
GetTranscriptFromJSON(item),
);
setResponse(response);
setLoading(false);
})
.catch((err) => {
setResponse(null);
setLoading(false);
setError(err);
setErrorState(err);
});
}, [!api, page]);
return { response, loading, error };
};
export default useTranscriptList;

View File

@@ -5,6 +5,7 @@ import {
} from "../../api/apis/DefaultApi";
import { AudioWaveform } from "../../api";
import { useError } from "../../(errors)/errorContext";
import getApi from "../../lib/getApi";
type AudioWaveFormResponse = {
waveform: AudioWaveform | null;
@@ -12,14 +13,15 @@ type AudioWaveFormResponse = {
error: Error | null;
};
const useWaveform = (api: DefaultApi, id: string): AudioWaveFormResponse => {
const useWaveform = (protectedPath, id: string): AudioWaveFormResponse => {
const [waveform, setWaveform] = useState<AudioWaveform | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(true);
const [error, setErrorState] = useState<Error | null>(null);
const { setError } = useError();
const api = getApi(protectedPath);
const getWaveform = (id: string) => {
if (!id) return;
useEffect(() => {
if (!id || !api) return;
setLoading(true);
const requestParameters: V1TranscriptGetAudioWaveformRequest = {
@@ -36,11 +38,7 @@ const useWaveform = (api: DefaultApi, id: string): AudioWaveFormResponse => {
setError(err);
setErrorState(err);
});
};
useEffect(() => {
getWaveform(id);
}, [id]);
}, [id, api]);
return { waveform, loading, error };
};

View File

@@ -5,14 +5,16 @@ import {
V1TranscriptRecordWebrtcRequest,
} from "../../api/apis/DefaultApi";
import { useError } from "../../(errors)/errorContext";
import getApi from "../../lib/getApi";
const useWebRTC = (
stream: MediaStream | null,
transcriptId: string | null,
api: DefaultApi,
protectedPath,
): Peer => {
const [peer, setPeer] = useState<Peer | null>(null);
const { setError } = useError();
const api = getApi(protectedPath);
useEffect(() => {
if (!stream || !transcriptId) {
@@ -35,6 +37,7 @@ const useWebRTC = (
});
p.on("signal", (data: any) => {
if (!api) return;
if ("sdp" in data) {
const requestParameters: V1TranscriptRecordWebrtcRequest = {
transcriptId: transcriptId,

View File

@@ -3,7 +3,7 @@ import { isDevelopment } from "./utils";
const localConfig = {
features: {
requireLogin: true,
requireLogin: false,
privacy: true,
browse: true,
},

View File

@@ -2,21 +2,32 @@ import { Configuration } from "../api/runtime";
import { DefaultApi } from "../api/apis/DefaultApi";
import { useFiefAccessTokenInfo } from "@fief/fief/nextjs/react";
import { useContext } from "react";
import { DomainContext } from "../[domain]/domainContext";
import { useContext, useEffect, useState } from "react";
import { DomainContext, featureEnabled } from "../[domain]/domainContext";
export default function getApi(): DefaultApi {
export default function getApi(protectedPath: boolean): DefaultApi | undefined {
const accessTokenInfo = useFiefAccessTokenInfo();
const api_url = useContext(DomainContext).api_url;
const requireLogin = featureEnabled("requireLogin");
const [api, setApi] = useState<DefaultApi>();
if (!api_url) throw new Error("no API URL");
const apiConfiguration = new Configuration({
basePath: api_url,
accessToken: accessTokenInfo
? "Bearer " + accessTokenInfo.access_token
: undefined,
});
const api = new DefaultApi(apiConfiguration);
useEffect(() => {
// console.log('trying auth', protectedPath, requireLogin, accessTokenInfo)
if (protectedPath && requireLogin && !accessTokenInfo) {
// console.log('waiting auth')
return;
}
const apiConfiguration = new Configuration({
basePath: api_url,
accessToken: accessTokenInfo
? "Bearer " + accessTokenInfo.access_token
: undefined,
});
setApi(new DefaultApi(apiConfiguration));
}, [!accessTokenInfo, protectedPath]);
return api;
}

View File

@@ -35,7 +35,7 @@
"supports-color": "^9.4.0",
"tailwindcss": "^3.3.2",
"typescript": "^5.1.6",
"wavesurfer.js": "^7.0.3"
"wavesurfer.js": "^7.4.2"
},
"main": "index.js",
"repository": "https://github.com/Monadical-SAS/reflector-ui.git",

View File

@@ -2638,10 +2638,10 @@ watchpack@2.4.0:
glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2"
wavesurfer.js@^7.0.3:
version "7.0.3"
resolved "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.0.3.tgz"
integrity sha512-gJ3P+Bd3Q4E8qETjjg0pneaVqm2J7jegG2Cc6vqEF5YDDKQ3m8sKsvVfgVhJkacKkO9jFAGDu58Hw4zLr7xD0A==
wavesurfer.js@^7.4.2:
version "7.4.2"
resolved "https://registry.yarnpkg.com/wavesurfer.js/-/wavesurfer.js-7.4.2.tgz#59f5c87193d4eeeb199858688ddac1ad7ba86b3a"
integrity sha512-4pNQ1porOCUBYBmd2F1TqVuBnB2wBPipaw2qI920zYLuPnada0Rd1CURgh8HRuPGKxijj2iyZDFN2UZwsaEuhA==
wcwidth@>=1.0.1, wcwidth@^1.0.1:
version "1.0.1"