• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kiva / ui / 13861583877

14 Mar 2025 05:01PM UTC coverage: 46.746% (-0.03%) from 46.777%
13861583877

push

github

emuvente
fix: only set user-agent header if configured

1174 of 2586 branches covered (45.4%)

Branch coverage included in aggregate %.

1663 of 3483 relevant lines covered (47.75%)

383.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/server-entry.js
1
/* eslint-disable vue/multi-word-component-names, no-throw-literal */
2
import { renderToString } from 'vue/server-renderer';
3
import serialize from 'serialize-javascript';
4
import { v4 as uuidv4 } from 'uuid';
5
import { renderSSRHead } from '@unhead/ssr';
6
import CookieStore from '#src/util/cookieStore';
7
import KvAuth0, { MockKvAuth0 } from '#src/util/KvAuth0';
8
import { preFetchAll } from '#src/util/apolloPreFetch';
9
import renderGlobals from '#src/util/renderGlobals';
10
import createApp from '#src/main';
11
import headScript from '#src/head/script';
12
import oneTrustEvent from '#src/head/oneTrustEvent';
13

14
// import noscriptTemplate from '#src/head/noscript.html';
15
import { authenticationGuard } from '#src/util/authenticationGuard';
16
import { contentfulPreviewCookie } from '#src/util/contentfulPreviewCookie';
17

18
import logFormatter from '#src/util/logFormatter';
19
import { buildUserDataGlobal } from '#src/util/optimizelyUserMetrics';
20

21
import fetch from 'make-fetch-happen';
22

23
const isDev = process.env.NODE_ENV !== 'production';
×
24

25
// custom fetch wrapper to log fetch requests
26
const customFetch = async (uri, options) => {
×
27
        const response = await fetch(uri, options);
×
28
        // Log the outgoing options
29
        logFormatter(`Fetch Options: ${uri}, Options: ${JSON.stringify(options)}`);
×
30

31
        // Log the full response
32
        // eslint-disable-next-line max-len
33
        logFormatter(`Server fetch: ${uri}, Status: ${response.status}, Headers: ${JSON.stringify(response.headers.raw())}`);
×
34

35
        return response;
×
36
};
37

38
function fillTemplate(template, data) {
39
        let html = template;
×
40
        Object.keys(data).forEach(key => {
×
41
                html = html.replace(`\${${key}}`, data[key]);
×
42
        });
43
        // TODO: minify html
44
        return html;
×
45
}
46

47
let renderedConfig = '';
×
48
let renderedExternals = '';
×
49
let renderedExternalsOptIn = '';
×
50

51
// This adds non-vue-rendered html strings to the request context.
52
// These strings are added to the final html response using server/index.template.html
53
function renderExtraHtml(config) {
54
        // render config if it hasn't been rendered yet
55
        if (!renderedConfig) {
×
56
                renderedConfig = renderGlobals({ __KV_CONFIG__: config });
×
57
        }
58
        // render externals if they haven't been rendered yet
59
        if (!renderedExternals) {
×
60
                // add OneTrust loader
61
                if (config.oneTrust && config.oneTrust.enable) {
×
62
                        const key = `${config.oneTrust.key}${config.oneTrust.domainSuffix}`;
×
63
                        const src = `https://cdn.cookielaw.org/consent/${key}/otSDKStub.js`;
×
64
                        renderedExternals += `<script type="text/javascript" data-domain-script="${key}" src="${src}"></script>`;
×
65
                }
66
                // add primary head script
67
                const renderedHeadScript = serialize(headScript);
×
68
                const renderedOneTrustEvent = serialize(oneTrustEvent);
×
69
                // eslint-disable-next-line max-len
70
                renderedExternals += `<script>(${renderedHeadScript})(window.__KV_CONFIG__, ${renderedOneTrustEvent});</script>`;
×
71
        }
72
        // render externals for users that are not opted out of 3rd party cookies
73
        if (!renderedExternalsOptIn) {
×
74
                // setup Optimizely loader
75
                if (config?.enableOptimizely && config?.optimizelyProjectId) {
×
76
                        // eslint-disable-next-line max-len
77
                        renderedExternalsOptIn += '<script type="text/javascript">window["optimizely"]=window["optimizely"]||[];window["optimizely"].push({"type":"holdEvents"});</script>';
×
78
                        const optimizelySrc = `https://cdn.optimizely.com/js/${config?.optimizelyProjectId}.js`;
×
79
                        renderedExternalsOptIn += `<script type="text/javascript" src="${optimizelySrc}"></script>`;
×
80
                }
81
                // append regular externals
82
                renderedExternalsOptIn += renderedExternals;
×
83
        }
84
}
85

86
// This function renders a <link> tag for a given file
87
function renderPreloadLink(file) {
88
        if (file.endsWith('.js')) {
×
89
                return `<link rel="modulepreload" crossorigin href="${file}">`;
×
90
        }
91
        if (file.endsWith('.css')) {
×
92
                return `<link rel="stylesheet" href="${file}">`;
×
93
        }
94
        // TODO: handle other file types if needed
95
        return '';
×
96
}
97

98
// This function renders <link> tags for all files in the manifest for the given modules
99
function renderPreloadLinks(modules, manifest = {}) {
×
100
        let links = '';
×
101
        const seen = new Set();
×
102
        modules.forEach(id => {
×
103
                const files = manifest[id];
×
104
                if (files) {
×
105
                        files.forEach(file => {
×
106
                                if (!seen.has(file)) {
×
107
                                        seen.add(file);
×
108
                                        links += renderPreloadLink(file);
×
109
                                }
110
                        });
111
                }
112
        });
113
        return links;
×
114
}
115

116
// This exported function will be called by `bundleRenderer`.
117
// This is where we perform data-prefetching to determine the
118
// state of our application before actually rendering it.
119
// Since data fetching is async, this function is expected to
120
// return a Promise that resolves to the app instance.
121
export default async context => {
122
        const s = isDev && Date.now();
×
123
        const {
124
                url,
125
                config,
126
                kivaUserAgent,
127
                cookies,
128
                user,
129
                locale,
130
                device,
131
                ssrManifest,
132
                template,
133
        } = context;
×
134
        const { accessToken, ...profile } = user;
×
135

136
        // Create cookie store with cookies passed from express middleware
137
        const cookieStore = new CookieStore(cookies);
×
138

139
        // Create random visitor id if none is set
140
        if (!cookieStore.get('uiv')) {
×
141
                // Set visitor id cookie expiration for 2 years from now
142
                const expires = new Date();
×
143
                expires.setFullYear(expires.getFullYear() + 2);
×
144
                // Store visitor id as 'uiv' cookie
145
                cookieStore.set('uiv', uuidv4(), {
×
146
                        expires,
147
                        sameSite: true,
148
                        secure: true,
149
                        path: '/',
150
                });
151
        }
152

153
        let kvAuth0;
154
        if (config.auth0.enable) {
×
155
                kvAuth0 = new KvAuth0({
×
156
                        accessToken,
157
                        checkFakeAuth: config.auth0.checkFakeAuth,
158
                        cookieStore,
159
                        domain: config.auth0.domain,
160
                        user: profile,
161
                });
162
        } else {
163
                kvAuth0 = MockKvAuth0;
×
164
        }
165

166
        // __webpack_public_path__ = config.publicPath || '/'; // eslint-disable-line
167

168
        const {
169
                app,
170
                head,
171
                router,
172
                apolloClient,
173
        } = createApp({
×
174
                name: '',
175
                appConfig: config,
176
                apollo: {
177
                        uri: config.graphqlUri,
178
                        types: config.graphqlPossibleTypes
179
                },
180
                cookieStore,
181
                device,
182
                kvAuth0,
183
                locale,
184
                fetch: config?.apolloQueryFetchLogging ? customFetch : fetch,
×
185
                kivaUserAgent,
186
                url,
187
                isServer: true,
188
        });
189

190
        // redirect to the resolved url if it does not match the requested url
191
        const { fullPath } = router.resolve(url);
×
192
        if (fullPath !== url) {
×
193
                // redirects defined in routes.js use a permanent (301) redirect
194
                throw { url: fullPath, code: 301 };
×
195
        }
196

197
        // render content for template
198
        renderExtraHtml(config);
×
199

200
        // set router's location, ignoring any errors about redirection
201
        router.push(url).catch(() => { });
×
202

203
        // wait until router has resolved possible async hooks
204
        await router.isReady();
×
205

206
        // get the components matched by the route
207
        const matchedComponents = router.currentRoute.value.matched;
×
208

209
        // no matched routes
210
        if (!matchedComponents.length) {
×
211
                // TODO: Check for + redirect to kiva php app external route
212
                throw { code: 404 };
×
213
        }
214
        contentfulPreviewCookie({ route: router.currentRoute, cookieStore });
×
215

216
        try {
×
217
                // Use route meta property to determine if route needs authentication
218
                // authenticationGuard will reject promise with a redirect to login if
219
                // required authentication query fails
220
                await authenticationGuard({ route: router.currentRoute, apolloClient, kvAuth0 });
×
221

222
                // Pre-fetch graphql queries from the components (and all of their child components)
223
                // matched by the route
224
                // preFetchAll dispatches the queries with Apollo and returns a Promise,
225
                // which is resolved when the action is complete and apollo cache has been updated.
226
                await preFetchAll(matchedComponents, apolloClient, {
×
227
                        cookieStore,
228
                        kvAuth0,
229
                        route: router.currentRoute,
230
                        device
231
                });
232

233
                let sp; // Vue serverPrefetch timing start
234
                if (isDev) {
×
235
                        logFormatter(`data pre-fetch: ${Date.now() - s}ms`);
×
236
                        sp = new Date();
×
237
                }
238

239
                // render the app
240
                const appHtml = await renderToString(app, context);
×
241

242
                if (isDev) logFormatter(`vue serverPrefetch: ${Date.now() - sp}ms`);
×
243

244
                // After all preFetch hooks are resolved, our store is now
245
                // filled with the state needed to render the app.
246
                // Expose the state on the render context, and let the request handler
247
                // inline the state in the HTML response. This allows the client-side
248
                // store to pick-up the server-side state without having to duplicate
249
                // the initial data fetching on the client.
250
                const appState = renderGlobals({
×
251
                        __APOLLO_STATE__: apolloClient.cache.extract(),
252
                        pageData: buildUserDataGlobal(router, cookieStore, apolloClient)
253
                });
254

255
                // render head tags
256
                const payload = await renderSSRHead(head);
×
257

258
                // render preload links
259
                const preloadLinks = renderPreloadLinks(context.modules, ssrManifest);
×
260

261
                // check for 3rd party script opt-out
262
                const hasOptOut = cookies?.kvgdpr?.indexOf('opted_out=true') > -1;
×
263

264
                const templateData = {
×
265
                        ...payload,
266
                        // Turn off SSR for local development to prevent component FOUC (Flash of Unstyled Content)
267
                        // https://github.com/vitejs/vite/issues/6887#issuecomment-1038664078
268
                        appHtml: isDev ? '' : appHtml,
×
269
                        appState,
270
                        appConfig: renderedConfig,
271
                        externals: hasOptOut ? renderedExternals : renderedExternalsOptIn,
×
272
                        googleTagmanagerId: config.googleTagmanagerId,
273
                        preloadLinks,
274
                };
275

276
                return {
×
277
                        html: fillTemplate(template, templateData),
278
                        setCookies: cookieStore.getSetCookies(),
279
                };
280
        } catch (error) {
281
                if (error instanceof Error) {
×
282
                        throw error;
×
283
                } else {
284
                        context.setCookies = cookieStore.getSetCookies();
×
285
                        throw {
×
286
                                url: router.resolve(error).href,
287
                        };
288
                }
289
        }
290
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc