mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
Implement multi tenancy
This commit is contained in:
@@ -1,13 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import {
|
import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react";
|
||||||
useFiefIsAuthenticated,
|
|
||||||
useFiefUserinfo,
|
|
||||||
} from "@fief/fief/nextjs/react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function UserInfo() {
|
export default function UserInfo() {
|
||||||
const isAuthenticated = useFiefIsAuthenticated();
|
const isAuthenticated = useFiefIsAuthenticated();
|
||||||
const userinfo = useFiefUserinfo();
|
|
||||||
|
|
||||||
return !isAuthenticated ? (
|
return !isAuthenticated ? (
|
||||||
<span className="hover:underline focus-within:underline underline-offset-2 decoration-[.5px] font-light px-2">
|
<span className="hover:underline focus-within:underline underline-offset-2 decoration-[.5px] font-light px-2">
|
||||||
|
|||||||
10
www/app/[domain]/api/current-user.ts
Normal file
10
www/app/[domain]/api/current-user.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { NextApiRequest } from "next";
|
||||||
|
import { fief, getFiefAuth } from "../../lib/fief";
|
||||||
|
// type FiefNextApiHandler<T> = (req: NextApiRequest & AuthenticateRequestResult, res: NextApiResponse<T>) => unknown | Promise<unknown>;
|
||||||
|
|
||||||
|
export default (req: any, res) => {
|
||||||
|
const domain = req.url;
|
||||||
|
console.log("user", req.url, getFiefAuth("localhost").currentUser());
|
||||||
|
return getFiefAuth("localhost").currentUser()(req, res);
|
||||||
|
};
|
||||||
|
// export default fief.currentUser()
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import getApi from "../lib/getApi";
|
import getApi from "../../lib/getApi";
|
||||||
import {
|
import {
|
||||||
PageGetTranscript,
|
PageGetTranscript,
|
||||||
GetTranscript,
|
GetTranscript,
|
||||||
GetTranscriptFromJSON,
|
GetTranscriptFromJSON,
|
||||||
} from "../api";
|
} from "../../api";
|
||||||
import { Title } from "../lib/textComponents";
|
import { Title } from "../../lib/textComponents";
|
||||||
import Pagination from "./pagination";
|
import Pagination from "./pagination";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react";
|
import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faGear } from "@fortawesome/free-solid-svg-icons";
|
import { faGear } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { featureEnabled } from "../domainContext";
|
||||||
|
import router from "next/router";
|
||||||
|
|
||||||
export default function TranscriptBrowser() {
|
export default function TranscriptBrowser() {
|
||||||
const api = getApi();
|
const api = getApi();
|
||||||
@@ -19,6 +21,7 @@ export default function TranscriptBrowser() {
|
|||||||
const [page, setPage] = useState<number>(1);
|
const [page, setPage] = useState<number>(1);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const isAuthenticated = useFiefIsAuthenticated();
|
const isAuthenticated = useFiefIsAuthenticated();
|
||||||
|
const browseEnabled = featureEnabled("browse");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAuthenticated) return;
|
if (!isAuthenticated) return;
|
||||||
49
www/app/[domain]/domainContext.tsx
Normal file
49
www/app/[domain]/domainContext.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
"use client";
|
||||||
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type DomainContextType = {
|
||||||
|
features: {
|
||||||
|
requireLogin: boolean;
|
||||||
|
privacy: boolean;
|
||||||
|
browse: boolean;
|
||||||
|
};
|
||||||
|
apiUrl: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DomainContext = createContext<DomainContextType>({
|
||||||
|
features: {
|
||||||
|
requireLogin: false,
|
||||||
|
privacy: true,
|
||||||
|
browse: false,
|
||||||
|
},
|
||||||
|
apiUrl: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DomainContextProvider = ({ config, children }) => {
|
||||||
|
const [context, setContext] = useState<DomainContextType>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!config) return;
|
||||||
|
setContext({
|
||||||
|
features: {
|
||||||
|
requireLogin: !!config["features"]["requireLogin"],
|
||||||
|
privacy: !!config["features"]["privacy"],
|
||||||
|
browse: !!config["features"]["browse"],
|
||||||
|
},
|
||||||
|
apiUrl: config["api_url"],
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
if (!context) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DomainContext.Provider value={context}>{children}</DomainContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const featureEnabled = (
|
||||||
|
featureName: "requireLogin" | "privacy" | "browse",
|
||||||
|
) => {
|
||||||
|
const context = useContext(DomainContext);
|
||||||
|
return context.features[featureName] as boolean | undefined;
|
||||||
|
};
|
||||||
162
www/app/[domain]/layout.tsx
Normal file
162
www/app/[domain]/layout.tsx
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import "../styles/globals.scss";
|
||||||
|
import { Poppins } from "next/font/google";
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import FiefWrapper from "../(auth)/fiefWrapper";
|
||||||
|
import UserInfo from "../(auth)/userInfo";
|
||||||
|
import { ErrorProvider } from "../(errors)/errorContext";
|
||||||
|
import ErrorMessage from "../(errors)/errorMessage";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
import About from "../(aboutAndPrivacy)/about";
|
||||||
|
import Privacy from "../(aboutAndPrivacy)/privacy";
|
||||||
|
import { get } from "@vercel/edge-config";
|
||||||
|
import { DomainContextProvider } from "./domainContext";
|
||||||
|
|
||||||
|
const poppins = Poppins({ subsets: ["latin"], weight: ["200", "400", "600"] });
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: {
|
||||||
|
template: "%s – Reflector",
|
||||||
|
default: "Reflector - AI-Powered Meeting Transcriptions by Monadical",
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
"Reflector is an AI-powered tool that transcribes your meetings with unparalleled accuracy, divides content by topics, and provides insightful summaries. Maximize your productivity with Reflector, brought to you by Monadical. Capture the signal, not the noise",
|
||||||
|
applicationName: "Reflector",
|
||||||
|
referrer: "origin-when-cross-origin",
|
||||||
|
keywords: ["Reflector", "Monadical", "AI", "Meetings", "Transcription"],
|
||||||
|
authors: [{ name: "Monadical Team", url: "https://monadical.com/team.html" }],
|
||||||
|
formatDetection: {
|
||||||
|
email: false,
|
||||||
|
address: false,
|
||||||
|
telephone: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
openGraph: {
|
||||||
|
title: "Reflector",
|
||||||
|
description:
|
||||||
|
"Reflector is an AI-powered tool that transcribes your meetings with unparalleled accuracy, divides content by topics, and provides insightful summaries. Maximize your productivity with Reflector, brought to you by Monadical. Capture the signal, not the noise.",
|
||||||
|
type: "website",
|
||||||
|
},
|
||||||
|
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title: "Reflector",
|
||||||
|
description:
|
||||||
|
"Reflector is an AI-powered tool that transcribes your meetings with unparalleled accuracy, divides content by topics, and provides insightful summaries. Maximize your productivity with Reflector, brought to you by Monadical. Capture the signal, not the noise.",
|
||||||
|
images: ["/r-icon.png"],
|
||||||
|
},
|
||||||
|
|
||||||
|
icons: {
|
||||||
|
icon: "/r-icon.png",
|
||||||
|
shortcut: "/r-icon.png",
|
||||||
|
apple: "/r-icon.png",
|
||||||
|
},
|
||||||
|
viewport: {
|
||||||
|
width: "device-width",
|
||||||
|
initialScale: 1,
|
||||||
|
maximumScale: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
robots: { index: false, follow: false, noarchive: true, noimageindex: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
type LayoutProps = {
|
||||||
|
params: {
|
||||||
|
domain: string;
|
||||||
|
};
|
||||||
|
children: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function RootLayout({ children, params }: LayoutProps) {
|
||||||
|
const config = await get(params.domain);
|
||||||
|
// console.log(config);
|
||||||
|
const requireLogin = config ? config["features"]["requireLogin"] : false;
|
||||||
|
// console.log(requireLogin);
|
||||||
|
const privacy = config ? config["features"]["privacy"] : true;
|
||||||
|
|
||||||
|
const browse = config ? config["features"]["browse"] : true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={poppins.className + " h-screen relative"}>
|
||||||
|
<FiefWrapper>
|
||||||
|
<DomainContextProvider config={config}>
|
||||||
|
<ErrorProvider>
|
||||||
|
<ErrorMessage />
|
||||||
|
<div
|
||||||
|
id="container"
|
||||||
|
className="items-center h-[100svh] w-[100svw] p-2 md:p-4 grid grid-rows-layout gap-2 md:gap-4"
|
||||||
|
>
|
||||||
|
<header className="flex justify-between items-center w-full">
|
||||||
|
{/* Logo on the left */}
|
||||||
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="flex outline-blue-300 md:outline-none focus-visible:underline underline-offset-2 decoration-[.5px] decoration-gray-500"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src="/reach.png"
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className="h-10 w-auto"
|
||||||
|
alt="Reflector"
|
||||||
|
/>
|
||||||
|
<div className="hidden flex-col ml-2 md:block">
|
||||||
|
<h1 className="text-[38px] font-bold tracking-wide leading-tight">
|
||||||
|
Reflector
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-500 text-xs tracking-tighter">
|
||||||
|
Capture the signal, not the noise
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<div>
|
||||||
|
{/* Text link on the right */}
|
||||||
|
<Link
|
||||||
|
href="/transcripts/new"
|
||||||
|
className="hover:underline focus-within:underline underline-offset-2 decoration-[.5px] font-light px-2"
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Link>
|
||||||
|
{browse ? (
|
||||||
|
<>
|
||||||
|
·
|
||||||
|
<Link
|
||||||
|
href="/browse"
|
||||||
|
className="hover:underline focus-within:underline underline-offset-2 decoration-[.5px] font-light px-2"
|
||||||
|
>
|
||||||
|
Browse
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
·
|
||||||
|
<About buttonText="About" />
|
||||||
|
{privacy ? (
|
||||||
|
<>
|
||||||
|
·
|
||||||
|
<Privacy buttonText="Privacy" />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
{requireLogin ? (
|
||||||
|
<>
|
||||||
|
·
|
||||||
|
<UserInfo />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</ErrorProvider>
|
||||||
|
</DomainContextProvider>
|
||||||
|
</FiefWrapper>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import Modal from "../modal";
|
import Modal from "../modal";
|
||||||
import getApi from "../../lib/getApi";
|
import getApi from "../../../lib/getApi";
|
||||||
import useTranscript from "../useTranscript";
|
import useTranscript from "../useTranscript";
|
||||||
import useTopics from "../useTopics";
|
import useTopics from "../useTopics";
|
||||||
import useWaveform from "../useWaveform";
|
import useWaveform from "../useWaveform";
|
||||||
@@ -8,13 +8,13 @@ import { TopicList } from "../topicList";
|
|||||||
import Recorder from "../recorder";
|
import Recorder from "../recorder";
|
||||||
import { Topic } from "../webSocketTypes";
|
import { Topic } from "../webSocketTypes";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import "../../styles/button.css";
|
import "../../../styles/button.css";
|
||||||
import FinalSummary from "../finalSummary";
|
import FinalSummary from "../finalSummary";
|
||||||
import ShareLink from "../shareLink";
|
import ShareLink from "../shareLink";
|
||||||
import QRCode from "react-qr-code";
|
import QRCode from "react-qr-code";
|
||||||
import TranscriptTitle from "../transcriptTitle";
|
import TranscriptTitle from "../transcriptTitle";
|
||||||
import { featRequireLogin } from "../../../app/lib/utils";
|
|
||||||
import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react";
|
import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react";
|
||||||
|
import { featureEnabled } from "../../domainContext";
|
||||||
|
|
||||||
type TranscriptDetails = {
|
type TranscriptDetails = {
|
||||||
params: {
|
params: {
|
||||||
@@ -32,7 +32,7 @@ export default function TranscriptDetails(details: TranscriptDetails) {
|
|||||||
const useActiveTopic = useState<Topic | null>(null);
|
const useActiveTopic = useState<Topic | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (featRequireLogin() && !isAuthenticated) return;
|
if (featureEnabled("requireLogin") && !isAuthenticated) return;
|
||||||
setTranscriptId(details.params.transcriptId);
|
setTranscriptId(details.params.transcriptId);
|
||||||
}, [api]);
|
}, [api]);
|
||||||
|
|
||||||
@@ -6,14 +6,14 @@ import useWebRTC from "../../useWebRTC";
|
|||||||
import useTranscript from "../../useTranscript";
|
import useTranscript from "../../useTranscript";
|
||||||
import { useWebSockets } from "../../useWebSockets";
|
import { useWebSockets } from "../../useWebSockets";
|
||||||
import useAudioDevice from "../../useAudioDevice";
|
import useAudioDevice from "../../useAudioDevice";
|
||||||
import "../../../styles/button.css";
|
import "../../../../styles/button.css";
|
||||||
import { Topic } from "../../webSocketTypes";
|
import { Topic } from "../../webSocketTypes";
|
||||||
import getApi from "../../../lib/getApi";
|
import getApi from "../../../../lib/getApi";
|
||||||
import LiveTrancription from "../../liveTranscription";
|
import LiveTrancription from "../../liveTranscription";
|
||||||
import DisconnectedIndicator from "../../disconnectedIndicator";
|
import DisconnectedIndicator from "../../disconnectedIndicator";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faGear } from "@fortawesome/free-solid-svg-icons";
|
import { faGear } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { lockWakeState, releaseWakeState } from "../../../lib/wakeLock";
|
import { lockWakeState, releaseWakeState } from "../../../../lib/wakeLock";
|
||||||
|
|
||||||
type TranscriptDetails = {
|
type TranscriptDetails = {
|
||||||
params: {
|
params: {
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { DefaultApi, V1TranscriptsCreateRequest } from "../api/apis/DefaultApi";
|
import {
|
||||||
import { GetTranscript } from "../api";
|
DefaultApi,
|
||||||
import { useError } from "../(errors)/errorContext";
|
V1TranscriptsCreateRequest,
|
||||||
import getApi from "../lib/getApi";
|
} from "../../api/apis/DefaultApi";
|
||||||
|
import { GetTranscript } from "../../api";
|
||||||
|
import { useError } from "../../(errors)/errorContext";
|
||||||
|
import getApi from "../../lib/getApi";
|
||||||
|
|
||||||
type CreateTranscript = {
|
type CreateTranscript = {
|
||||||
response: GetTranscript | null;
|
response: GetTranscript | null;
|
||||||
@@ -2,7 +2,7 @@ import { useRef, useState } from "react";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDom from "react-dom";
|
import ReactDom from "react-dom";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import "../styles/markdown.css";
|
import "../../styles/markdown.css";
|
||||||
|
|
||||||
type FinalSummaryProps = {
|
type FinalSummaryProps = {
|
||||||
summary: string;
|
summary: string;
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useContext, useEffect, useState } from "react";
|
||||||
import useAudioDevice from "../useAudioDevice";
|
import useAudioDevice from "../useAudioDevice";
|
||||||
import "react-select-search/style.css";
|
import "react-select-search/style.css";
|
||||||
import "../../styles/button.css";
|
import "../../../styles/button.css";
|
||||||
import "../../styles/form.scss";
|
import "../../../styles/form.scss";
|
||||||
import About from "../../(aboutAndPrivacy)/about";
|
import About from "../../../(aboutAndPrivacy)/about";
|
||||||
import Privacy from "../../(aboutAndPrivacy)/privacy";
|
import Privacy from "../../../(aboutAndPrivacy)/privacy";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import useCreateTranscript from "../createTranscript";
|
import useCreateTranscript from "../createTranscript";
|
||||||
import SelectSearch from "react-select-search";
|
import SelectSearch from "react-select-search";
|
||||||
import { supportedLatinLanguages } from "../../supportedLanguages";
|
import { supportedLatinLanguages } from "../../../supportedLanguages";
|
||||||
import { featRequireLogin, featPrivacy } from "../../lib/utils";
|
|
||||||
import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react";
|
import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react";
|
||||||
|
import { featureEnabled } from "../../domainContext";
|
||||||
|
|
||||||
const TranscriptCreate = () => {
|
const TranscriptCreate = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const isAuthenticated = useFiefIsAuthenticated();
|
const isAuthenticated = useFiefIsAuthenticated();
|
||||||
const requireLogin = featRequireLogin();
|
const requireLogin = featureEnabled("requireLogin");
|
||||||
|
|
||||||
const [name, setName] = useState<string>();
|
const [name, setName] = useState<string>();
|
||||||
const nameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const nameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -74,7 +74,9 @@ const TranscriptCreate = () => {
|
|||||||
In order to use Reflector, we kindly request permission to access
|
In order to use Reflector, we kindly request permission to access
|
||||||
your microphone during meetings and events.
|
your microphone during meetings and events.
|
||||||
</p>
|
</p>
|
||||||
{featPrivacy() && <Privacy buttonText="Privacy policy" />}
|
{featureEnabled("privacy") && (
|
||||||
|
<Privacy buttonText="Privacy policy" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section className="flex flex-col justify-center items-center w-full h-full">
|
<section className="flex flex-col justify-center items-center w-full h-full">
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
import React, { useRef, useEffect, useState } from "react";
|
import React, { useRef, useEffect, useState } from "react";
|
||||||
|
|
||||||
import WaveSurfer from "wavesurfer.js";
|
import WaveSurfer from "wavesurfer.js";
|
||||||
import RecordPlugin from "../lib/custom-plugins/record";
|
import RecordPlugin from "../../lib/custom-plugins/record";
|
||||||
import CustomRegionsPlugin from "../lib/custom-plugins/regions";
|
import CustomRegionsPlugin from "../../lib/custom-plugins/regions";
|
||||||
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faMicrophone } from "@fortawesome/free-solid-svg-icons";
|
import { faMicrophone } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { faDownload } from "@fortawesome/free-solid-svg-icons";
|
import { faDownload } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
import { formatTime } from "../lib/time";
|
import { formatTime } from "../../lib/time";
|
||||||
import { Topic } from "./webSocketTypes";
|
import { Topic } from "./webSocketTypes";
|
||||||
import { AudioWaveform } from "../api";
|
import { AudioWaveform } from "../../api";
|
||||||
import AudioInputsDropdown from "./audioInputsDropdown";
|
import AudioInputsDropdown from "./audioInputsDropdown";
|
||||||
import { Option } from "react-dropdown";
|
import { Option } from "react-dropdown";
|
||||||
import { useError } from "../(errors)/errorContext";
|
import { useError } from "../../(errors)/errorContext";
|
||||||
import { waveSurferStyles } from "../styles/recorder";
|
import { waveSurferStyles } from "../../styles/recorder";
|
||||||
|
|
||||||
type RecorderProps = {
|
type RecorderProps = {
|
||||||
setStream?: React.Dispatch<React.SetStateAction<MediaStream | null>>;
|
setStream?: React.Dispatch<React.SetStateAction<MediaStream | null>>;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useRef, useEffect, use } from "react";
|
import React, { useState, useRef, useEffect, use } from "react";
|
||||||
import { featPrivacy } from "../lib/utils";
|
import { featureEnabled } from "../domainContext";
|
||||||
|
|
||||||
const ShareLink = () => {
|
const ShareLink = () => {
|
||||||
const [isCopied, setIsCopied] = useState(false);
|
const [isCopied, setIsCopied] = useState(false);
|
||||||
@@ -23,12 +23,14 @@ const ShareLink = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const privacyEnabled = featureEnabled("privacy");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="p-2 md:p-4 rounded"
|
className="p-2 md:p-4 rounded"
|
||||||
style={{ background: "rgba(96, 165, 250, 0.2)" }}
|
style={{ background: "rgba(96, 165, 250, 0.2)" }}
|
||||||
>
|
>
|
||||||
{featPrivacy() ? (
|
{privacyEnabled ? (
|
||||||
<p className="text-sm mb-2">
|
<p className="text-sm mb-2">
|
||||||
You can share this link with others. Anyone with the link will have
|
You can share this link with others. Anyone with the link will have
|
||||||
access to the page, including the full audio recording, for the next 7
|
access to the page, including the full audio recording, for the next 7
|
||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
faChevronRight,
|
faChevronRight,
|
||||||
faChevronDown,
|
faChevronDown,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { formatTime } from "../lib/time";
|
import { formatTime } from "../../lib/time";
|
||||||
import ScrollToBottom from "./scrollToBottom";
|
import ScrollToBottom from "./scrollToBottom";
|
||||||
import { Topic } from "./webSocketTypes";
|
import { Topic } from "./webSocketTypes";
|
||||||
|
|
||||||
@@ -2,9 +2,9 @@ import { useEffect, useState } from "react";
|
|||||||
import {
|
import {
|
||||||
DefaultApi,
|
DefaultApi,
|
||||||
V1TranscriptGetAudioMp3Request,
|
V1TranscriptGetAudioMp3Request,
|
||||||
} from "../api/apis/DefaultApi";
|
} from "../../api/apis/DefaultApi";
|
||||||
import {} from "../api";
|
import {} from "../../api";
|
||||||
import { useError } from "../(errors)/errorContext";
|
import { useError } from "../../(errors)/errorContext";
|
||||||
|
|
||||||
type Mp3Response = {
|
type Mp3Response = {
|
||||||
url: string | null;
|
url: string | null;
|
||||||
@@ -2,9 +2,8 @@ import { useEffect, useState } from "react";
|
|||||||
import {
|
import {
|
||||||
DefaultApi,
|
DefaultApi,
|
||||||
V1TranscriptGetTopicsRequest,
|
V1TranscriptGetTopicsRequest,
|
||||||
} from "../api/apis/DefaultApi";
|
} from "../../api/apis/DefaultApi";
|
||||||
import { TranscriptTopic } from "../api";
|
import { useError } from "../../(errors)/errorContext";
|
||||||
import { useError } from "../(errors)/errorContext";
|
|
||||||
import { Topic } from "./webSocketTypes";
|
import { Topic } from "./webSocketTypes";
|
||||||
|
|
||||||
type TranscriptTopics = {
|
type TranscriptTopics = {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { DefaultApi, V1TranscriptGetRequest } from "../api/apis/DefaultApi";
|
import { DefaultApi, V1TranscriptGetRequest } from "../../api/apis/DefaultApi";
|
||||||
import { GetTranscript } from "../api";
|
import { GetTranscript } from "../../api";
|
||||||
import { useError } from "../(errors)/errorContext";
|
import { useError } from "../../(errors)/errorContext";
|
||||||
|
|
||||||
type Transcript = {
|
type Transcript = {
|
||||||
response: GetTranscript | null;
|
response: GetTranscript | null;
|
||||||
@@ -2,9 +2,9 @@ import { useEffect, useState } from "react";
|
|||||||
import {
|
import {
|
||||||
DefaultApi,
|
DefaultApi,
|
||||||
V1TranscriptGetAudioWaveformRequest,
|
V1TranscriptGetAudioWaveformRequest,
|
||||||
} from "../api/apis/DefaultApi";
|
} from "../../api/apis/DefaultApi";
|
||||||
import { AudioWaveform } from "../api";
|
import { AudioWaveform } from "../../api";
|
||||||
import { useError } from "../(errors)/errorContext";
|
import { useError } from "../../(errors)/errorContext";
|
||||||
|
|
||||||
type AudioWaveFormResponse = {
|
type AudioWaveFormResponse = {
|
||||||
waveform: AudioWaveform | null;
|
waveform: AudioWaveform | null;
|
||||||
@@ -3,8 +3,8 @@ import Peer from "simple-peer";
|
|||||||
import {
|
import {
|
||||||
DefaultApi,
|
DefaultApi,
|
||||||
V1TranscriptRecordWebrtcRequest,
|
V1TranscriptRecordWebrtcRequest,
|
||||||
} from "../api/apis/DefaultApi";
|
} from "../../api/apis/DefaultApi";
|
||||||
import { useError } from "../(errors)/errorContext";
|
import { useError } from "../../(errors)/errorContext";
|
||||||
|
|
||||||
const useWebRTC = (
|
const useWebRTC = (
|
||||||
stream: MediaStream | null,
|
stream: MediaStream | null,
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Topic, FinalSummary, Status } from "./webSocketTypes";
|
import { Topic, FinalSummary, Status } from "./webSocketTypes";
|
||||||
import { useError } from "../(errors)/errorContext";
|
import { useError } from "../../(errors)/errorContext";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
type UseWebSockets = {
|
type UseWebSockets = {
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
import "./styles/globals.scss";
|
|
||||||
import { Poppins } from "next/font/google";
|
|
||||||
import { Metadata } from "next";
|
|
||||||
import FiefWrapper from "./(auth)/fiefWrapper";
|
|
||||||
import UserInfo from "./(auth)/userInfo";
|
|
||||||
import { ErrorProvider } from "./(errors)/errorContext";
|
|
||||||
import ErrorMessage from "./(errors)/errorMessage";
|
|
||||||
import Image from "next/image";
|
|
||||||
import Link from "next/link";
|
|
||||||
import About from "./(aboutAndPrivacy)/about";
|
|
||||||
import Privacy from "./(aboutAndPrivacy)/privacy";
|
|
||||||
import { featPrivacy, featRequireLogin } from "./lib/utils";
|
|
||||||
|
|
||||||
const poppins = Poppins({ subsets: ["latin"], weight: ["200", "400", "600"] });
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: {
|
|
||||||
template: "%s – Reflector",
|
|
||||||
default: "Reflector - AI-Powered Meeting Transcriptions by Monadical",
|
|
||||||
},
|
|
||||||
description:
|
|
||||||
"Reflector is an AI-powered tool that transcribes your meetings with unparalleled accuracy, divides content by topics, and provides insightful summaries. Maximize your productivity with Reflector, brought to you by Monadical. Capture the signal, not the noise",
|
|
||||||
applicationName: "Reflector",
|
|
||||||
referrer: "origin-when-cross-origin",
|
|
||||||
keywords: ["Reflector", "Monadical", "AI", "Meetings", "Transcription"],
|
|
||||||
authors: [{ name: "Monadical Team", url: "https://monadical.com/team.html" }],
|
|
||||||
formatDetection: {
|
|
||||||
email: false,
|
|
||||||
address: false,
|
|
||||||
telephone: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
openGraph: {
|
|
||||||
title: "Reflector",
|
|
||||||
description:
|
|
||||||
"Reflector is an AI-powered tool that transcribes your meetings with unparalleled accuracy, divides content by topics, and provides insightful summaries. Maximize your productivity with Reflector, brought to you by Monadical. Capture the signal, not the noise.",
|
|
||||||
type: "website",
|
|
||||||
},
|
|
||||||
|
|
||||||
twitter: {
|
|
||||||
card: "summary_large_image",
|
|
||||||
title: "Reflector",
|
|
||||||
description:
|
|
||||||
"Reflector is an AI-powered tool that transcribes your meetings with unparalleled accuracy, divides content by topics, and provides insightful summaries. Maximize your productivity with Reflector, brought to you by Monadical. Capture the signal, not the noise.",
|
|
||||||
images: ["/r-icon.png"],
|
|
||||||
},
|
|
||||||
|
|
||||||
icons: {
|
|
||||||
icon: "/r-icon.png",
|
|
||||||
shortcut: "/r-icon.png",
|
|
||||||
apple: "/r-icon.png",
|
|
||||||
},
|
|
||||||
viewport: {
|
|
||||||
width: "device-width",
|
|
||||||
initialScale: 1,
|
|
||||||
maximumScale: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
robots: { index: false, follow: false, noarchive: true, noimageindex: true },
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({ children }) {
|
|
||||||
const requireLogin = featRequireLogin();
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<body className={poppins.className + " h-screen relative"}>
|
|
||||||
<FiefWrapper>
|
|
||||||
<ErrorProvider>
|
|
||||||
<ErrorMessage />
|
|
||||||
<div
|
|
||||||
id="container"
|
|
||||||
className="items-center h-[100svh] w-[100svw] p-2 md:p-4 grid grid-rows-layout gap-2 md:gap-4"
|
|
||||||
>
|
|
||||||
<header className="flex justify-between items-center w-full">
|
|
||||||
{/* Logo on the left */}
|
|
||||||
<Link
|
|
||||||
href="/"
|
|
||||||
className="flex outline-blue-300 md:outline-none focus-visible:underline underline-offset-2 decoration-[.5px] decoration-gray-500"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src="/reach.png"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className="h-10 w-auto"
|
|
||||||
alt="Reflector"
|
|
||||||
/>
|
|
||||||
<div className="hidden flex-col ml-2 md:block">
|
|
||||||
<h1 className="text-[38px] font-bold tracking-wide leading-tight">
|
|
||||||
Reflector
|
|
||||||
</h1>
|
|
||||||
<p className="text-gray-500 text-xs tracking-tighter">
|
|
||||||
Capture the signal, not the noise
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
<div>
|
|
||||||
{/* Text link on the right */}
|
|
||||||
<Link
|
|
||||||
href="/transcripts/new"
|
|
||||||
className="hover:underline focus-within:underline underline-offset-2 decoration-[.5px] font-light px-2"
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</Link>
|
|
||||||
·
|
|
||||||
<Link
|
|
||||||
href="/browse"
|
|
||||||
className="hover:underline focus-within:underline underline-offset-2 decoration-[.5px] font-light px-2"
|
|
||||||
>
|
|
||||||
Browse
|
|
||||||
</Link>
|
|
||||||
·
|
|
||||||
<About buttonText="About" />
|
|
||||||
{featPrivacy() ? (
|
|
||||||
<>
|
|
||||||
·
|
|
||||||
<Privacy buttonText="Privacy" />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
{requireLogin ? (
|
|
||||||
<>
|
|
||||||
·
|
|
||||||
<UserInfo />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</ErrorProvider>
|
|
||||||
</FiefWrapper>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
"use client";
|
|
||||||
import { Fief, FiefUserInfo } from "@fief/fief";
|
import { Fief, FiefUserInfo } from "@fief/fief";
|
||||||
import { FiefAuth, IUserInfoCache } from "@fief/fief/nextjs";
|
import { FiefAuth, IUserInfoCache } from "@fief/fief/nextjs";
|
||||||
|
import { get } from "@vercel/edge-config";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { useError } from "../(errors)/errorContext";
|
||||||
|
|
||||||
export const SESSION_COOKIE_NAME = "reflector-auth";
|
export const SESSION_COOKIE_NAME = "reflector-auth";
|
||||||
|
|
||||||
@@ -38,13 +40,54 @@ class MemoryUserInfoCache implements IUserInfoCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fiefAuth = new FiefAuth({
|
const FIEF_AUTHS = {} as { [domain: string]: FiefAuth };
|
||||||
client: fiefClient,
|
|
||||||
sessionCookieName: SESSION_COOKIE_NAME,
|
export const getFiefAuth = async (url: URL) => {
|
||||||
redirectURI:
|
if (FIEF_AUTHS[url.hostname]) {
|
||||||
process.env.NEXT_PUBLIC_AUTH_CALLBACK_URL ||
|
return FIEF_AUTHS[url.hostname];
|
||||||
"http://localhost:3000/auth-callback",
|
} else {
|
||||||
logoutRedirectURI:
|
const config = url && (await get(url.hostname));
|
||||||
process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000",
|
if (config) {
|
||||||
userInfoCache: new MemoryUserInfoCache(),
|
FIEF_AUTHS[url.hostname] = new FiefAuth({
|
||||||
});
|
client: fiefClient,
|
||||||
|
sessionCookieName: SESSION_COOKIE_NAME,
|
||||||
|
redirectURI: config["auth_callback_url"],
|
||||||
|
logoutRedirectURI: url.origin,
|
||||||
|
userInfoCache: new MemoryUserInfoCache(),
|
||||||
|
});
|
||||||
|
return FIEF_AUTHS[url.hostname];
|
||||||
|
} else {
|
||||||
|
throw new Error("Fief intanciation failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFiefAuthMiddleware = async (url) => {
|
||||||
|
const protectedPaths = [
|
||||||
|
{
|
||||||
|
matcher: "/:domain/transcripts",
|
||||||
|
parameters: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: "/:domain/transcripts/:path*",
|
||||||
|
parameters: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: "/:domain/browse",
|
||||||
|
parameters: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: "/transcripts",
|
||||||
|
parameters: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: "/transcripts/:path*",
|
||||||
|
parameters: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
matcher: "/browse",
|
||||||
|
parameters: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return (await getFiefAuth(url))?.middleware(protectedPaths);
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ import { Configuration } from "../api/runtime";
|
|||||||
import { DefaultApi } from "../api/apis/DefaultApi";
|
import { DefaultApi } from "../api/apis/DefaultApi";
|
||||||
|
|
||||||
import { useFiefAccessTokenInfo } from "@fief/fief/nextjs/react";
|
import { useFiefAccessTokenInfo } from "@fief/fief/nextjs/react";
|
||||||
|
import { useContext } from "react";
|
||||||
|
import { DomainContext } from "../[domain]/domainContext";
|
||||||
|
|
||||||
export default function getApi(): DefaultApi {
|
export default function getApi(): DefaultApi {
|
||||||
const accessTokenInfo = useFiefAccessTokenInfo();
|
const accessTokenInfo = useFiefAccessTokenInfo();
|
||||||
|
const api_url = useContext(DomainContext).apiUrl;
|
||||||
|
if (!api_url) throw new Error("no API URL");
|
||||||
|
|
||||||
const apiConfiguration = new Configuration({
|
const apiConfiguration = new Configuration({
|
||||||
basePath: process.env.NEXT_PUBLIC_API_URL,
|
basePath: api_url,
|
||||||
accessToken: accessTokenInfo
|
accessToken: accessTokenInfo
|
||||||
? "Bearer " + accessTokenInfo.access_token
|
? "Bearer " + accessTokenInfo.access_token
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@@ -1,15 +1,3 @@
|
|||||||
export function isDevelopment() {
|
export function isDevelopment() {
|
||||||
return process.env.NEXT_PUBLIC_ENV === "development";
|
return process.env.NEXT_PUBLIC_ENV === "development";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function featPrivacy() {
|
|
||||||
return process.env.NEXT_PUBLIC_FEAT_PRIVACY === "1";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function featBrowse() {
|
|
||||||
return process.env.NEXT_PUBLIC_FEAT_BROWSE === "1";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function featRequireLogin() {
|
|
||||||
return process.env.NEXT_PUBLIC_FEAT_LOGIN_REQUIRED === "1";
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,22 +1,50 @@
|
|||||||
import type { NextRequest } from "next/server";
|
import { NextResponse, NextRequest } from "next/server";
|
||||||
|
import { get } from "@vercel/edge-config";
|
||||||
|
|
||||||
import { fiefAuth } from "./app/lib/fief";
|
import { FiefAuth, IUserInfoCache } from "@fief/fief/nextjs";
|
||||||
|
import { getFiefAuth, getFiefAuthMiddleware } from "./app/lib/fief";
|
||||||
|
|
||||||
let protectedPath: any = [];
|
|
||||||
if (process.env.NEXT_PUBLIC_FEAT_LOGIN_REQUIRED === "1") {
|
|
||||||
protectedPath = [
|
|
||||||
{
|
|
||||||
matcher: "/transcripts/((?!new).*)",
|
|
||||||
parameters: {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
matcher: "/browse",
|
|
||||||
parameters: {},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const authMiddleware = fiefAuth.middleware(protectedPath);
|
|
||||||
export async function middleware(request: NextRequest) {
|
export async function middleware(request: NextRequest) {
|
||||||
return authMiddleware(request);
|
const domain = request.nextUrl.hostname;
|
||||||
|
const config = await get(domain);
|
||||||
|
|
||||||
|
if (!config) return NextResponse.error();
|
||||||
|
|
||||||
|
// Feature-flag protedted paths
|
||||||
|
if (
|
||||||
|
!config["features"]["browse"] &&
|
||||||
|
request.nextUrl.pathname.startsWith("/browse")
|
||||||
|
) {
|
||||||
|
return NextResponse.redirect(request.nextUrl.origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config["features"]["requireLogin"]) {
|
||||||
|
const fiefMiddleware = await getFiefAuthMiddleware(request.nextUrl);
|
||||||
|
const fiefResponse = fiefMiddleware(request);
|
||||||
|
if (
|
||||||
|
request.nextUrl.pathname == "/" ||
|
||||||
|
request.nextUrl.pathname.startsWith("/transcripts") ||
|
||||||
|
request.nextUrl.pathname.startsWith("/browse")
|
||||||
|
) {
|
||||||
|
// return fiefAuthMiddleware(domain, config['auth_callback_url'])(request, {rewrite: request.nextUrl.origin + "/" + domain + request.nextUrl.pathname})
|
||||||
|
const response = NextResponse.rewrite(
|
||||||
|
request.nextUrl.origin + "/" + domain + request.nextUrl.pathname,
|
||||||
|
);
|
||||||
|
// response = (await fiefResponse).headers
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
return fiefResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
request.nextUrl.pathname == "/" ||
|
||||||
|
request.nextUrl.pathname.startsWith("/transcripts") ||
|
||||||
|
request.nextUrl.pathname.startsWith("/browse")
|
||||||
|
) {
|
||||||
|
return NextResponse.rewrite(
|
||||||
|
request.nextUrl.origin + "/" + domain + request.nextUrl.pathname,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@sentry/nextjs": "^7.64.0",
|
"@sentry/nextjs": "^7.64.0",
|
||||||
|
"@vercel/edge-config": "^0.4.1",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"fontawesome": "^5.6.3",
|
"fontawesome": "^5.6.3",
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
import { fiefAuth } from "../../app/lib/fief";
|
import { get } from "@vercel/edge-config";
|
||||||
|
import { getFiefAuth } from "../../app/lib/fief";
|
||||||
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
export default fiefAuth.currentUser();
|
export default async (req: NextApiRequest, res: NextApiResponse<any>) => {
|
||||||
|
const fromUrl = req.headers["referer"] && new URL(req.headers["referer"]);
|
||||||
|
const fief = fromUrl && (await getFiefAuth(fromUrl));
|
||||||
|
if (fief) {
|
||||||
|
return fief.currentUser()(req as any, res as any);
|
||||||
|
} else {
|
||||||
|
return res.status(200).json({ userinfo: null, access_token_info: null });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -496,6 +496,18 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
||||||
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
|
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
|
||||||
|
|
||||||
|
"@vercel/edge-config-fs@0.1.0":
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vercel/edge-config-fs/-/edge-config-fs-0.1.0.tgz#cda8327f611418c1d1cbb28c6bed02d782be928b"
|
||||||
|
integrity sha512-NRIBwfcS0bUoUbRWlNGetqjvLSwgYH/BqKqDN7vK1g32p7dN96k0712COgaz6VFizAm9b0g6IG6hR6+hc0KCPg==
|
||||||
|
|
||||||
|
"@vercel/edge-config@^0.4.1":
|
||||||
|
version "0.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vercel/edge-config/-/edge-config-0.4.1.tgz#c247011dcd05ce6a6b4d17139215c8f684d83cb3"
|
||||||
|
integrity sha512-4Mc3H7lE+x4RrL17nY8CWeEorvJHbkNbQTy9p8H1tO7y11WeKj5xeZSr07wNgfWInKXDUwj5FZ3qd/jIzjPxug==
|
||||||
|
dependencies:
|
||||||
|
"@vercel/edge-config-fs" "0.1.0"
|
||||||
|
|
||||||
agent-base@6:
|
agent-base@6:
|
||||||
version "6.0.2"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||||
|
|||||||
Reference in New Issue
Block a user