A way to link tRPC and pusherJS
npm install @marienilba/prpc
// server.ts
import { initPRPC } from "@marienilba/prpc";
import Pusher from "pusher";
import superjson from "superjson";
import { z } from "zod";
import { createTRPCContext, protectedProcedure } from "./trpc";
export const pusherClient = new Pusher({
appId: process.env.PUSHER_APP_ID,
cluster: process.env.PUSHER_CLUSTER,
key: process.env.PUSHER_KEY,
secret: process.env.PUSHER_SECRET,
});
const p = initPRPC.context().create({
pusher: pusherClient,
transformer: superjson,
context: createTRPCContext,
});
export const prpc = p.createPRPCRouter({
game: p
.presenceRoute({
procedure: protectedProcedure,
user: z.object({
id: z.string(),
name: z.string(),
image: z.string(),
isHost: z.boolean(),
}),
})
.auth(async ({ ctx, data }) => {
return {
id: ctx.session?.user?.id || "",
name: ctx.session?.user?.name || "",
image: ctx.session?.user?.image || "",
isHost: data.isHost || false,
};
}),
});
export type PRPCRouter = typeof prpc;
// trpc/routes/index.ts
import { prpc } from "server/api/prpc";
import { createTRPCRouter, enforceUserIsHost } from "server/api/trpc";
import { z } from "zod";
export const router = createTRPCRouter({
join: prpc.game
.data(
z.object({
joined: z.boolean(),
})
)
.trigger(async ({ ctx, input }) => {
return await ctx.pusher.trigger({
joined: input.joined,
user: input.prpc.me,
});
}),
});
// client.ts
import { createPRPCNext } from "@marienilba/prpc";
import { PRPCRouter } from "server/api/prpc";
import { AppRouter } from "../server/api/root";
import { api } from "./api";
export const prpc = createPRPCNext<AppRouter, PRPCRouter>(api, {
app_key: process.env.NEXT_PUBLIC_PUSHER_KEY!,
options: {
authEndpoint: "/api/prpc/",
cluster: process.env.NEXT_PUBLIC_PUSHER_CLUSTER!,
},
log: process.env.NODE_ENV !== "production" && typeof window !== "undefined",
});
// index.ts
const App = () => {
return (
<prpc.withPRPC {...prpc.context}>
<...>
</prpc.withPRPC>
);
};
// page.ts
const { send, bind, members, unbind_all, me } = prpc.game.useConnect(
id,
{
subscribeOnMount: true,
userDataOnAuth: {
isHost: false,
},
},
() => {
bind("pusher:member_removed", (member) => {
});
bind("join", ({ joined, user }) => {
});
return () => {
unbind_all();
};
},
[]
);
send("join", { joined: !isJoined });
// api/prpc/[prpc].ts
import { createNextApiHandler, createNextWehbookApiHandler } from "@marienilba/prpc";
import { prpc } from "@server/api/prpc";
const webhooks = createNextWehbookApiHandler<typeof prpc>({
presence: async (data, ctx) => {
if (data.name === "member_removed") {
console.log(
`Member ${data.user_id} removed from ${data.channel.channel}`
);
}
},
});
export default createNextApiHandler({
router: prpc,
webhooks,
onError:
process.env.NODE_ENV === "development"
? ({ channel_name, message }) => {
console.error(
`❌ failed on ${channel_name ?? "<no-path>"}: ${message}`
);
}
: undefined,
});