Skip to content

Commit b73e76c

Browse files
committed
feat(api): use custom apollo plugin to render sandbox
1 parent 8ea16fd commit b73e76c

File tree

2 files changed

+84
-40
lines changed

2 files changed

+84
-40
lines changed

api/src/unraid-api/graph/graph.module.ts

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { ApolloDriver } from '@nestjs/apollo';
33
import { Module } from '@nestjs/common';
44
import { GraphQLModule } from '@nestjs/graphql';
55

6-
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default';
76
import { NoUnusedVariablesRule, print } from 'graphql';
87
import {
98
DateTimeResolver,
@@ -16,37 +15,16 @@ import {
1615
import { GRAPHQL_INTROSPECTION } from '@app/environment';
1716
import { GraphQLLong } from '@app/graphql/resolvers/graphql-type-long';
1817
import { typeDefs } from '@app/graphql/schema/index';
19-
import { getters } from '@app/store';
2018
import { idPrefixPlugin } from '@app/unraid-api/graph/id-prefix-plugin';
2119

2220
import { ConnectResolver } from './connect/connect.resolver';
2321
import { ConnectService } from './connect/connect.service';
2422
import { NetworkResolver } from './network/network.resolver';
2523
import { ResolversModule } from './resolvers/resolvers.module';
24+
import { sandboxPlugin } from './sandbox-plugin';
2625
import { ServicesResolver } from './services/services.resolver';
2726
import { SharesResolver } from './shares/shares.resolver';
2827

29-
/** The initial query displayed in the Apollo sandbox */
30-
const initialDocument = `query ExampleQuery {
31-
notifications {
32-
id
33-
overview {
34-
unread {
35-
info
36-
warning
37-
alert
38-
total
39-
}
40-
archive {
41-
info
42-
warning
43-
alert
44-
total
45-
}
46-
}
47-
}
48-
}`;
49-
5028
@Module({
5129
imports: [
5230
ResolversModule,
@@ -59,23 +37,7 @@ const initialDocument = `query ExampleQuery {
5937
extra,
6038
}),
6139
playground: false,
62-
plugins: GRAPHQL_INTROSPECTION
63-
? [
64-
ApolloServerPluginLandingPageLocalDefault({
65-
footer: false,
66-
includeCookies: true,
67-
document: initialDocument,
68-
embed: {
69-
initialState: {
70-
sharedHeaders: {
71-
'x-csrf-token': getters.emhttp().var.csrfToken ?? 'no csrf token',
72-
},
73-
},
74-
},
75-
}),
76-
idPrefixPlugin,
77-
]
78-
: [idPrefixPlugin],
40+
plugins: GRAPHQL_INTROSPECTION ? [sandboxPlugin, idPrefixPlugin] : [idPrefixPlugin],
7941
subscriptions: {
8042
'graphql-ws': {
8143
path: '/graphql',
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { HttpException, HttpStatus } from '@nestjs/common';
2+
3+
import type { ApolloServerPlugin, GraphQLServerContext, GraphQLServerListener } from '@apollo/server';
4+
5+
/** The initial query displayed in the Apollo sandbox */
6+
const initialDocument = `query ExampleQuery {
7+
notifications {
8+
id
9+
overview {
10+
unread {
11+
info
12+
warning
13+
alert
14+
total
15+
}
16+
archive {
17+
info
18+
warning
19+
alert
20+
total
21+
}
22+
}
23+
}
24+
}`;
25+
26+
/** helper for raising precondition failure errors during an http request. */
27+
const preconditionFailed = (preconditionName: string) => {
28+
throw new HttpException(`Precondition failed: ${preconditionName} `, HttpStatus.PRECONDITION_FAILED);
29+
};
30+
31+
/**
32+
* Renders the sandbox page for the GraphQL server with Apollo Server landing page configuration.
33+
*
34+
* @param service - The GraphQL server context object
35+
* @returns Promise that resolves to an Apollo `LandingPage`, or throws a precondition failed error
36+
* @throws {Error} When downstream plugin components from apollo are unavailable. This should never happen.
37+
*
38+
* @remarks
39+
* This function configures and renders the Apollo Server landing page with:
40+
* - Disabled footer
41+
* - Enabled cookies
42+
* - Initial document state
43+
* - Shared headers containing CSRF token
44+
*/
45+
async function renderSandboxPage(service: GraphQLServerContext) {
46+
const { getters } = await import('@app/store');
47+
const { ApolloServerPluginLandingPageLocalDefault } = await import(
48+
'@apollo/server/plugin/landingPage/default'
49+
);
50+
const plugin = ApolloServerPluginLandingPageLocalDefault({
51+
footer: false,
52+
includeCookies: true,
53+
document: initialDocument,
54+
embed: {
55+
initialState: {
56+
sharedHeaders: {
57+
'x-csrf-token': getters.emhttp().var.csrfToken,
58+
},
59+
},
60+
},
61+
});
62+
if (!plugin.serverWillStart) return preconditionFailed('serverWillStart');
63+
const serverListener = await plugin.serverWillStart(service);
64+
65+
if (!serverListener) return preconditionFailed('serverListener');
66+
if (!serverListener.renderLandingPage) return preconditionFailed('renderLandingPage');
67+
return serverListener.renderLandingPage();
68+
}
69+
70+
/**
71+
* Apollo plugin to render the GraphQL Sandbox page on-demand based on current server state.
72+
*
73+
* Usually, the `ApolloServerPluginLandingPageLocalDefault` plugin configures its
74+
* parameters once, during server startup. This plugin defers the configuration
75+
* and rendering to request-time instead of server startup.
76+
*/
77+
export const sandboxPlugin: ApolloServerPlugin = {
78+
serverWillStart: async (service) =>
79+
({
80+
renderLandingPage: () => renderSandboxPage(service),
81+
}) satisfies GraphQLServerListener,
82+
};

0 commit comments

Comments
 (0)