Tamás Soós
Tamás Soós10mo ago

tRPC over WebSocket with Next.js and NextAuth

Hi. I'm trying to set up Next 13 with tRPC over a WebSocket and NextAuth. I got it mostly working with:
// route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { createContext } from '@/server/context';
import { appRouter } from '@/server/routers/app';

const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext,
batching: {
enabled: true,
},
});

export { handler as GET, handler as POST };
// route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { createContext } from '@/server/context';
import { appRouter } from '@/server/routers/app';

const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext,
batching: {
enabled: true,
},
});

export { handler as GET, handler as POST };
// context.ts
import * as trpc from '@trpc/server';
import * as trpcNext from '@trpc/server/adapters/fetch';
import { NodeHTTPCreateContextFnOptions } from '@trpc/server/adapters/node-http';
import { IncomingMessage } from 'node:http';
import ws from 'ws';
import { getSession } from 'next-auth/react';

export async function createContext(
opts:
| NodeHTTPCreateContextFnOptions<IncomingMessage, ws>
| trpcNext.FetchCreateContextFnOptions
) {
const req = opts?.req;

const session = req && (await getSession({ req }));

return {
req,
session,
};
}

export type Context = trpc.inferAsyncReturnType<typeof createContext>;
// context.ts
import * as trpc from '@trpc/server';
import * as trpcNext from '@trpc/server/adapters/fetch';
import { NodeHTTPCreateContextFnOptions } from '@trpc/server/adapters/node-http';
import { IncomingMessage } from 'node:http';
import ws from 'ws';
import { getSession } from 'next-auth/react';

export async function createContext(
opts:
| NodeHTTPCreateContextFnOptions<IncomingMessage, ws>
| trpcNext.FetchCreateContextFnOptions
) {
const req = opts?.req;

const session = req && (await getSession({ req }));

return {
req,
session,
};
}

export type Context = trpc.inferAsyncReturnType<typeof createContext>;
Solution:
My guess is that next-auth tries to use
import { headers } from 'next/headers';
import { headers } from 'next/headers';
but when you are not using Next, in the case of your ws server that import does nothing. You might want to take a look at examples-next-prisma-websocket-starter, if you are looking for a method to implement tRPC + Next + Next Auth + Websockets....
GitHub
GitHub - BeBoRE/ei-noah-bot: De officiële Discord Bot voor de Sweat...
De officiële Discord Bot voor de Sweaty GG Chat. Contribute to BeBoRE/ei-noah-bot development by creating an account on GitHub.
Jump to solution
5 Replies
Tamás Soós
Tamás Soós10mo ago
and a provider component
Tamás Soós
Tamás Soós10mo ago
My issue is with the createContext call. The opts type isn't quite right. getSession is giving me:
Type 'IncomingMessage | Request' is not assignable to type '(Partial<IncomingMessage> & { body?: any; }) | undefined'.
Type 'Request' is not assignable to type 'Partial<IncomingMessage> & { body?: any; }'.
Type 'Request' is not assignable to type 'Partial<IncomingMessage>'.
Types of property 'headers' are incompatible.
Type 'Headers' is not assignable to type 'IncomingHttpHeaders'.
Index signature for type 'string' is missing in type 'Headers'.ts(2322)
(property) CtxOrReq.req?: (Partial<IncomingMessage> & {
body?: any;
}) | undefined
Type 'IncomingMessage | Request' is not assignable to type '(Partial<IncomingMessage> & { body?: any; }) | undefined'.
Type 'Request' is not assignable to type 'Partial<IncomingMessage> & { body?: any; }'.
Type 'Request' is not assignable to type 'Partial<IncomingMessage>'.
Types of property 'headers' are incompatible.
Type 'Headers' is not assignable to type 'IncomingHttpHeaders'.
Index signature for type 'string' is missing in type 'Headers'.ts(2322)
(property) CtxOrReq.req?: (Partial<IncomingMessage> & {
body?: any;
}) | undefined
even though it happens to be working for now. Btw getSession creates infinite reconnects on the ws client with any other inputs and getServerSession does the same except I couldn't find any arguments that work at all. Adding a try catch in there slows down the reconnect attempts to a few a seconds vs the original that reconnected as fast as it could. getServerSession throws
Error: Invariant: headers() expects to have requestAsyncStorage, none available.
at headers (/Users/tamas/Projects/oddp/node_modules/next/dist/client/components/headers.js:42:15)
at getServerSession (/Users/tamas/Projects/oddp/node_modules/next-auth/next/index.js:139:35)
at createContext (/Users/tamas/Projects/oddp/src/server/context.ts:22:21)
at WebSocketServer.<anonymous> (/Users/tamas/Projects/oddp/node_modules/@trpc/server/dist/adapters/ws.js:82:28)
at WebSocketServer.emit (node:events:526:35)
at WebSocketServer.emit (node:domain:488:12)
at WebSocketServer.completeUpgrade (/Users/tamas/Projects/oddp/node_modules/ws/lib/websocket-server.js:427:5)
at WebSocketServer.handleUpgrade (/Users/tamas/Projects/oddp/node_modules/ws/lib/websocket-server.js:336:10)
at Server.upgrade (/Users/tamas/Projects/oddp/node_modules/ws/lib/websocket-server.js:112:16)
at Server.emit (node:events:514:28)
Error: Invariant: headers() expects to have requestAsyncStorage, none available.
at headers (/Users/tamas/Projects/oddp/node_modules/next/dist/client/components/headers.js:42:15)
at getServerSession (/Users/tamas/Projects/oddp/node_modules/next-auth/next/index.js:139:35)
at createContext (/Users/tamas/Projects/oddp/src/server/context.ts:22:21)
at WebSocketServer.<anonymous> (/Users/tamas/Projects/oddp/node_modules/@trpc/server/dist/adapters/ws.js:82:28)
at WebSocketServer.emit (node:events:526:35)
at WebSocketServer.emit (node:domain:488:12)
at WebSocketServer.completeUpgrade (/Users/tamas/Projects/oddp/node_modules/ws/lib/websocket-server.js:427:5)
at WebSocketServer.handleUpgrade (/Users/tamas/Projects/oddp/node_modules/ws/lib/websocket-server.js:336:10)
at Server.upgrade (/Users/tamas/Projects/oddp/node_modules/ws/lib/websocket-server.js:112:16)
at Server.emit (node:events:514:28)
Solution
BeBoRE
BeBoRE10mo ago
My guess is that next-auth tries to use
import { headers } from 'next/headers';
import { headers } from 'next/headers';
but when you are not using Next, in the case of your ws server that import does nothing. You might want to take a look at examples-next-prisma-websocket-starter, if you are looking for a method to implement tRPC + Next + Next Auth + Websockets. I opted to use LuciaJS, because it's a little bit more versatile and less restrictive, but it's a little bit more complex and requires more boilerplate code. https://github.com/BeBoRE/ei-noah-bot
GitHub
GitHub - BeBoRE/ei-noah-bot: De officiële Discord Bot voor de Sweat...
De officiële Discord Bot voor de Sweaty GG Chat. Contribute to BeBoRE/ei-noah-bot development by creating an account on GitHub.
Tamás Soós
Tamás Soós10mo ago
Yes, that's a nice starter. The type issue in their context file I was able to solve by only defining the type there, and using getServerSession in the api route vs getSession in the ws server. Still it would be nice to use getServerSession in both places. I'll check out Lucia. The docs are nice. Thanks. Since in the end this is more of a NextAuth issue, I asked them directly on github. I'll close this post.