-
Notifications
You must be signed in to change notification settings - Fork 13.9k
feat: BigBlueButton video conferencing integration #29434
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| /** | ||
| * 需要自定义 setup form 的 app slug 列表 | ||
| * 这些 app 不是 OAuth 类型,但有自定义凭证表单, | ||
| * 需要走 setup form 流程而非自动安装流程 | ||
| */ | ||
| export const APPS_WITH_SETUP_FORM: string[] = ["bigbluebutton"]; | ||
|
|
||
| /** | ||
| * 判断指定 app 是否需要 setup form | ||
| */ | ||
| export function appRequiresSetupForm(slug: string): boolean { | ||
| return APPS_WITH_SETUP_FORM.includes(slug); | ||
| } | ||
|
|
||
| /** | ||
| * 为需要 setup form 的 app 返回重定向对象 | ||
| * 如果 app 不需要 setup form,返回 null | ||
| */ | ||
| export function setupFormRedirectFor(slug: string): { | ||
| redirect: { permanent: boolean; destination: string }; | ||
| } | null { | ||
| if (appRequiresSetupForm(slug)) { | ||
| return { | ||
| redirect: { | ||
| permanent: false, | ||
| destination: `/apps/${slug}/setup`, | ||
| }, | ||
| }; | ||
| } | ||
| return null; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| "use client"; | ||
|
|
||
| import { useRouter } from "next/navigation"; | ||
| import { useForm } from "react-hook-form"; | ||
| import { useState } from "react"; | ||
| import { z } from "zod"; | ||
| import { zodResolver } from "@hookform/resolvers/zod"; | ||
|
|
||
| import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||
| import { HttpError } from "@calcom/lib/http-error"; | ||
| import { Button } from "@calcom/ui/components/button"; | ||
| import { | ||
| Form, | ||
| FormField, | ||
| Input, | ||
| PasswordField, | ||
| Label, | ||
| } from "@calcom/ui/components/form"; | ||
| import { showToast } from "@calcom/ui/components/toast"; | ||
|
|
||
| /** 表单字段验证模式 */ | ||
| const formSchema = z.object({ | ||
| serverUrl: z.string().url(), | ||
| sharedSecret: z.string().trim().min(1), | ||
| }); | ||
|
|
||
| type FormValues = z.infer<typeof formSchema>; | ||
|
|
||
| /** | ||
| * BigBlueButton Setup 组件 | ||
| * 收集用户的 BBB 服务器 URL 和共享密钥 | ||
| */ | ||
| export default function BigBlueButtonSetup() { | ||
| const { t } = useLocale(); | ||
| const router = useRouter(); | ||
| const [isSubmitting, setIsSubmitting] = useState(false); | ||
|
|
||
| const form = useForm<FormValues>({ | ||
| resolver: zodResolver(formSchema), | ||
| defaultValues: { | ||
| serverUrl: "", | ||
| sharedSecret: "", | ||
| }, | ||
| }); | ||
|
|
||
| const onSubmit = form.handleSubmit(async (values) => { | ||
| setIsSubmitting(true); | ||
| try { | ||
| const res = await fetch("/api/integrations/bigbluebuttonvideo/add", { | ||
| method: "POST", | ||
| body: JSON.stringify(values), | ||
| headers: { "Content-Type": "application/json" }, | ||
| }); | ||
|
|
||
| if (!res.ok) { | ||
| const json = await res.json(); | ||
| throw new HttpError({ statusCode: res.status, message: json.message }); | ||
| } | ||
|
|
||
| const json = await res.json(); | ||
| router.push(json.url); | ||
| } catch (err) { | ||
| if (err instanceof HttpError) { | ||
| showToast(err.message, "error"); | ||
| } else { | ||
| showToast(t("something_went_wrong"), "error"); | ||
| } | ||
| } finally { | ||
| setIsSubmitting(false); | ||
| } | ||
| }); | ||
|
|
||
| return ( | ||
| <Form form={form} handleSubmit={onSubmit}> | ||
| <div className="space-y-4"> | ||
| <FormField | ||
| control={form.control} | ||
| name="serverUrl" | ||
| render={({ field }) => ( | ||
| <div> | ||
| <Label>{t("bbb_server_url")}</Label> | ||
| <Input | ||
| {...field} | ||
| type="url" | ||
| placeholder={t("bbb_server_url_placeholder")} | ||
| required | ||
| /> | ||
| </div> | ||
| )} | ||
| /> | ||
| <FormField | ||
| control={form.control} | ||
| name="sharedSecret" | ||
| render={({ field }) => ( | ||
| <div> | ||
| <Label>{t("bbb_shared_secret")}</Label> | ||
| <PasswordField | ||
| {...field} | ||
| placeholder={t("bbb_shared_secret_placeholder")} | ||
| required | ||
| /> | ||
| </div> | ||
| )} | ||
| /> | ||
| </div> | ||
| <Button | ||
| type="submit" | ||
| loading={isSubmitting} | ||
| className="mt-4 w-full" | ||
| data-testid="bbb-submit-button"> | ||
| {t("submit")} | ||
| </Button> | ||
| </Form> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,12 +9,14 @@ import { isConferencing as isConferencingApp } from "@calcom/app-store/utils"; | |||||||||||||||||
| import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; | ||||||||||||||||||
| import { UserRepository } from "@calcom/features/users/repositories/UserRepository"; | ||||||||||||||||||
| import { AppOnboardingSteps } from "@calcom/lib/apps/appOnboardingSteps"; | ||||||||||||||||||
| import { CAL_URL } from "@calcom/lib/constants"; | ||||||||||||||||||
| import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants"; | ||||||||||||||||||
| import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; | ||||||||||||||||||
| import prisma from "@calcom/prisma"; | ||||||||||||||||||
| import { Prisma } from "@calcom/prisma/client"; | ||||||||||||||||||
| import { eventTypeBookingFields } from "@calcom/prisma/zod-utils"; | ||||||||||||||||||
|
|
||||||||||||||||||
| import { setupFormRedirectFor } from "@calcom/web/components/apps/appsWithSetupForm"; | ||||||||||||||||||
|
|
||||||||||||||||||
| import { STEPS } from "../../../../modules/apps/installation/[[...step]]/constants"; | ||||||||||||||||||
| import type { | ||||||||||||||||||
| OnboardingPageProps, | ||||||||||||||||||
|
|
@@ -446,6 +448,11 @@ const getCredential = async ( | |||||||||||||||||
| parsedAppSlug: string | ||||||||||||||||||
| ): Promise<{ credentialId: number | null; redirect?: RedirectResult }> => { | ||||||||||||||||||
| let credentialId = getCredentialId(parsedTeamIdParam, appInstalls, user.id); | ||||||||||||||||||
| // 无凭证且 app 需要 setup form 时,重定向到 setup 页面采集凭证 | ||||||||||||||||||
| if (!credentialId && !appMetadata.isOAuth) { | ||||||||||||||||||
| const setupRedirect = setupFormRedirectFor(parsedAppSlug); | ||||||||||||||||||
| if (setupRedirect) return setupRedirect; | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+452
to
+455
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify return statements inside getCredential and compare shape expectations.
rg -n "const getCredential|return \\{ credentialId|return setupRedirect|Promise<\\{ credentialId" apps/web/lib/apps/installation/[[...step]]/getServerSideProps.tsRepository: calcom/cal.diy Length of output: 391 Fix
Suggested fix if (!credentialId && !appMetadata.isOAuth) {
const setupRedirect = setupFormRedirectFor(parsedAppSlug);
- if (setupRedirect) return setupRedirect;
+ if (setupRedirect) return { credentialId: null, redirect: setupRedirect };
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| if ( | ||||||||||||||||||
| !credentialId && | ||||||||||||||||||
| !user.teams.length && | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| BigBlueButton is an open-source virtual classroom and web conferencing platform designed for online learning. It provides real-time sharing of audio, video, slides, chat, and screen, making it ideal for online classes, meetings, and collaborative sessions. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import type { AppMeta } from "@calcom/types/App"; | ||
|
|
||
| export const metadata = { | ||
| name: "BigBlueButton", | ||
| description: | ||
| "BigBlueButton is an open-source virtual classroom and web conferencing platform designed for online learning.", | ||
| installed: true, | ||
| type: "bigbluebutton_video", | ||
| variant: "conferencing", | ||
| categories: ["conferencing"], | ||
| logo: "icon.svg", | ||
| publisher: "Cal.diy", | ||
| url: "https://bigbluebutton.org/", | ||
| slug: "bigbluebutton", | ||
| title: "BigBlueButton", | ||
| isGlobal: false, | ||
| email: "help@cal.com", | ||
| appData: { | ||
| location: { | ||
| linkType: "dynamic", | ||
| type: "integrations:bigbluebutton", | ||
| label: "BigBlueButton Video", | ||
| }, | ||
| }, | ||
| dirName: "bigbluebuttonvideo", | ||
| concurrentMeetings: true, | ||
| isOAuth: false, | ||
| } as AppMeta; | ||
|
|
||
| export default metadata; |
Uh oh!
There was an error while loading. Please reload this page.