Skip to content

Commit

Permalink
Merge pull request #39 from ensdomains/fix/gql-middleware
Browse files Browse the repository at this point in the history
Fix GraphQL nullbyte middleware
  • Loading branch information
TateB authored Aug 26, 2022
2 parents e84048b + b497039 commit a233702
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 40 deletions.
9 changes: 5 additions & 4 deletions packages/ensjs/src/GqlManager.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { parse, print, visit } from 'graphql'
import traverse from 'traverse'
import { visit, parse } from 'graphql'

import { requestMiddleware, responseMiddleware, enter } from './GqlManager'
import { requestMiddleware, responseMiddleware } from './GqlManager'
import { namehash } from './utils/normalise'

describe('GqlManager', () => {
Expand Down Expand Up @@ -153,8 +153,9 @@ query getNames($id: ID!, $expiryDate: Int) {

describe('requestMiddleware', () => {
it('should add id to a SelectionSet if name is present and id is not', () => {
const result = requestMiddleware(visit, parse)(mockRequest)
expect(result.body.includes('id')).toBe(true)
const result = requestMiddleware(visit, parse, print)(mockRequest)
const body = result.body as string
expect(body.match(/domain.*id.*id.*domains.*id.*id/)?.length).toBe(1)
})
})
describe('responseMiddleware', () => {
Expand Down
90 changes: 54 additions & 36 deletions packages/ensjs/src/GqlManager.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
// @ts-nocheck
import type {
Kind,
parse as Parse,
print as Print,
SelectionSetNode,
visit as Visit,
} from 'graphql'
import type Traverse from 'traverse'
import { namehash } from './utils/normalise'

const generateSelection = (selection: any) => ({
kind: 'Field',
kind: 'Field' as Kind.FIELD,
name: {
kind: 'Name',
kind: 'Name' as Kind.NAME,
value: selection,
},
arguments: [],
Expand All @@ -13,62 +20,73 @@ const generateSelection = (selection: any) => ({
selectionSet: undefined,
})

export const enter = (node: any) => {
if (node.kind === 'SelectionSet') {
const id = node.selections.find((x) => x.name && x.name.value === 'id')
const name = node.selections.find((x) => x.name && x.name.value === 'name')
export const enter = (node: SelectionSetNode) => {
let hasName = false
let hasId = false

if (!id && name) {
node.selections = [...node.selections, generateSelection('id')]
return node
for (const selection of node.selections) {
if ('name' in selection) {
if (selection.name.value === 'name') hasName = true
else if (selection.name.value === 'id') hasId = true
}
}

if (hasName && !hasId) {
node.selections = [...node.selections, generateSelection('id')]
return node
}
}

export const requestMiddleware = (visit, parse) => (request: any) => {
const requestBody = JSON.parse(request.body)
const rawQuery = requestBody.query
const parsedQuery = parse(rawQuery)
const updatedQuery = visit(parsedQuery, { enter })
const updatedBody = { ...requestBody, query: updatedQuery.loc.source.body }
export const requestMiddleware =
(visit: typeof Visit, parse: typeof Parse, print: typeof Print) =>
(request: any) => {
const requestBody = JSON.parse(request.body)
const rawQuery = requestBody.query
const parsedQuery = parse(rawQuery)
const updatedQuery = visit(parsedQuery, {
SelectionSet: {
enter,
},
})

return {
...request,
body: JSON.stringify(updatedBody),
return {
...request,
body: JSON.stringify({ ...requestBody, query: print(updatedQuery) }),
}
}
}

export const responseMiddleware = (traverse) => (response: any) => {
traverse(response).forEach(function (responseItem: any) {
if (responseItem instanceof Object && responseItem.name) {
//Name already in hashed form
if (responseItem.name && responseItem.name.includes('[')) {
return
}
export const responseMiddleware =
(traverse: typeof Traverse) => (response: any) => {
traverse(response).forEach(function (responseItem: any) {
if (responseItem instanceof Object && responseItem.name) {
//Name already in hashed form
if (responseItem.name && responseItem.name.includes('[')) {
return
}

const hashedName = namehash(responseItem.name)
if (responseItem.id !== hashedName) {
this.update({ ...responseItem, name: hashedName, invalidName: true })
const hashedName = namehash(responseItem.name)
if (responseItem.id !== hashedName) {
this.update({ ...responseItem, name: hashedName, invalidName: true })
}
}
}
})
return response
}
})
return response
}

export default class GqlManager {
public gql: any = () => null
public client?: any | null = null

public setUrl = async (url: string | null) => {
if (url) {
const [imported, traverse, { visit, parse }] = await Promise.all([
const [imported, traverse, { visit, parse, print }] = await Promise.all([
import('graphql-request'),
import('traverse'),
import('graphql/language'),
])

this.client = new imported.GraphQLClient(url, {
requestMiddleware: requestMiddleware(visit, parse),
requestMiddleware: requestMiddleware(visit, parse, print),
responseMiddleware: responseMiddleware(traverse.default),
})
this.gql = imported.gql
Expand Down

0 comments on commit a233702

Please sign in to comment.