diff --git a/README.md b/README.md index afebe30..013897e 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,8 @@ await subgraph.query<{ elements: Element[] }>(getElementsQuery(), { count: 5 }) It supports the following ENV variables: -- `SUBGRAPH_COMPONENT_RETRIES`: How many retries per subraph query. Defaults to `3`. +- `SUBGRAPH_COMPONENT_RETRIES`: How many retries per subgraph query. Defaults to `3`. - `SUBGRAPH_COMPONENT_QUERY_TIMEOUT`: How long to wait until a connection is timed-out. Defaults to `10000`ms or 10 seconds. - `SUBGRAPH_COMPONENT_TIMEOUT_INCREMENT`: How much time to add after each retry. The value will be multiplied for the attempt number. For example: if the increment is 10000, it'll wait 10s the first retry, 20s next, 30s, etc. Defaults to `10000`ms or 10 seconds. -- `SUBGRAPH_COMPONENT_BACKOFF`: How long to wait until a new query is tried after an unsuccessfull one (retrying). Defaults to `500`ms. +- `SUBGRAPH_COMPONENT_BACKOFF`: How long to wait until a new query is tried after an unsuccessful one (retrying). Defaults to `500`ms. +- `SUBGRAPH_COMPONENT_AGENT_NAME`: The name of the agent that will be performing the graph requests. This agent name will be used to identify the graph requests using the user agent header. The crafted header will be: `Subgraph component / Agent Name`. diff --git a/src/index.ts b/src/index.ts index 88506b7..1dd41ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,9 @@ export async function createSubgraphComponent( const TIMEOUT = (await config.getNumber("SUBGRAPH_COMPONENT_QUERY_TIMEOUT")) ?? 10000 const TIMEOUT_INCREMENT = (await config.getNumber("SUBGRAPH_COMPONENT_TIMEOUT_INCREMENT")) ?? 10000 const BACKOFF = (await config.getNumber("SUBGRAPH_COMPONENT_BACKOFF")) ?? 500 + const USER_AGENT = `Subgraph component / ${ + (await config.getString("SUBGRAPH_COMPONENT_AGENT_NAME")) ?? "Unknown sender" + }` async function executeQuery( query: string, @@ -84,7 +87,7 @@ export async function createSubgraphComponent( ): Promise> { const response = await fetch.fetch(url, { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { "Content-Type": "application/json", "User-agent": USER_AGENT }, body: JSON.stringify({ query, variables }), signal: abortController.signal, }) diff --git a/test/component.spec.ts b/test/component.spec.ts index 3131023..74fcd1b 100644 --- a/test/component.spec.ts +++ b/test/component.spec.ts @@ -2,7 +2,7 @@ import { ILoggerComponent } from "@well-known-components/interfaces" import { IFetchComponent } from "@well-known-components/http-server" import { randomUUID } from "crypto" import { setTimeout } from "timers/promises" -import { ISubgraphComponent, SubgraphResponse } from "../src/types" +import { ISubgraphComponent, SubgraphResponse, Variables } from "../src/types" import { createSubgraphComponent } from "../src" import { SUBGRAPH_URL, test } from "./components" @@ -29,6 +29,8 @@ test("subgraph component", function ({ components, stubComponents }) { describe("when querying a subgraph", () => { let fetchMock: jest.SpyInstance let response: Response + const query = "query ThisIsAQuery() {}" + let variables: Variables beforeEach(() => { const { metrics } = stubComponents @@ -52,6 +54,7 @@ test("subgraph component", function ({ components, stubComponents }) { status: 200, json: async () => okResponseData, } as Response + variables = { some: "very interesting", variables: ["we have", "here"] } fetchMock = jest.spyOn(fetch, "fetch").mockImplementationOnce(async () => response) }) @@ -70,17 +73,57 @@ test("subgraph component", function ({ components, stubComponents }) { it("should forward the variables and query to fetch the subgraph", async () => { const { subgraph } = components - const query = "query ThisIsAQuery() {}" - const variables = { some: "very interesting", variables: ["we have", "here"] } await subgraph.query(query, variables) expect(fetchMock).toHaveBeenCalledWith(SUBGRAPH_URL, { method: "POST", - headers: { "Content-Type": "application/json" }, + headers: { "Content-Type": "application/json", "User-agent": "Subgraph component / Unknown sender" }, body: JSON.stringify({ query, variables }), signal: expect.any(AbortSignal), }) }) + + describe("and the agent name is provided", () => { + let subgraph: ISubgraphComponent + + beforeEach(async () => { + const { config } = components + jest.spyOn(config, "getString").mockImplementation(async (name: string) => { + switch (name) { + case "SUBGRAPH_COMPONENT_AGENT_NAME": + return "An agent" + default: + return "" + } + }) + subgraph = await createSubgraphComponent(components, SUBGRAPH_URL) + }) + + it("should perform the fetch to the subgraph with the provided user agent", async () => { + await subgraph.query(query, variables) + + expect(fetchMock).toHaveBeenCalledWith(SUBGRAPH_URL, { + method: "POST", + headers: { "Content-Type": "application/json", "User-agent": "Subgraph component / An agent" }, + body: JSON.stringify({ query, variables }), + signal: expect.any(AbortSignal), + }) + }) + }) + + describe("and the agent name is not provided", () => { + it("should perform the fetch to the subgraph with the provided user agent", async () => { + const { subgraph } = components + await subgraph.query(query, variables) + + expect(fetchMock).toHaveBeenCalledWith(SUBGRAPH_URL, { + method: "POST", + headers: { "Content-Type": "application/json", "User-agent": "Subgraph component / Unknown sender" }, + body: JSON.stringify({ query, variables }), + signal: expect.any(AbortSignal), + }) + }) + }) }) describe("when the request errors out", () => {