Skip to content

Commit

Permalink
Merge pull request #216 from gadget-inc/url-param-exchange
Browse files Browse the repository at this point in the history
Add the graphql operation to the client URL
  • Loading branch information
airhorns committed Jun 26, 2023
2 parents 095e3da + 7b4c8c7 commit 661b1f9
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 17 deletions.
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) => {
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
);
};

0 comments on commit 661b1f9

Please sign in to comment.