mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
self-review-fix
This commit is contained in:
@@ -98,7 +98,7 @@ export default function FilterSidebar({
|
|||||||
<Link
|
<Link
|
||||||
as={NextLink}
|
as={NextLink}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={() => onFilterChange("live" as SourceKind, "")}
|
onClick={() => onFilterChange("live", "")}
|
||||||
color={selectedSourceKind === "live" ? "blue.500" : "gray.600"}
|
color={selectedSourceKind === "live" ? "blue.500" : "gray.600"}
|
||||||
_hover={{ color: "blue.300" }}
|
_hover={{ color: "blue.300" }}
|
||||||
fontWeight={selectedSourceKind === "live" ? "bold" : "normal"}
|
fontWeight={selectedSourceKind === "live" ? "bold" : "normal"}
|
||||||
@@ -109,7 +109,7 @@ export default function FilterSidebar({
|
|||||||
<Link
|
<Link
|
||||||
as={NextLink}
|
as={NextLink}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={() => onFilterChange("file" as SourceKind, "")}
|
onClick={() => onFilterChange("file", "")}
|
||||||
color={selectedSourceKind === "file" ? "blue.500" : "gray.600"}
|
color={selectedSourceKind === "file" ? "blue.500" : "gray.600"}
|
||||||
_hover={{ color: "blue.300" }}
|
_hover={{ color: "blue.300" }}
|
||||||
fontWeight={selectedSourceKind === "file" ? "bold" : "normal"}
|
fontWeight={selectedSourceKind === "file" ? "bold" : "normal"}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import Pagination, {
|
|||||||
import TranscriptCards from "./_components/TranscriptCards";
|
import TranscriptCards from "./_components/TranscriptCards";
|
||||||
import DeleteTranscriptDialog from "./_components/DeleteTranscriptDialog";
|
import DeleteTranscriptDialog from "./_components/DeleteTranscriptDialog";
|
||||||
import { formatLocalDate } from "../../lib/time";
|
import { formatLocalDate } from "../../lib/time";
|
||||||
import { RECORD_A_MEETING_URL } from "../../lib/constants";
|
import { RECORD_A_MEETING_URL } from "../../api/urls";
|
||||||
|
|
||||||
const SEARCH_FORM_QUERY_INPUT_NAME = "query" as const;
|
const SEARCH_FORM_QUERY_INPUT_NAME = "query" as const;
|
||||||
|
|
||||||
@@ -298,24 +298,11 @@ export default function TranscriptBrowser() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleProcessTranscript = (transcriptId: string) => {
|
const handleProcessTranscript = (transcriptId: string) => {
|
||||||
processTranscript.mutate(
|
processTranscript.mutate({
|
||||||
{
|
params: {
|
||||||
params: {
|
path: { transcript_id: transcriptId },
|
||||||
path: { transcript_id: transcriptId },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
onSuccess: (result) => {
|
|
||||||
const status =
|
|
||||||
result && typeof result === "object" && "status" in result
|
|
||||||
? (result as { status: string }).status
|
|
||||||
: undefined;
|
|
||||||
if (status === "already running") {
|
|
||||||
// Note: setError is already handled in the hook
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const transcriptToDelete = results?.find(
|
const transcriptToDelete = results?.find(
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ import { Container, Flex, Link } from "@chakra-ui/react";
|
|||||||
import { getConfig } from "../lib/edgeConfig";
|
import { getConfig } from "../lib/edgeConfig";
|
||||||
import NextLink from "next/link";
|
import NextLink from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import About from "../(aboutAndPrivacy)/about";
|
|
||||||
import Privacy from "../(aboutAndPrivacy)/privacy";
|
|
||||||
import UserInfo from "../(auth)/userInfo";
|
import UserInfo from "../(auth)/userInfo";
|
||||||
import AuthWrapper from "./AuthWrapper";
|
import AuthWrapper from "./AuthWrapper";
|
||||||
import { RECORD_A_MEETING_URL } from "../lib/constants";
|
import { RECORD_A_MEETING_URL } from "../api/urls";
|
||||||
|
|
||||||
export default async function AppLayout({
|
export default async function AppLayout({
|
||||||
children,
|
children,
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ import {
|
|||||||
const TranscriptCreate = () => {
|
const TranscriptCreate = () => {
|
||||||
const isClient = typeof window !== "undefined";
|
const isClient = typeof window !== "undefined";
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { isLoading, isAuthenticated } = useSessionStatus();
|
const status = useSessionStatus();
|
||||||
|
const isAuthenticated = status === "authenticated";
|
||||||
|
const isLoading = status === "loading";
|
||||||
const requireLogin = featureEnabled("requireLogin");
|
const requireLogin = featureEnabled("requireLogin");
|
||||||
|
|
||||||
const [name, setName] = useState<string>("");
|
const [name, setName] = useState<string>("");
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import RecordPlugin from "../../lib/custom-plugins/record";
|
|||||||
import { formatTime, formatTimeMs } from "../../lib/time";
|
import { formatTime, formatTimeMs } from "../../lib/time";
|
||||||
import { waveSurferStyles } from "../../styles/recorder";
|
import { waveSurferStyles } from "../../styles/recorder";
|
||||||
import { useError } from "../../(errors)/errorContext";
|
import { useError } from "../../(errors)/errorContext";
|
||||||
import FileUploadButton from "./fileUploadButton";
|
|
||||||
import useWebRTC from "./useWebRTC";
|
import useWebRTC from "./useWebRTC";
|
||||||
import useAudioDevice from "./useAudioDevice";
|
import useAudioDevice from "./useAudioDevice";
|
||||||
import { Box, Flex, IconButton, Menu, RadioGroup } from "@chakra-ui/react";
|
import { Box, Flex, IconButton, Menu, RadioGroup } from "@chakra-ui/react";
|
||||||
import { LuScreenShare, LuMic, LuPlay, LuCircleStop } from "react-icons/lu";
|
import { LuScreenShare, LuMic, LuPlay, LuCircleStop } from "react-icons/lu";
|
||||||
import { RECORD_A_MEETING_URL } from "../../lib/constants";
|
import { RECORD_A_MEETING_URL } from "../../api/urls";
|
||||||
|
|
||||||
type RecorderProps = {
|
type RecorderProps = {
|
||||||
transcriptId: string;
|
transcriptId: string;
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export default function ShareZulip(props: ShareZulipProps & BoxProps) {
|
|||||||
const [topic, setTopic] = useState<string | undefined>(undefined);
|
const [topic, setTopic] = useState<string | undefined>(undefined);
|
||||||
const [includeTopics, setIncludeTopics] = useState(false);
|
const [includeTopics, setIncludeTopics] = useState(false);
|
||||||
|
|
||||||
// React Query hooks
|
|
||||||
const { data: streams = [], isLoading: isLoadingStreams } = useZulipStreams();
|
const { data: streams = [], isLoading: isLoadingStreams } = useZulipStreams();
|
||||||
const { data: topics = [] } = useZulipTopics(selectedStreamId);
|
const { data: topics = [] } = useZulipTopics(selectedStreamId);
|
||||||
const postToZulipMutation = useTranscriptPostToZulip();
|
const postToZulipMutation = useTranscriptPostToZulip();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useContext, useEffect, useState } from "react";
|
|||||||
import { DomainContext } from "../../domainContext";
|
import { DomainContext } from "../../domainContext";
|
||||||
import { useTranscriptGet } from "../../lib/apiHooks";
|
import { useTranscriptGet } from "../../lib/apiHooks";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
|
import { assertExtendedToken } from "../../lib/types";
|
||||||
|
|
||||||
export type Mp3Response = {
|
export type Mp3Response = {
|
||||||
media: HTMLMediaElement | null;
|
media: HTMLMediaElement | null;
|
||||||
@@ -21,9 +22,11 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
|
|||||||
const [audioDeleted, setAudioDeleted] = useState<boolean | null>(null);
|
const [audioDeleted, setAudioDeleted] = useState<boolean | null>(null);
|
||||||
const { api_url } = useContext(DomainContext);
|
const { api_url } = useContext(DomainContext);
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const accessTokenInfo = (session as any)?.accessToken as string | undefined;
|
const sessionExtended =
|
||||||
|
session === null ? null : assertExtendedToken(session);
|
||||||
|
const accessTokenInfo =
|
||||||
|
sessionExtended === null ? null : sessionExtended.accessToken;
|
||||||
|
|
||||||
// Use React Query to fetch transcript metadata
|
|
||||||
const {
|
const {
|
||||||
data: transcript,
|
data: transcript,
|
||||||
isLoading: transcriptMetadataLoading,
|
isLoading: transcriptMetadataLoading,
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
// Wrapper for backward compatibility
|
|
||||||
import type { components } from "../../reflector-api";
|
|
||||||
type SearchResult = components["schemas"]["SearchResult"];
|
|
||||||
type SourceKind = components["schemas"]["SourceKind"];
|
|
||||||
import { useTranscriptsSearch } from "../../lib/apiHooks";
|
|
||||||
import {
|
|
||||||
PaginationPage,
|
|
||||||
paginationPageTo0Based,
|
|
||||||
} from "../browse/_components/Pagination";
|
|
||||||
|
|
||||||
interface SearchFilters {
|
|
||||||
roomIds: readonly string[] | null;
|
|
||||||
sourceKind: SourceKind | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
type UseSearchTranscriptsOptions = {
|
|
||||||
pageSize: number;
|
|
||||||
page: PaginationPage;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface UseSearchTranscriptsReturn {
|
|
||||||
results: SearchResult[];
|
|
||||||
totalCount: number;
|
|
||||||
isLoading: boolean;
|
|
||||||
error: unknown;
|
|
||||||
reload: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSearchTranscripts(
|
|
||||||
query: string = "",
|
|
||||||
filters: SearchFilters = { roomIds: null, sourceKind: null },
|
|
||||||
options: UseSearchTranscriptsOptions = {
|
|
||||||
pageSize: 20,
|
|
||||||
page: PaginationPage(1),
|
|
||||||
},
|
|
||||||
): UseSearchTranscriptsReturn {
|
|
||||||
const { pageSize, page } = options;
|
|
||||||
|
|
||||||
const { data, isLoading, error, refetch } = useTranscriptsSearch(query, {
|
|
||||||
limit: pageSize,
|
|
||||||
offset: paginationPageTo0Based(page) * pageSize,
|
|
||||||
room_id: filters.roomIds?.[0],
|
|
||||||
source_kind: filters.sourceKind || undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
results: data?.results || [],
|
|
||||||
totalCount: data?.total || 0,
|
|
||||||
isLoading,
|
|
||||||
error,
|
|
||||||
reload: refetch,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -37,7 +37,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
|||||||
const [status, setStatus] = useState<Status>({ value: "" });
|
const [status, setStatus] = useState<Status>({ value: "" });
|
||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
|
|
||||||
const { websocket_url } = useContext(DomainContext);
|
const { websocket_url: websocketUrl } = useContext(DomainContext);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [accumulatedText, setAccumulatedText] = useState<string>("");
|
const [accumulatedText, setAccumulatedText] = useState<string>("");
|
||||||
@@ -328,7 +328,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
|||||||
|
|
||||||
if (!transcriptId) return;
|
if (!transcriptId) return;
|
||||||
|
|
||||||
const url = `${websocket_url}/v1/transcripts/${transcriptId}/events`;
|
const url = `${websocketUrl}/v1/transcripts/${transcriptId}/events`;
|
||||||
let ws = new WebSocket(url);
|
let ws = new WebSocket(url);
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
@@ -489,7 +489,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
|||||||
return () => {
|
return () => {
|
||||||
ws.close();
|
ws.close();
|
||||||
};
|
};
|
||||||
}, [transcriptId, websocket_url, queryClient]);
|
}, [transcriptId, websocketUrl, queryClient]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transcriptTextLive,
|
transcriptTextLive,
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import useSessionStatus from "../lib/useSessionStatus";
|
|||||||
import { Spinner, Link } from "@chakra-ui/react";
|
import { Spinner, Link } from "@chakra-ui/react";
|
||||||
|
|
||||||
export default function UserInfo() {
|
export default function UserInfo() {
|
||||||
const { isLoading, isAuthenticated } = useSessionStatus();
|
const status = useSessionStatus();
|
||||||
|
const isLoading = status === "loading";
|
||||||
|
const isAuthenticated = status === "authenticated";
|
||||||
return isLoading ? (
|
return isLoading ? (
|
||||||
<Spinner size="xs" className="mx-3" />
|
<Spinner size="xs" className="mx-3" />
|
||||||
) : !isAuthenticated ? (
|
) : !isAuthenticated ? (
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
// Application-wide constants
|
|
||||||
export const RECORD_A_MEETING_URL = "/transcripts/new" as const;
|
export const RECORD_A_MEETING_URL = "/transcripts/new" as const;
|
||||||
@@ -4,6 +4,7 @@ import { useEffect } from "react";
|
|||||||
import { configureApiAuth } from "./apiClient";
|
import { configureApiAuth } from "./apiClient";
|
||||||
import useSessionAccessToken from "./useSessionAccessToken";
|
import useSessionAccessToken from "./useSessionAccessToken";
|
||||||
|
|
||||||
|
// TODO should be context
|
||||||
export function ApiAuthProvider({ children }: { children: React.ReactNode }) {
|
export function ApiAuthProvider({ children }: { children: React.ReactNode }) {
|
||||||
const { accessToken } = useSessionAccessToken();
|
const { accessToken } = useSessionAccessToken();
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import createFetchClient from "openapi-react-query";
|
|||||||
// Create the base openapi-fetch client with a default URL
|
// Create the base openapi-fetch client with a default URL
|
||||||
// The actual URL will be set via middleware in ApiAuthProvider
|
// The actual URL will be set via middleware in ApiAuthProvider
|
||||||
export const client = createClient<paths>({
|
export const client = createClient<paths>({
|
||||||
baseUrl: "http://127.0.0.1:1250",
|
baseUrl: "http://192.0.2.1:1250",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const $api = createFetchClient<paths>(client);
|
export const $api = createFetchClient<paths>(client);
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
import { AuthOptions } from "next-auth";
|
import { AuthOptions } from "next-auth";
|
||||||
import AuthentikProvider from "next-auth/providers/authentik";
|
import AuthentikProvider from "next-auth/providers/authentik";
|
||||||
import { JWT } from "next-auth/jwt";
|
import { JWT } from "next-auth/jwt";
|
||||||
import { JWTWithAccessToken, CustomSession } from "./types";
|
import {
|
||||||
|
JWTWithAccessToken,
|
||||||
|
CustomSession,
|
||||||
|
assertExtendedToken,
|
||||||
|
} from "./types";
|
||||||
|
import {
|
||||||
|
assertExistsAndNonEmptyString,
|
||||||
|
parseMaybeNonEmptyString,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
const PRETIMEOUT = 60; // seconds before token expires to refresh it
|
const PRETIMEOUT = 60; // seconds before token expires to refresh it
|
||||||
|
|
||||||
@@ -15,11 +23,18 @@ const TOKEN_CACHE_TTL = 60 * 60 * 24 * 30 * 1000; // 30 days in milliseconds
|
|||||||
// Simple lock mechanism to prevent concurrent token refreshes
|
// Simple lock mechanism to prevent concurrent token refreshes
|
||||||
const refreshLocks = new Map<string, Promise<JWTWithAccessToken>>();
|
const refreshLocks = new Map<string, Promise<JWTWithAccessToken>>();
|
||||||
|
|
||||||
|
const CLIENT_ID = assertExistsAndNonEmptyString(
|
||||||
|
process.env.AUTHENTIK_CLIENT_ID,
|
||||||
|
);
|
||||||
|
const CLIENT_SECRET = assertExistsAndNonEmptyString(
|
||||||
|
process.env.AUTHENTIK_CLIENT_SECRET,
|
||||||
|
);
|
||||||
|
|
||||||
export const authOptions: AuthOptions = {
|
export const authOptions: AuthOptions = {
|
||||||
providers: [
|
providers: [
|
||||||
AuthentikProvider({
|
AuthentikProvider({
|
||||||
clientId: process.env.AUTHENTIK_CLIENT_ID as string,
|
clientId: CLIENT_ID,
|
||||||
clientSecret: process.env.AUTHENTIK_CLIENT_SECRET as string,
|
clientSecret: CLIENT_SECRET,
|
||||||
issuer: process.env.AUTHENTIK_ISSUER,
|
issuer: process.env.AUTHENTIK_ISSUER,
|
||||||
authorization: {
|
authorization: {
|
||||||
params: {
|
params: {
|
||||||
@@ -33,23 +48,28 @@ export const authOptions: AuthOptions = {
|
|||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
async jwt({ token, account, user }) {
|
async jwt({ token, account, user }) {
|
||||||
const extendedToken = token as JWTWithAccessToken;
|
const extendedToken = assertExtendedToken(token);
|
||||||
|
const KEY = `token:${token.sub}`;
|
||||||
if (account && user) {
|
if (account && user) {
|
||||||
// called only on first login
|
// called only on first login
|
||||||
// XXX account.expires_in used in example is not defined for authentik backend, but expires_at is
|
// XXX account.expires_in used in example is not defined for authentik backend, but expires_at is
|
||||||
const expiresAt = (account.expires_at as number) - PRETIMEOUT;
|
const expiresAt = (account.expires_at as number) - PRETIMEOUT;
|
||||||
const jwtToken: JWTWithAccessToken = {
|
if (!account.access_token) {
|
||||||
...extendedToken,
|
tokenCache.delete(KEY);
|
||||||
accessToken: account.access_token || "",
|
} else {
|
||||||
accessTokenExpires: expiresAt * 1000,
|
const jwtToken: JWTWithAccessToken = {
|
||||||
refreshToken: account.refresh_token || "",
|
...extendedToken,
|
||||||
};
|
accessToken: account.access_token,
|
||||||
// Store in memory cache
|
accessTokenExpires: expiresAt * 1000,
|
||||||
tokenCache.set(`token:${jwtToken.sub}`, {
|
refreshToken: account.refresh_token || "",
|
||||||
token: jwtToken,
|
};
|
||||||
timestamp: Date.now(),
|
// Store in memory cache
|
||||||
});
|
tokenCache.set(`token:${jwtToken.sub}`, {
|
||||||
return jwtToken;
|
token: jwtToken,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
return jwtToken;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Date.now() < extendedToken.accessTokenExpires) {
|
if (Date.now() < extendedToken.accessTokenExpires) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Session } from "next-auth";
|
import { Session } from "next-auth";
|
||||||
import { JWT } from "next-auth/jwt";
|
import { JWT } from "next-auth/jwt";
|
||||||
|
import { parseMaybeNonEmptyString } from "./utils";
|
||||||
|
|
||||||
export interface JWTWithAccessToken extends JWT {
|
export interface JWTWithAccessToken extends JWT {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
@@ -18,3 +19,28 @@ export interface CustomSession extends Session {
|
|||||||
email?: string | null;
|
email?: string | null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assumption that JWT is JWTWithAccessToken - not ideal, TODO find a reason we have to do that
|
||||||
|
export const assertExtendedToken = <T>(
|
||||||
|
t: T,
|
||||||
|
): T & {
|
||||||
|
accessTokenExpires: number;
|
||||||
|
accessToken: string;
|
||||||
|
} => {
|
||||||
|
if (
|
||||||
|
typeof (t as { accessTokenExpires: any }).accessTokenExpires === "number" &&
|
||||||
|
!isNaN((t as { accessTokenExpires: any }).accessTokenExpires) &&
|
||||||
|
typeof (
|
||||||
|
t as {
|
||||||
|
accessToken: any;
|
||||||
|
}
|
||||||
|
).accessToken === "string" &&
|
||||||
|
parseMaybeNonEmptyString((t as { accessToken: any }).accessToken) !== null
|
||||||
|
) {
|
||||||
|
return t as T & {
|
||||||
|
accessTokenExpires: number;
|
||||||
|
accessToken: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error("Token is not extended with access token");
|
||||||
|
};
|
||||||
|
|||||||
@@ -10,13 +10,16 @@ import { isAuthConfigured } from "./apiClient";
|
|||||||
* Prevents race conditions where React Query fires requests before the token is set.
|
* Prevents race conditions where React Query fires requests before the token is set.
|
||||||
*/
|
*/
|
||||||
export default function useAuthReady() {
|
export default function useAuthReady() {
|
||||||
const { status, isAuthenticated } = useSessionStatus();
|
const status = useSessionStatus();
|
||||||
|
const isAuthenticated = status === "authenticated";
|
||||||
const [authReady, setAuthReady] = useState(false);
|
const [authReady, setAuthReady] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let ready_ = false;
|
||||||
// Check if both session is authenticated and token is configured
|
// Check if both session is authenticated and token is configured
|
||||||
const checkAuthReady = () => {
|
const checkAuthReady = () => {
|
||||||
const ready = isAuthenticated && isAuthConfigured();
|
const ready = isAuthenticated && isAuthConfigured();
|
||||||
|
ready_ = ready;
|
||||||
setAuthReady(ready);
|
setAuthReady(ready);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,7 +30,14 @@ export default function useAuthReady() {
|
|||||||
const interval = setInterval(checkAuthReady, 100);
|
const interval = setInterval(checkAuthReady, 100);
|
||||||
|
|
||||||
// Stop checking after 2 seconds (auth should be ready by then)
|
// Stop checking after 2 seconds (auth should be ready by then)
|
||||||
const timeout = setTimeout(() => clearInterval(interval), 2000);
|
const timeout = setTimeout(() => {
|
||||||
|
if (ready_) {
|
||||||
|
clearInterval(interval);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
console.warn("Auth not ready after 2 seconds");
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
|
|||||||
@@ -1,22 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { useSession as useNextAuthSession } from "next-auth/react";
|
import { useSession as useNextAuthSession } from "next-auth/react";
|
||||||
import { Session } from "next-auth";
|
|
||||||
|
|
||||||
export default function useSessionStatus() {
|
export default function useSessionStatus() {
|
||||||
const { status: naStatus } = useNextAuthSession();
|
const { status } = useNextAuthSession();
|
||||||
const [status, setStatus] = useState<typeof naStatus>("loading");
|
return status;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (naStatus !== "loading" && naStatus !== status) {
|
|
||||||
setStatus(naStatus);
|
|
||||||
}
|
|
||||||
}, [naStatus]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
status,
|
|
||||||
isLoading: status === "loading",
|
|
||||||
isAuthenticated: status === "authenticated",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,9 +137,28 @@ export function extractDomain(url) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assertExists<T>(value: T | null | undefined, err?: string): T {
|
export type NonEmptyString = string & { __brand: "NonEmptyString" };
|
||||||
|
export const parseMaybeNonEmptyString = (
|
||||||
|
s: string,
|
||||||
|
trim = true,
|
||||||
|
): NonEmptyString | null => {
|
||||||
|
s = trim ? s.trim() : s;
|
||||||
|
return s.length > 0 ? (s as NonEmptyString) : null;
|
||||||
|
};
|
||||||
|
export const parseNonEmptyString = (s: string, trim = true): NonEmptyString =>
|
||||||
|
assertExists(parseMaybeNonEmptyString(s, trim), "Expected non-empty string");
|
||||||
|
|
||||||
|
export const assertExists = <T>(
|
||||||
|
value: T | null | undefined,
|
||||||
|
err?: string,
|
||||||
|
): T => {
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
throw new Error(`Assertion failed: ${err ?? "value is null or undefined"}`);
|
throw new Error(`Assertion failed: ${err ?? "value is null or undefined"}`);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const assertExistsAndNonEmptyString = (
|
||||||
|
value: string | null | undefined,
|
||||||
|
): NonEmptyString =>
|
||||||
|
parseNonEmptyString(assertExists(value, "Expected non-empty string"));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { RECORD_A_MEETING_URL } from "./lib/constants";
|
import { RECORD_A_MEETING_URL } from "./api/urls";
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
redirect(RECORD_A_MEETING_URL);
|
redirect(RECORD_A_MEETING_URL);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
let authToken = ""; // Variable to store the token
|
let authToken = null;
|
||||||
|
|
||||||
self.addEventListener("message", (event) => {
|
self.addEventListener("message", (event) => {
|
||||||
if (event.data && event.data.type === "SET_AUTH_TOKEN") {
|
if (event.data && event.data.type === "SET_AUTH_TOKEN") {
|
||||||
|
|||||||
Reference in New Issue
Block a user