Skip to content

Commit 65a0b3e

Browse files
committed
fix(@angular/build): simplify SSL handling for with SSR
This commit simplifies the handling of self-signed SSL certificates for with SSR. Previously, tests and potentially users had to set or configure to bypass certificate validation issues with self-signed certificates. Closes #31710 (cherry picked from commit 1850f7b)
1 parent 0497b01 commit 65a0b3e

File tree

14 files changed

+237
-67
lines changed

14 files changed

+237
-67
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@
169169
}
170170
},
171171
"resolutions": {
172-
"typescript": "5.9.3"
172+
"typescript": "5.9.3",
173+
"undici-types": "^7.16.0"
173174
}
174175
}

packages/angular/build/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ ts_project(
105105
":node_modules/sass",
106106
":node_modules/source-map-support",
107107
":node_modules/tinyglobby",
108+
":node_modules/undici",
108109
":node_modules/vite",
109110
":node_modules/vitest",
110111
":node_modules/watchpack",

packages/angular/build/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@
4242
"semver": "7.7.3",
4343
"source-map-support": "0.5.21",
4444
"tinyglobby": "0.2.15",
45+
"undici": "7.16.0",
4546
"vite": "7.1.11",
4647
"watchpack": "2.4.4"
4748
},
4849
"optionalDependencies": {
4950
"lmdb": "3.4.3"
5051
},
5152
"devDependencies": {
52-
"@angular/ssr": "workspace:*",
5353
"@angular-devkit/core": "workspace:*",
5454
"jsdom": "27.0.1",
5555
"less": "4.4.2",
@@ -59,9 +59,9 @@
5959
"vitest": "4.0.6"
6060
},
6161
"peerDependencies": {
62-
"@angular/core": "0.0.0-ANGULAR-FW-PEER-DEP",
6362
"@angular/compiler": "0.0.0-ANGULAR-FW-PEER-DEP",
6463
"@angular/compiler-cli": "0.0.0-ANGULAR-FW-PEER-DEP",
64+
"@angular/core": "0.0.0-ANGULAR-FW-PEER-DEP",
6565
"@angular/localize": "0.0.0-ANGULAR-FW-PEER-DEP",
6666
"@angular/platform-browser": "0.0.0-ANGULAR-FW-PEER-DEP",
6767
"@angular/platform-server": "0.0.0-ANGULAR-FW-PEER-DEP",

packages/angular/build/src/builders/dev-server/vite/server.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { ComponentStyleRecord } from '../../../tools/vite/middlewares';
1313
import {
1414
ServerSsrMode,
1515
createAngularMemoryPlugin,
16+
createAngularServerSideSSLPlugin,
1617
createAngularSetupMiddlewaresPlugin,
1718
createAngularSsrTransformPlugin,
1819
createRemoveIdPrefixPlugin,
@@ -207,16 +208,19 @@ export async function setupServer(
207208
preTransformRequests,
208209
cacheDir,
209210
),
210-
ssr: createSsrConfig(
211-
externalMetadata,
212-
serverOptions,
213-
prebundleTransformer,
214-
zoneless,
215-
target,
216-
prebundleLoaderExtensions,
217-
thirdPartySourcemaps,
218-
define,
219-
),
211+
ssr:
212+
ssrMode === ServerSsrMode.NoSsr
213+
? undefined
214+
: createSsrConfig(
215+
externalMetadata,
216+
serverOptions,
217+
prebundleTransformer,
218+
zoneless,
219+
target,
220+
prebundleLoaderExtensions,
221+
thirdPartySourcemaps,
222+
define,
223+
),
220224
plugins: [
221225
createAngularSetupMiddlewaresPlugin({
222226
outputFiles,
@@ -258,11 +262,15 @@ export async function setupServer(
258262
};
259263

260264
if (serverOptions.ssl) {
265+
configuration.plugins ??= [];
261266
if (!serverOptions.sslCert || !serverOptions.sslKey) {
262267
const { default: basicSslPlugin } = await import('@vitejs/plugin-basic-ssl');
263-
configuration.plugins ??= [];
264268
configuration.plugins.push(basicSslPlugin());
265269
}
270+
271+
if (ssrMode !== ServerSsrMode.NoSsr) {
272+
configuration.plugins?.push(createAngularServerSideSSLPlugin());
273+
}
266274
}
267275

268276
return configuration;

packages/angular/build/src/tools/vite/plugins/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export { createAngularMemoryPlugin } from './angular-memory-plugin';
1010
export { createRemoveIdPrefixPlugin } from './id-prefix-plugin';
1111
export { createAngularSetupMiddlewaresPlugin, ServerSsrMode } from './setup-middlewares-plugin';
1212
export { createAngularSsrTransformPlugin } from './ssr-transform-plugin';
13+
export { createAngularServerSideSSLPlugin } from './ssr-ssl-plugin';
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import type { Plugin } from 'vite';
10+
11+
export function createAngularServerSideSSLPlugin(): Plugin {
12+
return {
13+
name: 'angular-ssr-ssl-plugin',
14+
apply: 'serve',
15+
async configureServer({ config, httpServer }) {
16+
console.log('config server');
17+
const {
18+
ssr,
19+
server: { https },
20+
} = config;
21+
22+
if (!ssr || !https) {
23+
return;
24+
}
25+
26+
const { getGlobalDispatcher, setGlobalDispatcher, Agent } = await import('undici');
27+
const originalDispatcher = getGlobalDispatcher();
28+
29+
setGlobalDispatcher(
30+
new Agent({
31+
connect: {
32+
ca: [...getCerts(https.key), ...getCerts(https.cert)].join('\n'),
33+
},
34+
}),
35+
);
36+
37+
httpServer?.on('close', () => {
38+
console.log('setting original');
39+
setGlobalDispatcher(originalDispatcher);
40+
});
41+
},
42+
};
43+
}
44+
45+
function getCerts(
46+
items: string | Buffer | (string | Buffer | { pem: string | Buffer })[] | undefined,
47+
): string[] {
48+
if (!items) {
49+
return [];
50+
}
51+
52+
const certs: string[] = [];
53+
if (Array.isArray(items)) {
54+
for (const item of items) {
55+
const value = typeof item === 'string' ? item : item.toString('utf-8');
56+
certs.push(value.trim());
57+
}
58+
} else {
59+
const value = typeof items === 'string' ? items : items.toString('utf-8');
60+
certs.push(value.trim());
61+
}
62+
63+
return certs;
64+
}

packages/angular_devkit/build_angular/src/builders/dev-server/specs/ssl_spec.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import { Architect, BuilderRun } from '@angular-devkit/architect';
1010
import { tags } from '@angular-devkit/core';
11-
import { Agent, getGlobalDispatcher, setGlobalDispatcher } from 'undici';
11+
import { Agent } from 'undici';
1212
import { createArchitect, host } from '../../../testing/test-utils';
1313
import { DevServerBuilderOutput } from '../index';
1414

@@ -35,20 +35,12 @@ describe('Dev Server Builder ssl', () => {
3535
expect(output.success).toBe(true);
3636
expect(output.baseUrl).toMatch(/^https:\/\/localhost:\d+\//);
3737

38-
// The self-signed certificate used by the dev server will cause fetch to fail
39-
// unless reject unauthorized is disabled.
40-
const originalDispatcher = getGlobalDispatcher();
41-
setGlobalDispatcher(
42-
new Agent({
38+
const response = await fetch(output.baseUrl, {
39+
dispatcher: new Agent({
4340
connect: { rejectUnauthorized: false },
4441
}),
45-
);
46-
try {
47-
const response = await fetch(output.baseUrl);
48-
expect(await response.text()).toContain('<title>HelloWorldApp</title>');
49-
} finally {
50-
setGlobalDispatcher(originalDispatcher);
51-
}
42+
});
43+
expect(await response.text()).toContain('<title>HelloWorldApp</title>');
5244
});
5345

5446
it('supports key and cert', async () => {

packages/angular_devkit/build_angular/src/builders/ssr-dev-server/specs/ssl_spec.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { Architect } from '@angular-devkit/architect';
1010
// eslint-disable-next-line import/no-extraneous-dependencies
1111
import * as browserSync from 'browser-sync';
12-
import { Agent, getGlobalDispatcher, setGlobalDispatcher } from 'undici';
12+
import { Agent } from 'undici';
1313
import { createArchitect, host } from '../../../testing/test-utils';
1414
import { SSRDevServerBuilderOutput } from '../index';
1515

@@ -85,20 +85,13 @@ describe('Serve SSR Builder', () => {
8585
expect(output.success).toBe(true);
8686
expect(output.baseUrl).toBe(`https://localhost:${output.port}`);
8787

88-
// The self-signed certificate used by the dev server will cause fetch to fail
89-
// unless reject unauthorized is disabled.
90-
const originalDispatcher = getGlobalDispatcher();
91-
setGlobalDispatcher(
92-
new Agent({
88+
const response = await fetch(`https://localhost:${output.port}/index.html`, {
89+
dispatcher: new Agent({
9390
connect: { rejectUnauthorized: false },
9491
}),
95-
);
96-
try {
97-
const response = await fetch(`https://localhost:${output.port}/index.html`);
98-
expect(await response.text()).toContain('<title>HelloWorldApp</title>');
99-
} finally {
100-
setGlobalDispatcher(originalDispatcher);
101-
}
92+
});
93+
94+
expect(await response.text()).toContain('<title>HelloWorldApp</title>');
10295

10396
await run.stop();
10497
});

pnpm-lock.yaml

Lines changed: 5 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/legacy-cli/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ e2e_suites(
6868
# Extra runtime deps due to bundling issues.
6969
# TODO: Clean this up.
7070
"//:node_modules/express",
71+
"//:node_modules/undici",
7172
],
7273
runner = ":runner_entrypoint",
7374
)

0 commit comments

Comments
 (0)