diff --git a/apps/api/src/controllers/error.html b/apps/api/src/controllers/error.html new file mode 100644 index 00000000..b174d613 --- /dev/null +++ b/apps/api/src/controllers/error.html @@ -0,0 +1,62 @@ + + + + + + + Error - OpenPanel + + + + + +
+ +

Oops! Something went wrong

+

We encountered an error while processing your request. Please try again later or contact support if the problem + persists.

+
+ + + \ No newline at end of file diff --git a/apps/api/src/controllers/live.controller.ts b/apps/api/src/controllers/live.controller.ts index 341652fe..61fd2c38 100644 --- a/apps/api/src/controllers/live.controller.ts +++ b/apps/api/src/controllers/live.controller.ts @@ -224,39 +224,3 @@ export async function wsProjectNotifications( getRedisSub().off('message', message as any); }); } - -export async function wsIntegrationsSlack( - connection: { - socket: WebSocket; - }, - req: FastifyRequest<{ - Querystring: { - organizationId?: string; - }; - }>, -) { - const { organizationId } = req.query; - - if (!organizationId) { - connection.socket.send('No organizationId provided'); - connection.socket.close(); - return; - } - - const subscribeToEvent = 'integrations:slack'; - - getRedisSub().subscribe(subscribeToEvent); - const onMessage = (channel: string, message: string) => { - if (channel === subscribeToEvent) { - const parsed = getSuperJson<{ organizationId: string }>(message); - if (parsed && parsed.organizationId === organizationId) { - connection.socket.send(message); - } - } - }; - getRedisSub().on('message', onMessage); - connection.socket.on('close', () => { - getRedisSub().unsubscribe(subscribeToEvent); - getRedisSub().off('message', onMessage); - }); -} diff --git a/apps/api/src/controllers/webhook.controller.ts b/apps/api/src/controllers/webhook.controller.ts index 440acbeb..65b0b14f 100644 --- a/apps/api/src/controllers/webhook.controller.ts +++ b/apps/api/src/controllers/webhook.controller.ts @@ -1,5 +1,6 @@ +import fs from 'node:fs'; +import path from 'node:path'; import type { WebhookEvent } from '@clerk/fastify'; -import { setSuperJson } from '@openpanel/common'; import { AccessLevel, db } from '@openpanel/db'; import { sendSlackNotification, @@ -167,6 +168,7 @@ const paramsSchema = z.object({ const metadataSchema = z.object({ organizationId: z.string(), + projectId: z.string(), integrationId: z.string(), }); @@ -179,7 +181,7 @@ export async function slackWebhook( const parsedParams = paramsSchema.safeParse(request.query); if (!parsedParams.success) { - request.log.error('Invalid params', parsedParams); + request.log.error(parsedParams.error, 'Invalid params'); return reply.status(400).send({ error: 'Invalid params' }); } @@ -192,7 +194,7 @@ export async function slackWebhook( ); if (!parsedMetadata.success) { - request.log.error('Invalid metadata', parsedMetadata.error.errors); + request.log.error(parsedMetadata.error, 'Invalid metadata'); return reply.status(400).send({ error: 'Invalid metadata' }); } @@ -217,10 +219,8 @@ export async function slackWebhook( }, 'Failed to parse slack auth response', ); - return reply - .status(400) - .header('Content-Type', 'text/html') - .send('

Failed to exchange code for token

'); + const html = fs.readFileSync(path.join(__dirname, 'error.html'), 'utf8'); + return reply.status(500).header('Content-Type', 'text/html').send(html); } // Send a notification first to confirm the connection @@ -230,10 +230,12 @@ export async function slackWebhook( '👋 Hello. You have successfully connected OpenPanel.dev to your Slack workspace.', }); + const { projectId, organizationId, integrationId } = parsedMetadata.data; + await db.integration.update({ where: { - id: parsedMetadata.data.integrationId, - organizationId: parsedMetadata.data.organizationId, + id: integrationId, + organizationId, }, data: { config: { @@ -243,22 +245,12 @@ export async function slackWebhook( }, }); - getRedisPub().publish( - 'integrations:slack', - setSuperJson({ - organizationId: parsedMetadata.data.organizationId, - }), + return reply.redirect( + `${process.env.NEXT_PUBLIC_DASHBOARD_URL}/${organizationId}/${projectId}/settings/integrations?tab=installed`, ); - - return reply - .status(200) - .header('Content-Type', 'text/html') - .send('

Slack integration added. You can close this window now.

'); } catch (err) { request.log.error(err); - return reply - .status(500) - .header('Content-Type', 'text/html') - .send('

Failed to exchange code for token

'); + const html = fs.readFileSync(path.join(__dirname, 'error.html'), 'utf8'); + return reply.status(500).header('Content-Type', 'text/html').send(html); } } diff --git a/apps/api/src/routes/live.router.ts b/apps/api/src/routes/live.router.ts index 4cd2766c..9bca7a2d 100644 --- a/apps/api/src/routes/live.router.ts +++ b/apps/api/src/routes/live.router.ts @@ -32,11 +32,6 @@ const liveRouter: FastifyPluginCallback = (fastify, opts, done) => { { websocket: true }, controller.wsProjectNotifications, ); - fastify.get( - '/integrations/slack', - { websocket: true }, - controller.wsIntegrationsSlack, - ); done(); }); diff --git a/apps/dashboard/src/components/integrations/forms/slack-integration.tsx b/apps/dashboard/src/components/integrations/forms/slack-integration.tsx index 77e10600..aef05480 100644 --- a/apps/dashboard/src/components/integrations/forms/slack-integration.tsx +++ b/apps/dashboard/src/components/integrations/forms/slack-integration.tsx @@ -19,49 +19,20 @@ export function SlackIntegrationForm({ defaultValues?: RouterOutputs['integration']['get']; onSuccess: () => void; }) { - const { organizationId } = useAppParams(); - useWS(`/live/integrations/slack?organizationId=${organizationId}`, (res) => { - // @ts-expect-error - console.log('3. slack integration done', window.slackPopup); - // @ts-expect-error - if (window.slackPopup && typeof window.slackPopup.close === 'function') { - console.log('4. close popup'); - // @ts-expect-error - window.slackPopup.close(); - } - onSuccess(); - }); + const { organizationId, projectId } = useAppParams(); + const form = useForm({ defaultValues: { id: defaultValues?.id, organizationId, + projectId, name: defaultValues?.name ?? '', }, resolver: zodResolver(zCreateSlackIntegration), }); const mutation = api.integration.createOrUpdateSlack.useMutation({ async onSuccess(res) { - console.log('1. onSuccess', res); - - const url = res.slackInstallUrl; - const width = 600; - const height = 800; - const left = window.screenX + (window.outerWidth - width) / 2; - const top = window.screenY + (window.outerHeight - height) / 2.5; - console.log('2. open popup'); - // @ts-expect-error - window.slackPopup = window.open( - url, - '', - `toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=${width}, height=${height}, top=${top}, left=${left}`, - ); - - // The popup might have been blocked, so we redirect the user to the URL instead - // - // @ts-expect-error - if (!window.slackPopup) { - window.location.href = url; - } + window.location.href = res.slackInstallUrl; }, onError() { toast.error('Failed to create integration'); diff --git a/packages/integrations/src/slack.ts b/packages/integrations/src/slack.ts index 4b02f385..be8d98c8 100644 --- a/packages/integrations/src/slack.ts +++ b/packages/integrations/src/slack.ts @@ -18,7 +18,8 @@ export const slackInstaller = new InstallProvider({ export const getSlackInstallUrl = ({ integrationId, organizationId, -}: { integrationId: string; organizationId: string }) => { + projectId, +}: { integrationId: string; organizationId: string; projectId: string }) => { return slackInstaller.generateInstallUrl({ scopes: [ 'incoming-webhook', @@ -27,7 +28,7 @@ export const getSlackInstallUrl = ({ 'team:read', ], redirectUri: SLACK_OAUTH_REDIRECT_URL, - metadata: JSON.stringify({ integrationId, organizationId }), + metadata: JSON.stringify({ integrationId, organizationId, projectId }), }); }; diff --git a/packages/trpc/src/routers/integration.ts b/packages/trpc/src/routers/integration.ts index f6270b02..cee6f285 100644 --- a/packages/trpc/src/routers/integration.ts +++ b/packages/trpc/src/routers/integration.ts @@ -66,6 +66,7 @@ export const integrationRouter = createTRPCRouter({ slackInstallUrl: await getSlackInstallUrl({ integrationId: res.id, organizationId: input.organizationId, + projectId: input.projectId, }), }; } @@ -84,6 +85,7 @@ export const integrationRouter = createTRPCRouter({ slackInstallUrl: await getSlackInstallUrl({ integrationId: res.id, organizationId: input.organizationId, + projectId: input.projectId, }), }; }), diff --git a/packages/validation/src/index.ts b/packages/validation/src/index.ts index b6085c4b..341e4d91 100644 --- a/packages/validation/src/index.ts +++ b/packages/validation/src/index.ts @@ -214,6 +214,7 @@ const zCreateIntegration = z.object({ id: z.string().optional(), name: z.string().min(1), organizationId: z.string().min(1), + projectId: z.string().min(1), }); export const zCreateSlackIntegration = zCreateIntegration;