mirror of
https://github.com/wassname/talk.git
synced 2026-07-23 13:10:20 +08:00
[CORL-149] Persisted Queries (#2445)
* feat: enable persisted queries on the client * fix: use `id` inside websocket message * feat: initial server support for PQ * feat: deeper server support * feat: abstracted persisted query replacing logic
This commit is contained in:
@@ -4,13 +4,16 @@ import {
|
||||
Disposable,
|
||||
Variables,
|
||||
} from "react-relay-network-modern/es";
|
||||
import { SubscriptionClient } from "subscriptions-transport-ws";
|
||||
import {
|
||||
OperationOptions,
|
||||
SubscriptionClient,
|
||||
} from "subscriptions-transport-ws";
|
||||
|
||||
import { ACCESS_TOKEN_PARAM, CLIENT_ID_PARAM } from "coral-common/constants";
|
||||
import { ERROR_CODES } from "coral-common/errors";
|
||||
|
||||
/**
|
||||
* SubscriptionRequest containts the subscription
|
||||
* SubscriptionRequest contains the subscription
|
||||
* request data that comes from Relay.
|
||||
*/
|
||||
export interface SubscriptionRequest {
|
||||
@@ -109,17 +112,29 @@ export default function createManagedSubscriptionClient(
|
||||
},
|
||||
});
|
||||
}
|
||||
const subscription = subscriptionClient
|
||||
.request({
|
||||
operationName: operation.name,
|
||||
query: operation.text!,
|
||||
variables,
|
||||
})
|
||||
.subscribe({
|
||||
next({ data }) {
|
||||
observer.onNext({ data });
|
||||
},
|
||||
});
|
||||
if (!operation.text && !operation.id) {
|
||||
throw Error("Neither subscription query nor id was provided.");
|
||||
}
|
||||
|
||||
const opts: OperationOptions = {
|
||||
operationName: operation.name,
|
||||
// subscriptions-transport-ws requires `query` to be set to an non-empty string.
|
||||
// With persisted queries we only have the id, so set this to
|
||||
// "PERSISTED_QUERY" to get around validation.
|
||||
query: operation.text || "PERSISTED_QUERY",
|
||||
variables,
|
||||
};
|
||||
|
||||
// Query is not available which means we can use the id from persisted queries.
|
||||
if (!operation.text) {
|
||||
opts.id = operation.id;
|
||||
}
|
||||
|
||||
const subscription = subscriptionClient.request(opts).subscribe({
|
||||
next({ data }) {
|
||||
observer.onNext({ data });
|
||||
},
|
||||
});
|
||||
request.unsubscribe = () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
authMiddleware,
|
||||
batchMiddleware,
|
||||
cacheMiddleware,
|
||||
RelayNetworkLayer,
|
||||
retryMiddleware,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
import clientIDMiddleware from "./clientIDMiddleware";
|
||||
import { ManagedSubscriptionClient } from "./createManagedSubscriptionClient";
|
||||
import customErrorMiddleware from "./customErrorMiddleware";
|
||||
import persistedQueriesGetMethodMiddleware from "./persistedQueriesGetMethodMiddleware";
|
||||
|
||||
export type TokenGetter = () => string;
|
||||
|
||||
@@ -51,11 +51,6 @@ export default function createNetwork(
|
||||
urlMiddleware({
|
||||
url: () => Promise.resolve(graphqlURL),
|
||||
}),
|
||||
batchMiddleware({
|
||||
batchUrl: (requestMap: any) => Promise.resolve(graphqlURL),
|
||||
batchTimeout: 0,
|
||||
allowMutations: true,
|
||||
}),
|
||||
retryMiddleware({
|
||||
fetchTimeout: 15000,
|
||||
retryDelays: (attempt: number) => Math.pow(2, attempt + 4) * 100,
|
||||
@@ -66,6 +61,7 @@ export default function createNetwork(
|
||||
token: tokenGetter,
|
||||
}),
|
||||
clientIDMiddleware(clientID),
|
||||
persistedQueriesGetMethodMiddleware,
|
||||
],
|
||||
{ subscribeFn: createSubscriptionFunction(subscriptionClient) }
|
||||
);
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Middleware, RelayRequestAny } from "react-relay-network-modern/es";
|
||||
|
||||
import { modifyQuery } from "coral-framework/utils";
|
||||
|
||||
function hasMutations(req: RelayRequestAny): boolean {
|
||||
return req.isMutation();
|
||||
}
|
||||
|
||||
function queriesAreEmpty(req: RelayRequestAny): boolean {
|
||||
return req.getQueryString() === "";
|
||||
}
|
||||
|
||||
/**
|
||||
* persistedQueriesGetMethodMiddleware will use the GET method instead of POST for
|
||||
* all request excluding mutations when persisted queries are used.
|
||||
* The request data will be encoded in base64url and set in the GET query string under
|
||||
* the variable "d=".
|
||||
*/
|
||||
const persistedQueriesGetMethodMiddleware: Middleware = next => async req => {
|
||||
if (queriesAreEmpty(req) && !hasMutations(req)) {
|
||||
// Pull the body out (serializing it) and delete it off of the original
|
||||
// fetch options.
|
||||
const body: Record<string, any> = JSON.parse(req.fetchOpts.body as string);
|
||||
delete req.fetchOpts.body;
|
||||
|
||||
// Reconfigure the fetch for GET.
|
||||
req.fetchOpts.method = "GET";
|
||||
|
||||
// Rebuild the query parameters for GET.
|
||||
const params: Record<string, string> = { query: "" };
|
||||
for (const key in body) {
|
||||
if (!body.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = body[key];
|
||||
params[key] = typeof value === "string" ? value : JSON.stringify(value);
|
||||
}
|
||||
|
||||
// Combine the new parameters onto the URL.
|
||||
req.fetchOpts.url = modifyQuery(req.fetchOpts.url as string, params);
|
||||
}
|
||||
return next(req);
|
||||
};
|
||||
|
||||
export default persistedQueriesGetMethodMiddleware;
|
||||
Reference in New Issue
Block a user