Files
DiscordClone/convex/typing.ts
Bryan1029384756 ff269ee154 nickname
2026-02-18 10:16:12 -06:00

105 lines
2.6 KiB
TypeScript

import { query, mutation, internalMutation } from "./_generated/server";
import { v } from "convex/values";
import { internal } from "./_generated/api";
const TYPING_TTL_MS = 6000;
export const startTyping = mutation({
args: {
channelId: v.id("channels"),
userId: v.id("userProfiles"),
username: v.string(),
},
returns: v.null(),
handler: async (ctx, args) => {
const expiresAt = Date.now() + TYPING_TTL_MS;
const existing = await ctx.db
.query("typingIndicators")
.withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
.collect();
const userTyping = existing.find((t) => t.userId === args.userId);
if (userTyping) {
await ctx.db.patch(userTyping._id, { expiresAt });
} else {
await ctx.db.insert("typingIndicators", {
channelId: args.channelId,
userId: args.userId,
username: args.username,
expiresAt,
});
}
await ctx.scheduler.runAfter(TYPING_TTL_MS, internal.typing.cleanExpired, {});
return null;
},
});
export const stopTyping = mutation({
args: {
channelId: v.id("channels"),
userId: v.id("userProfiles"),
},
returns: v.null(),
handler: async (ctx, args) => {
const indicators = await ctx.db
.query("typingIndicators")
.withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
.collect();
const mine = indicators.find((t) => t.userId === args.userId);
if (mine) {
await ctx.db.delete(mine._id);
}
return null;
},
});
export const getTyping = query({
args: { channelId: v.id("channels") },
returns: v.array(
v.object({
userId: v.id("userProfiles"),
username: v.string(),
displayName: v.union(v.string(), v.null()),
})
),
handler: async (ctx, args) => {
const now = Date.now();
const indicators = await ctx.db
.query("typingIndicators")
.withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
.collect();
const active = indicators.filter((t) => t.expiresAt > now);
const results = [];
for (const t of active) {
const user = await ctx.db.get(t.userId);
results.push({
userId: t.userId,
username: t.username,
displayName: user?.displayName || null,
});
}
return results;
},
});
export const cleanExpired = internalMutation({
args: {},
returns: v.null(),
handler: async (ctx) => {
const now = Date.now();
const expired = await ctx.db.query("typingIndicators").collect();
for (const t of expired) {
if (t.expiresAt <= now) {
await ctx.db.delete(t._id);
}
}
return null;
},
});