From 9c3c9d67863f75f325bf826bbc8ec2f5bd16b5a0 Mon Sep 17 00:00:00 2001 From: Yuto Nishida Date: Sun, 11 Jan 2026 16:29:51 -0800 Subject: [PATCH] [Chatproto3] Example messages --- .../chatproto3/src/components/MessageDemo.tsx | 153 +++++++++++++++++- .../chatproto3/src/pages/channel.astro | 10 +- 2 files changed, 150 insertions(+), 13 deletions(-) diff --git a/experimental/chatproto3/src/components/MessageDemo.tsx b/experimental/chatproto3/src/components/MessageDemo.tsx index fffa897..895e6b0 100644 --- a/experimental/chatproto3/src/components/MessageDemo.tsx +++ b/experimental/chatproto3/src/components/MessageDemo.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { CompositeHandleResolver, DohJsonHandleResolver, @@ -8,6 +8,17 @@ import { WebDidDocumentResolver, LocalActorResolver, } from '@atcute/identity-resolver'; +import { Client, simpleFetchHandler } from '@atcute/client'; +import type { ActorIdentifier } from '@atcute/lexicons'; +import { isActorIdentifier } from '@atcute/lexicons/syntax'; +import type {} from '@atcute/atproto'; // Augments XRPCQueries with com.atproto.* methods +import { safeParse } from '@atcute/lexicons'; +import { + AppAndrefChatproto3Channel, + AppAndrefChatproto3Message, + AppAndrefChatproto3Writers, +} from '../lexicons'; + const handleResolver = new CompositeHandleResolver({ strategy: 'race', // default - first successful response wins methods: { @@ -28,15 +39,141 @@ const actorResolver = new LocalActorResolver({ interface MessageDemoProps { hintChannelOwner?: string; - channelNsid: string; + channelNsid?: string; } -export default function MessageDemo({ - hintChannelOwner, - channelNsid, -}: MessageDemoProps) { - if (!hintChannelOwner) { +export default function MessageDemo() { + const [queryParams, setQueryParams] = useState<{ + hintChannelOwner?: string; + channelNsid?: string; + }>({}); + useEffect(() => { + // Using URLSearchParams (modern approach) + const params = new URLSearchParams(window.location.search); + + // Convert to a plain object + const paramsObj = {} as any; + for (const [key, value] of params.entries()) { + paramsObj[key] = value; + } + + setQueryParams({...paramsObj}); + console.log('Query Params:', paramsObj); + }, []); + console.log('Current Query Params:', queryParams); + + const [messages, setMessages] = useState<{ + displayName: string; + message: AppAndrefChatproto3Message.Main; + }[]>([]); + const [channelName, setChannelName] = useState(''); + const [channelWriters, setChannelWriters] = useState([]); + + useEffect(() => { + async function fetchMessages() { + if (queryParams.hintChannelOwner === undefined || queryParams.channelNsid === undefined || !isActorIdentifier(queryParams.hintChannelOwner)) return; + + let actor = null; + try { + actor = await actorResolver.resolve(queryParams.hintChannelOwner); + } catch (error) { + console.log('Failed to resolve actor:', error); + } + if (actor === null) return; + + const channelOwnerRpc = new Client({ + handler: simpleFetchHandler({ service: actor.pds }), + }); + const channel = await channelOwnerRpc.get('com.atproto.repo.getRecord', { + params: { + repo: actor.handle, + collection: 'app.andref.chatproto3.channel', + rkey: queryParams.channelNsid, + }, + }); + if (!channel.ok) { + console.log('Failed to fetch channel record:', channel); + return; + } + const channelData = safeParse(AppAndrefChatproto3Channel.mainSchema, channel.data.value); + if (!channelData.ok) { + console.log('Failed to parse channel record:', channelData.issues); + console.log('Raw data:', channel.data); + return; + } + console.log('Channel data:', channelData); + setChannelName(channelData.value.name); + + const writers = channelData.value.writers || []; + setChannelWriters(writers); + + // TODO: Handle `since` and multiple writers changing over time + // Fetch messages + const rule = writers[0]; + if (!Array.isArray(rule.identifiers)) { + return; + } + const messagesByUser = await Promise.all(rule.identifiers.map(async (identifier) => { + const actor = await actorResolver.resolve(identifier); + if (actor === null) return null; + + const writerRpc = new Client({ + handler: simpleFetchHandler({ service: actor.pds }), + }); + const messages = await writerRpc.get('com.atproto.repo.listRecords', { + params: { + repo: actor.handle, + collection: 'app.andref.chatproto3.message', + reverse: true, + limit: 100, + }, + }); + if (!messages.ok) { + console.log('Failed to fetch messages:', messages); + return null; + } + // TODO: handle pagination properly + const allMessageDataFromCurrentUser = messages.data.records.map((record) => { + const parsed = safeParse(AppAndrefChatproto3Message.mainSchema, record.value); + if (!parsed.ok) { + console.log('Failed to parse message record:', parsed.issues); + console.log('Raw message data:', record); + return null; + } + return parsed.value; + }); + const relevantMessages = allMessageDataFromCurrentUser.filter((message) => { + return message !== null; + }).filter((message) => { + return message.channel === queryParams.channelNsid; + }); + return { + displayName: actor.handle, + messages: relevantMessages, + }; + })).then((results) => results.filter((res) => res !== null)); + + const allMessages = messagesByUser.flatMap((userMessages) => { + return userMessages.messages.map((message) => ({ + displayName: userMessages.displayName, + message, + })); + }).sort((a, b) => { + return new Date(a.message.createdAt) < new Date(b.message.createdAt) ? 1 : -1 + }); + + setMessages(allMessages); + } + fetchMessages(); + }, [queryParams]); + + if (!queryParams.hintChannelOwner) { return

Please provide a hintChannelOwner.

} - return
Placeholder
+ return messages.map(({ displayName, message }) =>
+

{displayName}

+

{new Date(message.createdAt).toISOString()}

+ {/* TODO: handle null plaintext properly */} +

{message.plaintext}

+
).toReversed(); } \ No newline at end of file diff --git a/experimental/chatproto3/src/pages/channel.astro b/experimental/chatproto3/src/pages/channel.astro index c35a313..b75a9ee 100644 --- a/experimental/chatproto3/src/pages/channel.astro +++ b/experimental/chatproto3/src/pages/channel.astro @@ -1,10 +1,10 @@ --- import MessageDemo from '../components/MessageDemo.jsx'; -interface Props { - hintChannelOwner?: string; - channelNsid: string; -} +const queryParams = { + hintChannelOwner: Astro.url.searchParams.get('hintChannelOwner') || undefined, + channelNsid: Astro.url.searchParams.get('channelNsid') || undefined, +}; // Full Astro Component Syntax: // https://docs.astro.build/basics/astro-components/ @@ -29,7 +29,7 @@ interface Props {
- {} + {}
-- 2.51.2