Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the graphql operation to the client URL #216

Merged
merged 1 commit into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions packages/api-client-core/spec/GadgetConnection-suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {
describe("authorization", () => {
it("should allow connecting with anonymous authentication", async () => {
nock("https://someapp.gadget.app")
.post("/api/graphql", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.post("/api/graphql?operation=meta", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.reply(200, {
data: {
meta: {
Expand Down Expand Up @@ -71,7 +71,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {

it("should allow connecting with internal auth token authentication", async () => {
nock("https://someapp.gadget.app")
.post("/api/graphql", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.post("/api/graphql?operation=meta", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.reply(200, function () {
expect(this.req.headers["authorization"]).toEqual([`Basic ${base64("gadget-internal:opaque-token-thing")}`]);

Expand Down Expand Up @@ -108,7 +108,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {

it("should allow connecting with a gadget API Key", async () => {
nock("https://someapp.gadget.app")
.post("/api/graphql", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.post("/api/graphql?operation=meta", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.reply(200, function () {
expect(this.req.headers["authorization"]).toEqual([`Bearer gsk-abcde`]);

Expand Down Expand Up @@ -146,7 +146,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {
describe("session token storage", () => {
it("should allow connecting with no session in a session storage mode", async () => {
nock("https://someapp.gadget.app")
.post("/api/graphql", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.post("/api/graphql?operation=meta", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.reply(
200,
{
Expand Down Expand Up @@ -185,7 +185,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {

it("should allow connecting with an initial session token in session storage mode", async () => {
nock("https://someapp.gadget.app")
.post("/api/graphql", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.post("/api/graphql?operation=meta", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.reply(
200,
function () {
Expand Down Expand Up @@ -227,7 +227,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {

it("should store a x-set-authorization header and reuse it for subsequent requests", async () => {
nock("https://someapp.gadget.app")
.post("/api/graphql", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.post("/api/graphql?operation=meta", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.reply(
200,
function () {
Expand All @@ -244,7 +244,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {
"x-set-authorization": "Session token-123",
}
)
.post("/api/graphql", { query: `{\n currentSession {\n id\n${queryExtra} }\n}`, variables: {} })
.post("/api/graphql?operation=currentSession", { query: `{\n currentSession {\n id\n${queryExtra} }\n}`, variables: {} })
.reply(
200,
function () {
Expand Down Expand Up @@ -303,7 +303,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {

it("should not re-use session tokens across apps", async () => {
nock("https://someapp.gadget.app")
.post("/api/graphql")
.post("/api/graphql?operation=meta")
.reply(
200,
function () {
Expand All @@ -321,7 +321,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {
}
);
nock("https://anotherapp.gadget.app")
.post("/api/graphql")
.post("/api/graphql?operation=meta")
.reply(
200,
function () {
Expand Down Expand Up @@ -382,7 +382,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {

it("should support a custom auth mode that can set arbitrary fetch headers", async () => {
nock("https://someapp.gadget.app")
.post("/api/graphql", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.post("/api/graphql?operation=meta", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.reply(200, function () {
expect(this.req.headers["authorization"]).toEqual([`FancyMode whatever`]);

Expand Down Expand Up @@ -428,7 +428,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {

it("custom auth mode requests shouldn't send back x-set-authorization headers on subsequent requests", async () => {
nock("https://someapp.gadget.app")
.post("/api/graphql", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.post("/api/graphql?operation=meta", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.reply(
200,
function () {
Expand All @@ -446,7 +446,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {
"x-set-authorization": "Session token-123",
}
)
.post("/api/graphql", { query: `{\n currentSession {\n id\n${queryExtra} }\n}`, variables: {} })
.post("/api/graphql?operation=currentSession", { query: `{\n currentSession {\n id\n${queryExtra} }\n}`, variables: {} })
.reply(
200,
function () {
Expand Down Expand Up @@ -517,7 +517,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {
});

nock("https://someapp.gadget.app")
.post("/api/graphql", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.post("/api/graphql?operation=meta", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.reply(
200,
{
Expand Down Expand Up @@ -560,7 +560,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {
});

nock("https://someapp.gadget.app")
.post("/api/graphql", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.post("/api/graphql?operation=meta", { query: `{\n meta {\n appName\n${queryExtra} }\n}`, variables: {} })
.reply(200, function () {
expect(this.req.headers["authorization"]).toEqual([`FancyMode whatever`]);

Expand Down Expand Up @@ -631,7 +631,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {
test("fetch can pass relative string paths when used with a relative base endpoint", async () => {
const fetch = jest.fn().mockResolvedValue(new Response("hello")) as any;
const connection = new GadgetConnection({
endpoint: "/api/graphql",
endpoint: "/api/graphql?operation=meta",
authenticationMode: { apiKey: "gsk-abcde" },
fetchImplementation: fetch,
});
Expand Down Expand Up @@ -826,7 +826,7 @@ export const GadgetConnectionSharedSuite = (queryExtra = "") => {

test("fetch can fetch URLs other than the configured endpoint when the configured endpoint is relative", async () => {
const connection = new GadgetConnection({
endpoint: "/api/graphql",
endpoint: "/api/graphql?operation=meta",
authenticationMode: { apiKey: "gsk-abcde" },
});

Expand Down
3 changes: 2 additions & 1 deletion packages/api-client-core/src/GadgetConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
storageAvailable,
traceFunction,
} from "./support";
import { urlParamExchange } from "./urlParamExchange";

export type TransactionRun<T> = (transaction: GadgetTransaction) => Promise<T>;
export interface GadgetSubscriptionClientOptions extends Partial<SubscriptionClientOptions> {
Expand Down Expand Up @@ -328,7 +329,7 @@ export class GadgetConnection {
}

private newBaseClient() {
const exchanges = [dedupExchange];
const exchanges = [dedupExchange, urlParamExchange];

// apply urql's default caching behaviour when client side (but skip it server side)
if (typeof window != "undefined") {
Expand Down
34 changes: 34 additions & 0 deletions packages/api-client-core/src/urlParamExchange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Exchange } from "@urql/core";
import type { DocumentNode, OperationDefinitionNode } from "graphql";
import { onPush, pipe } from "wonka";

export const graphqlDocumentName = (doc: DocumentNode) => {
const lastDefinition: OperationDefinitionNode | undefined = [...doc.definitions]
.reverse()
.find((definition) => definition.kind == "OperationDefinition") as any;
if (lastDefinition) {
if (lastDefinition.name) {
return lastDefinition.name.value;
}
const firstSelection = lastDefinition.selectionSet.selections.find((node) => node.kind == "Field") as any;
return firstSelection.name.value;
}
};

export const urlParamExchange: Exchange = ({ forward }) => {
return (ops$) =>
pipe(
ops$,
onPush((op) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was wondering what the difference between tap and onPush was and turns out they're the same.

if (op.context.url && op.query && !op.context.url.includes("?")) {
const operation = graphqlDocumentName(op.query) || "unknown";

op.context = {
...op.context,
url: op.context.url + `?operation=${operation}`,
};
}
}),
forward
);
};