diff --git a/package-lock.json b/package-lock.json
index b8236ba..0d2a329 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -43,6 +43,7 @@
"pg": "^8.16.3",
"react": "19.1.0",
"react-dom": "19.1.0",
+ "react-iframes-bridge": "^1.0.1",
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1",
"sonner": "^2.0.7",
@@ -4802,6 +4803,16 @@
"react": ">=16.13.1"
}
},
+ "node_modules/react-iframes-bridge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/react-iframes-bridge/-/react-iframes-bridge-1.0.1.tgz",
+ "integrity": "sha512-sjUbHOF7x23nZCek+i3okMasTBYKhvlJXYZe3IBvF/8GAspEG/uHxeoVvLmTj2IZKdUTcKBPNCqCB8lz6LFxTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "uuid": "^9.0.0",
+ "zod": "^3.22.4"
+ }
+ },
"node_modules/react-markdown": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
@@ -5381,6 +5392,19 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/vfile": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
@@ -5483,6 +5507,15 @@
"url": "https://github.com/sponsors/dmonad"
}
},
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
"node_modules/zustand": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz",
diff --git a/package.json b/package.json
index 9106001..199c567 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
"pg": "^8.16.3",
"react": "19.1.0",
"react-dom": "19.1.0",
+ "react-iframes-bridge": "^1.0.1",
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1",
"sonner": "^2.0.7",
diff --git a/src/app/chat/[id]/page.tsx b/src/app/chat/[id]/page.tsx
index 69b9284..d62c919 100644
--- a/src/app/chat/[id]/page.tsx
+++ b/src/app/chat/[id]/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import MessageBox from "@/components/MessageBox";
+import MessageBox from "@/components/channel/MessageBox";
import AppLayout from "@/components/app/AppLayout";
import { useParams, useRouter } from "next/navigation";
import useApp from "@/hooks/use-app";
diff --git a/src/app/server/[ip]/page.tsx b/src/app/server/[ip]/page.tsx
index 971f9ab..de76580 100644
--- a/src/app/server/[ip]/page.tsx
+++ b/src/app/server/[ip]/page.tsx
@@ -2,14 +2,14 @@
import { useEffect, useRef, useState } from "react";
import { useParams, useSearchParams } from "next/navigation";
-import MessageBox from "@/components/MessageBox";
+import MessageBox from "@/components/channel/MessageBox";
import AppLayout from "@/components/app/AppLayout";
import { Server as ServerType } from "@/types/types";
import auth from "@/lib/auth";
import { useServerMessages } from "@/hooks/use-messages";
import useApp from "@/hooks/use-app";
import { useEffectOnceWhenReady } from "@/hooks/use-once";
-import ChannelView from "@/components/ChannelView";
+import ChannelView from "@/components/channel/ChannelView";
export default function Server() {
const { ip } = useParams<{ ip: string }>();
diff --git a/src/components/ChannelView.tsx b/src/components/channel/ChannelView.tsx
similarity index 81%
rename from src/components/ChannelView.tsx
rename to src/components/channel/ChannelView.tsx
index 9f193db..d77542e 100644
--- a/src/components/ChannelView.tsx
+++ b/src/components/channel/ChannelView.tsx
@@ -1,7 +1,8 @@
import { Channel, Message, Server } from "@/types/types";
import MessageBox from "./MessageBox";
import App from "@/types/app";
-import React, { Ref } from "react";
+import React, { Ref, useEffect, useRef } from "react";
+import PluginChannel from "./PluginChannel";
export default function ChannelView({
app,
@@ -47,6 +48,13 @@ export default function ChannelView({
return null;
default:
- return ;
+ return (
+
+ );
}
}
diff --git a/src/components/MessageBox.tsx b/src/components/channel/MessageBox.tsx
similarity index 97%
rename from src/components/MessageBox.tsx
rename to src/components/channel/MessageBox.tsx
index 06cb565..73d7f61 100644
--- a/src/components/MessageBox.tsx
+++ b/src/components/channel/MessageBox.tsx
@@ -1,24 +1,24 @@
"use client";
import { Message } from "@/types/types";
-import { Button } from "./ui/button";
-import { Input } from "./ui/input";
+import { Button } from "../ui/button";
+import { Input } from "../ui/input";
import * as emoji from "emoji-picker-react";
import EmojiPicker from "emoji-picker-react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
-} from "./ui/dropdown-menu";
+} from "../ui/dropdown-menu";
import { ChevronLeftIcon, SendIcon, SmilePlusIcon } from "lucide-react";
import { useRef, useState } from "react";
import { format, isToday, isYesterday } from "date-fns";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { UserProfile } from "@/hooks/get-user";
-import ProfilePicture from "./ProfilePicture";
+import ProfilePicture from "../ProfilePicture";
import App from "@/types/app";
-import { Textarea } from "./ui/textarea";
+import { Textarea } from "../ui/textarea";
function MessageContainer({ message, app }: { message: Message; app: App }) {
const [profile, setProfile] = useState();
diff --git a/src/components/channel/PluginChannel.tsx b/src/components/channel/PluginChannel.tsx
new file mode 100644
index 0000000..62bb1b8
--- /dev/null
+++ b/src/components/channel/PluginChannel.tsx
@@ -0,0 +1,50 @@
+import { Message } from "@/types/types";
+import App from "@/types/app";
+import React, { useEffect, useRef } from "react";
+import { useParentCommunication } from "react-iframes-bridge";
+
+export default function PluginChannel({
+ app,
+ messages,
+ ip,
+ serverRef,
+}: {
+ app: App;
+ messages: Record;
+ ip: string;
+ serverRef: React.RefObject;
+}) {
+ if (!app.currentChannel)
+ return (
+
+ );
+
+ const iframeRef = useRef(null);
+
+ const parentComm = useParentCommunication(iframeRef, {
+ allowedOrigins: [new URL(app.currentChannel.kind).origin],
+ communication: { debug: false },
+ });
+
+ useEffect(() => {
+ parentComm.onMessage("GET_MESSAGES", () => {
+ parentComm.sendToChild("MESSAGES", messages);
+ });
+
+ parentComm.onMessage("SEND_MESSAGE", (msg: string) => {
+ serverRef.current?.send(
+ JSON.stringify({
+ type: "send_message",
+ params: {
+ channel_id: app.currentChannel?.id,
+ contents: msg,
+ },
+ })
+ );
+ });
+ }, [parentComm]);
+
+ return (
+
+ );
+}