Skip to content

Commit 9cd53be

Browse files
committed
chore: refactor to GraphQLSubscriptionHandler class
1 parent b315275 commit 9cd53be

File tree

1 file changed

+108
-31
lines changed

1 file changed

+108
-31
lines changed

src/core/handlers/GraphQLSubscriptionHandler.ts

Lines changed: 108 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DocumentNode, OperationTypeNode } from 'graphql'
1+
import { OperationTypeNode } from 'graphql'
22
import type {
33
GraphQLHandlerNameSelector,
44
GraphQLQuery,
@@ -11,10 +11,11 @@ import { WebSocketHandler } from './WebSocketHandler'
1111
import { jsonParse } from '../utils/internal/jsonParse'
1212
import type { TypedDocumentNode } from '../graphql'
1313
import { BatchHandler } from './BatchHandler'
14+
import { createRequestId } from '@mswjs/interceptors'
1415

1516
export interface GraphQLPubsub {
1617
/**
17-
* A WebSocket handler to intercept GraphQL subscription events.
18+
* A WebSocket handler associated with this pubsub.
1819
*/
1920
handler: WebSocketHandler
2021

@@ -32,11 +33,13 @@ export interface GraphQLPubsub {
3233
type GraphQLWebSocketInitMessage = {
3334
type: 'connection_init'
3435
}
36+
3537
type GraphQLWebSocketSubscribeMessage = {
3638
type: 'subscribe'
3739
id: string
3840
payload: GraphQLWebSocketSubscribeMessagePayload
3941
}
42+
4043
type GraphQLWebSocketCompleteMessage = {
4144
type: 'complete'
4245
id: string
@@ -50,6 +53,7 @@ type GraphQLWebSocketOutgoingMessage =
5053
type GraphQLWebSocketAcknowledgeMessage = {
5154
type: 'connection_ack'
5255
}
56+
5357
type GraphQLWebSocketNextMessage = {
5458
type: 'next'
5559
id: string
@@ -192,37 +196,73 @@ export class GraphQLSubscription<
192196
}
193197
}
194198

195-
export type GraphQLSubscriptionHandler = <
199+
export type GraphQLSubscriptionName<
200+
Query extends GraphQLQuery,
201+
Variables extends GraphQLVariables,
202+
> = GraphQLHandlerNameSelector | TypedDocumentNode<Query, Variables>
203+
204+
export type GraphQLSubscriptionResolver<
205+
Query extends GraphQLQuery = GraphQLQuery,
206+
Variables extends GraphQLVariables = GraphQLVariables,
207+
> = (info: GraphQLSubscriptionResolverInfo<Query, Variables>) => void
208+
209+
export type GraphQLSubscriptionHandlerFactory = <
196210
Query extends GraphQLQuery = GraphQLQuery,
197211
Variables extends GraphQLVariables = GraphQLVariables,
198212
>(
199-
operationName:
200-
| GraphQLHandlerNameSelector
201-
| DocumentNode
202-
| TypedDocumentNode<Query, Variables>,
203-
resolver: (info: GraphQLSubscriptionHandlerInfo<Query, Variables>) => void,
204-
) => BatchHandler | WebSocketHandler
205-
206-
export interface GraphQLSubscriptionHandlerInfo<
213+
operationName: GraphQLSubscriptionName<Query, Variables>,
214+
resolver: GraphQLSubscriptionResolver<Query, Variables>,
215+
) => GraphQLSubscriptionHandler<Query, Variables>
216+
217+
export interface GraphQLSubscriptionResolverInfo<
207218
Query extends GraphQLQuery,
208219
Variables extends GraphQLVariables,
209220
> {
210221
params: PathParams
211222
operationName: string
212223

213224
/**
214-
* An object representing the intercepted GraphQL subscription.
225+
* Intercepted GraphQL subscription.
215226
*/
216227
subscription: GraphQLSubscription<Query, Variables>
217228
}
218229

219-
export function createGraphQLSubscriptionHandler(
220-
internalPubsub: GraphQLInternalPubsub,
221-
): GraphQLSubscriptionHandler {
222-
return (operationName, resolver) => {
223-
const subscriptionHandler = internalPubsub.webSocketLink.addEventListener(
230+
interface GraphQLSubscriptionHandlerArgs<
231+
Query extends GraphQLQuery,
232+
Variables extends GraphQLVariables,
233+
> {
234+
info: GraphQLSubscriptionHandlerInfo<Query, Variables>
235+
pubsub: GraphQLInternalPubsub
236+
resolver: GraphQLSubscriptionResolver<any, any>
237+
}
238+
239+
interface GraphQLSubscriptionHandlerInfo<
240+
Query extends GraphQLQuery,
241+
Variables extends GraphQLVariables,
242+
> {
243+
header: string
244+
operationName: GraphQLSubscriptionName<Query, Variables>
245+
}
246+
247+
export class GraphQLSubscriptionHandler<
248+
Query extends GraphQLQuery,
249+
Variables extends GraphQLVariables,
250+
/**
251+
* @fixme This CANNOT be a batch handler.
252+
* I'm not sure a batch handler is a good concept overall.
253+
* It "unfolds" its entries, making it super unclear who
254+
* actually originated them. Consider "fallthrough" WebSocket handler here.
255+
*/
256+
> extends BatchHandler {
257+
public id: string
258+
public info: GraphQLSubscriptionHandlerInfo<Query, Variables>
259+
260+
constructor(
261+
private readonly args: GraphQLSubscriptionHandlerArgs<Query, Variables>,
262+
) {
263+
const subscriptionHandler = args.pubsub.webSocketLink.addEventListener(
224264
'connection',
225-
({ client, params }) => {
265+
({ params, client }) => {
226266
client.addEventListener('message', async (event) => {
227267
if (typeof event.data !== 'string') {
228268
return
@@ -241,14 +281,14 @@ export function createGraphQLSubscriptionHandler(
241281

242282
if (
243283
node.operationType === OperationTypeNode.SUBSCRIPTION &&
244-
node.operationName === operationName
284+
node.operationName === args.info.operationName
245285
) {
246286
const subscription = new GraphQLSubscription<any, any>({
247287
message,
248-
internalPubsub,
288+
internalPubsub: args.pubsub,
249289
})
250290

251-
resolver({
291+
args.resolver({
252292
params,
253293
operationName: node.operationName,
254294
subscription,
@@ -259,19 +299,56 @@ export function createGraphQLSubscriptionHandler(
259299
},
260300
)
261301

302+
const isPubsubHandlerAdded = Reflect.get(
303+
args.pubsub.pubsub.handler,
304+
kPubsubHandlerAdded,
305+
)
306+
262307
// Skip adding the internal pubsub WebSocket handler if it was already added.
263-
// We only need that handler once, and there's no need to pollute the handlers.
264-
if (Reflect.get(internalPubsub.pubsub.handler, kPubSubHandlerAdded)) {
265-
return subscriptionHandler
308+
// We only need that handler once, and there's no need to pollute the handlers.
309+
if (isPubsubHandlerAdded) {
310+
super([subscriptionHandler])
311+
} else {
312+
super([args.pubsub.pubsub.handler, subscriptionHandler])
313+
314+
/**
315+
* @todo @fixme This will NOT reset on `.resetHandlers()`,
316+
* breaking the subscriptions interception.
317+
*/
318+
Reflect.set(args.pubsub.pubsub.handler, kPubSubHandlerAdded, true)
266319
}
267320

268-
// If this is the first time using this subscription, add a batch handler.
269-
const batchSubscriptionHandler = new BatchHandler([
270-
internalPubsub.pubsub.handler,
271-
subscriptionHandler,
272-
])
273-
Reflect.set(internalPubsub.pubsub.handler, kPubSubHandlerAdded, true)
321+
this.id = createRequestId()
322+
this.info = args.info
323+
}
324+
}
325+
326+
function getDisplayOperationName(
327+
_operationName: GraphQLSubscriptionName<any, any>,
328+
): string {
329+
/**
330+
* @todo
331+
*/
332+
return '???'
333+
}
334+
335+
const kPubsubHandlerAdded = Symbol('pubsubHandlerAdded')
274336

275-
return batchSubscriptionHandler
337+
export function createGraphQLSubscriptionHandler(
338+
internalPubsub: GraphQLInternalPubsub,
339+
): GraphQLSubscriptionHandlerFactory {
340+
return (operationName, resolver) => {
341+
return new GraphQLSubscriptionHandler({
342+
/**
343+
* @fixme Remove this and instead add `info` support to
344+
* BatchHandler. Fill in the values in GraphQLSubscriptionHandler.
345+
*/
346+
info: {
347+
header: getDisplayOperationName(operationName),
348+
operationName,
349+
},
350+
pubsub: internalPubsub,
351+
resolver,
352+
})
276353
}
277354
}

0 commit comments

Comments
 (0)