Skip to content
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
1 change: 1 addition & 0 deletions changes/6379.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement WebSocket connectionParams to HTTP headers conversion
49 changes: 45 additions & 4 deletions configs/graphql/gateway.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,54 @@
import { defineConfig } from '@graphql-hive/gateway';
import { defineConfig, WSTransportOptions, type GatewayPlugin } from '@graphql-hive/gateway';

// Custom plugin to convert WebSocket connectionParams to HTTP headers
// This allows authentication parameters sent via WebSocket connection_init
// to be forwarded as HTTP headers when the gateway makes federation requests to subgraphs
const useConnectionParamsToHeadersPlugin = (): GatewayPlugin => {
return {
onSubgraphExecute({ executionRequest, executor, setExecutor }) {
// Access WebSocket connectionParams from context
const connectionParams = executionRequest.context?.connectionParams;

if (connectionParams && typeof connectionParams === 'object') {
// Wrap executor to inject connectionParams as HTTP headers
const originalExecutor = executor;
const wrappedExecutor = async (execRequest: any) => {
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

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

The execRequest parameter uses any type, which bypasses TypeScript's type safety. Consider using a proper type from the gateway library or defining an appropriate interface for the execution request.

Suggested change
const wrappedExecutor = async (execRequest: any) => {
interface ExecutorRequest {
context: {
headers?: Record<string, string>;
[key: string]: any;
};
extensions?: Record<string, any>;
[key: string]: any;
}
const wrappedExecutor = async (execRequest: ExecutorRequest) => {

Copilot uses AI. Check for mistakes.
// Extract Authorization header from context if it exists
let reqHeaders = execRequest.context.headers || {};
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

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

Mutating the original headers object with delete could cause unintended side effects. Create a copy of the headers object before modification: const reqHeaders = { ...(execRequest.context.headers || {}) }; followed by delete reqHeaders['content-length'];

Suggested change
let reqHeaders = execRequest.context.headers || {};
const reqHeaders = { ...(execRequest.context.headers || {}) };

Copilot uses AI. Check for mistakes.
delete reqHeaders['content-length'];
// Merge connectionParams with Authorization (if present)
const mergedHeaders = {
...reqHeaders,
...connectionParams,
};
const modifiedRequest = {
...execRequest,
extensions: {
...execRequest.extensions,
headers: mergedHeaders,
},
};
return originalExecutor(modifiedRequest);
};

setExecutor(wrappedExecutor);
}
},
};
};

export const gatewayConfig = defineConfig({
plugins: (ctx) => [
useConnectionParamsToHeadersPlugin(),
],
propagateHeaders: {
fromClientToSubgraphs({ request }) {
let headers = Object.fromEntries(request.headers.entries());
fromClientToSubgraphs({ context }) {
let headers = context.headers || {};
delete headers['content-length'];
return headers;
}
},
},
logging: false,
supergraph: '/gateway/supergraph.graphql',
graphqlEndpoint: '/admin/gql',
skipValidation: true,
Expand Down
17 changes: 0 additions & 17 deletions configs/graphql/router.yaml

This file was deleted.

9 changes: 0 additions & 9 deletions configs/graphql/supergraph.yaml

This file was deleted.

Loading