@angular/ssr 18.2.0-rc.0 → 19.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,4 +5,5 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
- export { CommonEngine, } from './src/common-engine';
8
+ export { CommonEngine, } from './src/common-engine/common-engine';
9
+ export { getRoutesFromAngularRouterConfig as ɵgetRoutesFromAngularRouterConfig } from './src/routes/ng-routes';
@@ -0,0 +1,106 @@
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
+ import { Hooks } from './hooks';
9
+ import { getPotentialLocaleIdFromUrl } from './i18n';
10
+ import { getAngularAppEngineManifest } from './manifest';
11
+ /**
12
+ * Angular server application engine.
13
+ * Manages Angular server applications (including localized ones), handles rendering requests,
14
+ * and optionally transforms index HTML before rendering.
15
+ */
16
+ export class AngularAppEngine {
17
+ /**
18
+ * Hooks for extending or modifying the behavior of the server application.
19
+ * @internal This property is accessed by the Angular CLI when running the dev-server.
20
+ */
21
+ static hooks = new Hooks();
22
+ /**
23
+ * Hooks for extending or modifying the behavior of the server application.
24
+ * This instance can be used to attach custom functionality to various events in the server application lifecycle.
25
+ * @internal
26
+ */
27
+ get hooks() {
28
+ return AngularAppEngine.hooks;
29
+ }
30
+ /**
31
+ * Specifies if the application is operating in development mode.
32
+ * This property controls the activation of features intended for production, such as caching mechanisms.
33
+ * @internal
34
+ */
35
+ static isDevMode = false;
36
+ /**
37
+ * The manifest for the server application.
38
+ */
39
+ manifest = getAngularAppEngineManifest();
40
+ /**
41
+ * Map of locale strings to corresponding `AngularServerApp` instances.
42
+ * Each instance represents an Angular server application.
43
+ */
44
+ appsCache = new Map();
45
+ /**
46
+ * Renders an HTTP request using the appropriate Angular server application and returns a response.
47
+ *
48
+ * This method determines the entry point for the Angular server application based on the request URL,
49
+ * and caches the server application instances for reuse. If the application is in development mode,
50
+ * the cache is bypassed and a new instance is created for each request.
51
+ *
52
+ * If the request URL appears to be for a file (excluding `/index.html`), the method returns `null`.
53
+ * A request to `https://www.example.com/page/index.html` will render the Angular route
54
+ * corresponding to `https://www.example.com/page`.
55
+ *
56
+ * @param request - The incoming HTTP request object to be rendered.
57
+ * @param requestContext - Optional additional context for the request, such as metadata.
58
+ * @returns A promise that resolves to a Response object, or `null` if the request URL represents a file (e.g., `./logo.png`)
59
+ * rather than an application route.
60
+ */
61
+ async render(request, requestContext) {
62
+ // Skip if the request looks like a file but not `/index.html`.
63
+ const url = new URL(request.url);
64
+ const entryPoint = this.getEntryPointFromUrl(url);
65
+ if (!entryPoint) {
66
+ return null;
67
+ }
68
+ const [locale, loadModule] = entryPoint;
69
+ let serverApp = this.appsCache.get(locale);
70
+ if (!serverApp) {
71
+ const { AngularServerApp } = await loadModule();
72
+ serverApp = new AngularServerApp({
73
+ isDevMode: AngularAppEngine.isDevMode,
74
+ hooks: this.hooks,
75
+ });
76
+ if (!AngularAppEngine.isDevMode) {
77
+ this.appsCache.set(locale, serverApp);
78
+ }
79
+ }
80
+ return serverApp.render(request, requestContext);
81
+ }
82
+ /**
83
+ * Retrieves the entry point path and locale for the Angular server application based on the provided URL.
84
+ *
85
+ * This method determines the appropriate entry point and locale for rendering the application by examining the URL.
86
+ * If there is only one entry point available, it is returned regardless of the URL.
87
+ * Otherwise, the method extracts a potential locale identifier from the URL and looks up the corresponding entry point.
88
+ *
89
+ * @param url - The URL used to derive the locale and determine the entry point.
90
+ * @returns An array containing:
91
+ * - The first element is the locale extracted from the URL.
92
+ * - The second element is a function that returns a promise resolving to an object with the `AngularServerApp` type.
93
+ *
94
+ * Returns `null` if no matching entry point is found for the extracted locale.
95
+ */
96
+ getEntryPointFromUrl(url) {
97
+ // Find bundle for locale
98
+ const { entryPoints, basePath } = this.manifest;
99
+ if (entryPoints.size === 1) {
100
+ return entryPoints.entries().next().value;
101
+ }
102
+ const potentialLocale = getPotentialLocaleIdFromUrl(url, basePath);
103
+ const entryPoint = entryPoints.get(potentialLocale);
104
+ return entryPoint ? [potentialLocale, entryPoint] : null;
105
+ }
106
+ }
@@ -0,0 +1,85 @@
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
+ import { ServerAssets } from './assets';
9
+ import { Hooks } from './hooks';
10
+ import { getAngularAppManifest } from './manifest';
11
+ import { ServerRenderContext, render } from './render';
12
+ import { ServerRouter } from './routes/router';
13
+ /**
14
+ * Represents a locale-specific Angular server application managed by the server application engine.
15
+ *
16
+ * The `AngularServerApp` class handles server-side rendering and asset management for a specific locale.
17
+ */
18
+ export class AngularServerApp {
19
+ options;
20
+ /**
21
+ * The manifest associated with this server application.
22
+ * @internal
23
+ */
24
+ manifest = getAngularAppManifest();
25
+ /**
26
+ * Hooks for extending or modifying the behavior of the server application.
27
+ * This instance can be used to attach custom functionality to various events in the server application lifecycle.
28
+ * @internal
29
+ */
30
+ hooks;
31
+ /**
32
+ * Specifies if the server application is operating in development mode.
33
+ * This property controls the activation of features intended for production, such as caching mechanisms.
34
+ * @internal
35
+ */
36
+ isDevMode;
37
+ /**
38
+ * An instance of ServerAsset that handles server-side asset.
39
+ * @internal
40
+ */
41
+ assets = new ServerAssets(this.manifest);
42
+ /**
43
+ * The router instance used for route matching and handling.
44
+ */
45
+ router;
46
+ /**
47
+ * Creates a new `AngularServerApp` instance with the provided configuration options.
48
+ *
49
+ * @param options - The configuration options for the server application.
50
+ * - `isDevMode`: Flag indicating if the application is in development mode.
51
+ * - `hooks`: Optional hooks for customizing application behavior.
52
+ */
53
+ constructor(options) {
54
+ this.options = options;
55
+ this.isDevMode = options.isDevMode ?? false;
56
+ this.hooks = options.hooks ?? new Hooks();
57
+ }
58
+ /**
59
+ * Renders a response for the given HTTP request using the server application.
60
+ *
61
+ * This method processes the request and returns a response based on the specified rendering context.
62
+ *
63
+ * @param request - The incoming HTTP request to be rendered.
64
+ * @param requestContext - Optional additional context for rendering, such as request metadata.
65
+ * @param serverContext - The rendering context.
66
+ *
67
+ * @returns A promise that resolves to the HTTP response object resulting from the rendering, or null if no match is found.
68
+ */
69
+ async render(request, requestContext, serverContext = ServerRenderContext.SSR) {
70
+ const url = new URL(request.url);
71
+ this.router ??= await ServerRouter.from(this.manifest, url);
72
+ const matchedRoute = this.router.match(url);
73
+ if (!matchedRoute) {
74
+ // Not a known Angular route.
75
+ return null;
76
+ }
77
+ const { redirectTo } = matchedRoute;
78
+ if (redirectTo !== undefined) {
79
+ // 302 Found is used by default for redirections
80
+ // See: https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static#status
81
+ return Response.redirect(new URL(redirectTo, url), 302);
82
+ }
83
+ return render(this, request, serverContext, requestContext);
84
+ }
85
+ }
@@ -0,0 +1,44 @@
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
+ * Manages server-side assets.
10
+ */
11
+ export class ServerAssets {
12
+ manifest;
13
+ /**
14
+ * Creates an instance of ServerAsset.
15
+ *
16
+ * @param manifest - The manifest containing the server assets.
17
+ */
18
+ constructor(manifest) {
19
+ this.manifest = manifest;
20
+ }
21
+ /**
22
+ * Retrieves the content of a server-side asset using its path.
23
+ *
24
+ * @param path - The path to the server asset.
25
+ * @returns A promise that resolves to the asset content as a string.
26
+ * @throws Error If the asset path is not found in the manifest, an error is thrown.
27
+ */
28
+ async getServerAsset(path) {
29
+ const asset = this.manifest.assets[path];
30
+ if (!asset) {
31
+ throw new Error(`Server asset '${path}' does not exist.`);
32
+ }
33
+ return asset();
34
+ }
35
+ /**
36
+ * Retrieves and caches the content of 'index.server.html'.
37
+ *
38
+ * @returns A promise that resolves to the content of 'index.server.html'.
39
+ * @throws Error If there is an issue retrieving the asset.
40
+ */
41
+ getIndexServerHtml() {
42
+ return this.getServerAsset('index.server.html');
43
+ }
44
+ }
@@ -0,0 +1,34 @@
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
+ import { ɵConsole } from '@angular/core';
9
+ /**
10
+ * Custom implementation of the Angular Console service that filters out specific log messages.
11
+ *
12
+ * This class extends the internal Angular `ɵConsole` class to provide customized logging behavior.
13
+ * It overrides the `log` method to suppress logs that match certain predefined messages.
14
+ */
15
+ export class Console extends ɵConsole {
16
+ /**
17
+ * A set of log messages that should be ignored and not printed to the console.
18
+ */
19
+ ignoredLogs = new Set(['Angular is running in development mode.']);
20
+ /**
21
+ * Logs a message to the console if it is not in the set of ignored messages.
22
+ *
23
+ * @param message - The message to log to the console.
24
+ *
25
+ * This method overrides the `log` method of the `ɵConsole` class. It checks if the
26
+ * message is in the `ignoredLogs` set. If it is not, it delegates the logging to
27
+ * the parent class's `log` method. Otherwise, the message is suppressed.
28
+ */
29
+ log(message) {
30
+ if (!this.ignoredLogs.has(message)) {
31
+ super.log(message);
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,94 @@
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
+ * Manages a collection of hooks and provides methods to register and execute them.
10
+ * Hooks are functions that can be invoked with specific arguments to allow modifications or enhancements.
11
+ */
12
+ export class Hooks {
13
+ /**
14
+ * A map of hook names to arrays of hook functions.
15
+ * Each hook name can have multiple associated functions, which are executed in sequence.
16
+ */
17
+ store = new Map();
18
+ /**
19
+ * Executes all hooks associated with the specified name, passing the given argument to each hook function.
20
+ * The hooks are invoked sequentially, and the argument may be modified by each hook.
21
+ *
22
+ * @template Hook - The type of the hook name. It should be one of the keys of `HooksMapping`.
23
+ * @param name - The name of the hook whose functions will be executed.
24
+ * @param context - The input value to be passed to each hook function. The value is mutated by each hook function.
25
+ * @returns A promise that resolves once all hook functions have been executed.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const hooks = new Hooks();
30
+ * hooks.on('html:transform:pre', async (ctx) => {
31
+ * ctx.html = ctx.html.replace(/foo/g, 'bar');
32
+ * return ctx.html;
33
+ * });
34
+ * const result = await hooks.run('html:transform:pre', { html: '<div>foo</div>' });
35
+ * console.log(result); // '<div>bar</div>'
36
+ * ```
37
+ * @internal
38
+ */
39
+ async run(name, context) {
40
+ const hooks = this.store.get(name);
41
+ switch (name) {
42
+ case 'html:transform:pre': {
43
+ if (!hooks) {
44
+ return context.html;
45
+ }
46
+ const ctx = { ...context };
47
+ for (const hook of hooks) {
48
+ ctx.html = await hook(ctx);
49
+ }
50
+ return ctx.html;
51
+ }
52
+ default:
53
+ throw new Error(`Running hook "${name}" is not supported.`);
54
+ }
55
+ }
56
+ /**
57
+ * Registers a new hook function under the specified hook name.
58
+ * This function should be a function that takes an argument of type `T` and returns a `string` or `Promise<string>`.
59
+ *
60
+ * @template Hook - The type of the hook name. It should be one of the keys of `HooksMapping`.
61
+ * @param name - The name of the hook under which the function will be registered.
62
+ * @param handler - A function to be executed when the hook is triggered. The handler will be called with an argument
63
+ * that may be modified by the hook functions.
64
+ *
65
+ * @remarks
66
+ * - If there are existing handlers registered under the given hook name, the new handler will be added to the list.
67
+ * - If no handlers are registered under the given hook name, a new list will be created with the handler as its first element.
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * hooks.on('html:transform:pre', async (ctx) => {
72
+ * return ctx.html.replace(/foo/g, 'bar');
73
+ * });
74
+ * ```
75
+ */
76
+ on(name, handler) {
77
+ const hooks = this.store.get(name);
78
+ if (hooks) {
79
+ hooks.push(handler);
80
+ }
81
+ else {
82
+ this.store.set(name, [handler]);
83
+ }
84
+ }
85
+ /**
86
+ * Checks if there are any hooks registered under the specified name.
87
+ *
88
+ * @param name - The name of the hook to check.
89
+ * @returns `true` if there are hooks registered under the specified name, otherwise `false`.
90
+ */
91
+ has(name) {
92
+ return !!this.store.get(name)?.length;
93
+ }
94
+ }
@@ -0,0 +1,41 @@
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
+ * Extracts a potential locale ID from a given URL based on the specified base path.
10
+ *
11
+ * This function parses the URL to locate a potential locale identifier that immediately
12
+ * follows the base path segment in the URL's pathname. If the URL does not contain a valid
13
+ * locale ID, an empty string is returned.
14
+ *
15
+ * @param url - The full URL from which to extract the locale ID.
16
+ * @param basePath - The base path used as the reference point for extracting the locale ID.
17
+ * @returns The extracted locale ID if present, or an empty string if no valid locale ID is found.
18
+ *
19
+ * @example
20
+ * ```js
21
+ * const url = new URL('https://example.com/base/en/page');
22
+ * const basePath = '/base';
23
+ * const localeId = getPotentialLocaleIdFromUrl(url, basePath);
24
+ * console.log(localeId); // Output: 'en'
25
+ * ```
26
+ */
27
+ export function getPotentialLocaleIdFromUrl(url, basePath) {
28
+ const { pathname } = url;
29
+ // Move forward of the base path section.
30
+ let start = basePath.length;
31
+ if (pathname[start] === '/') {
32
+ start++;
33
+ }
34
+ // Find the next forward slash.
35
+ let end = pathname.indexOf('/', start);
36
+ if (end === -1) {
37
+ end = pathname.length;
38
+ }
39
+ // Extract the potential locale id.
40
+ return pathname.slice(start, end);
41
+ }
@@ -0,0 +1,59 @@
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
+ * The Angular app manifest object.
10
+ * This is used internally to store the current Angular app manifest.
11
+ */
12
+ let angularAppManifest;
13
+ /**
14
+ * Sets the Angular app manifest.
15
+ *
16
+ * @param manifest - The manifest object to set for the Angular application.
17
+ */
18
+ export function setAngularAppManifest(manifest) {
19
+ angularAppManifest = manifest;
20
+ }
21
+ /**
22
+ * Gets the Angular app manifest.
23
+ *
24
+ * @returns The Angular app manifest.
25
+ * @throws Will throw an error if the Angular app manifest is not set.
26
+ */
27
+ export function getAngularAppManifest() {
28
+ if (!angularAppManifest) {
29
+ throw new Error('Angular app manifest is not set. ' +
30
+ `Please ensure you are using the '@angular/build:application' builder to build your server application.`);
31
+ }
32
+ return angularAppManifest;
33
+ }
34
+ /**
35
+ * The Angular app engine manifest object.
36
+ * This is used internally to store the current Angular app engine manifest.
37
+ */
38
+ let angularAppEngineManifest;
39
+ /**
40
+ * Sets the Angular app engine manifest.
41
+ *
42
+ * @param manifest - The engine manifest object to set.
43
+ */
44
+ export function setAngularAppEngineManifest(manifest) {
45
+ angularAppEngineManifest = manifest;
46
+ }
47
+ /**
48
+ * Gets the Angular app engine manifest.
49
+ *
50
+ * @returns The Angular app engine manifest.
51
+ * @throws Will throw an error if the Angular app engine manifest is not set.
52
+ */
53
+ export function getAngularAppEngineManifest() {
54
+ if (!angularAppEngineManifest) {
55
+ throw new Error('Angular app engine manifest is not set. ' +
56
+ `Please ensure you are using the '@angular/build:application' builder to build your server application.`);
57
+ }
58
+ return angularAppEngineManifest;
59
+ }
@@ -0,0 +1,74 @@
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
+ import { ɵConsole, ɵresetCompiledComponents } from '@angular/core';
9
+ import { ɵSERVER_CONTEXT as SERVER_CONTEXT } from '@angular/platform-server';
10
+ import { Console } from './console';
11
+ import { REQUEST, REQUEST_CONTEXT, RESPONSE_INIT } from './tokens';
12
+ import { renderAngular } from './utils/ng';
13
+ /**
14
+ * Enum representing the different contexts in which server rendering can occur.
15
+ */
16
+ export var ServerRenderContext;
17
+ (function (ServerRenderContext) {
18
+ ServerRenderContext["SSR"] = "ssr";
19
+ ServerRenderContext["SSG"] = "ssg";
20
+ ServerRenderContext["AppShell"] = "app-shell";
21
+ })(ServerRenderContext || (ServerRenderContext = {}));
22
+ /**
23
+ * Renders an Angular server application to produce a response for the given HTTP request.
24
+ * Supports server-side rendering (SSR), static site generation (SSG), or app shell rendering.
25
+ *
26
+ * @param app - The server application instance to render.
27
+ * @param request - The incoming HTTP request object.
28
+ * @param serverContext - Context specifying the rendering mode.
29
+ * @param requestContext - Optional additional context for the request, such as metadata.
30
+ * @returns A promise that resolves to a response object representing the rendered content.
31
+ */
32
+ export async function render(app, request, serverContext, requestContext) {
33
+ const isSsrMode = serverContext === ServerRenderContext.SSR;
34
+ const responseInit = {};
35
+ const platformProviders = [
36
+ {
37
+ provide: SERVER_CONTEXT,
38
+ useValue: serverContext,
39
+ },
40
+ ];
41
+ if (isSsrMode) {
42
+ platformProviders.push({
43
+ provide: REQUEST,
44
+ useValue: request,
45
+ }, {
46
+ provide: REQUEST_CONTEXT,
47
+ useValue: requestContext,
48
+ }, {
49
+ provide: RESPONSE_INIT,
50
+ useValue: responseInit,
51
+ });
52
+ }
53
+ const { manifest, hooks, isDevMode } = app;
54
+ if (isDevMode) {
55
+ // Need to clean up GENERATED_COMP_IDS map in `@angular/core`.
56
+ // Otherwise an incorrect component ID generation collision detected warning will be displayed in development.
57
+ // See: https://github.com/angular/angular-cli/issues/25924
58
+ ɵresetCompiledComponents();
59
+ // An Angular Console Provider that does not print a set of predefined logs.
60
+ platformProviders.push({
61
+ provide: ɵConsole,
62
+ // Using `useClass` would necessitate decorating `Console` with `@Injectable`,
63
+ // which would require switching from `ts_library` to `ng_module`. This change
64
+ // would also necessitate various patches of `@angular/bazel` to support ESM.
65
+ useFactory: () => new Console(),
66
+ });
67
+ }
68
+ let html = await app.assets.getIndexServerHtml();
69
+ // Skip extra microtask if there are no pre hooks.
70
+ if (hooks.has('html:transform:pre')) {
71
+ html = await hooks.run('html:transform:pre', { html });
72
+ }
73
+ return new Response(await renderAngular(html, manifest.bootstrap(), new URL(request.url), platformProviders), responseInit);
74
+ }
@@ -0,0 +1,63 @@
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
+ * Converts a Node.js `IncomingMessage` into a Web Standard `Request`.
10
+ *
11
+ * @param nodeRequest - The Node.js `IncomingMessage` object to convert.
12
+ * @returns A Web Standard `Request` object.
13
+ */
14
+ export function createWebRequestFromNodeRequest(nodeRequest) {
15
+ const { headers, method = 'GET' } = nodeRequest;
16
+ const withBody = method !== 'GET' && method !== 'HEAD';
17
+ return new Request(createRequestUrl(nodeRequest), {
18
+ method,
19
+ headers: createRequestHeaders(headers),
20
+ body: withBody ? nodeRequest : undefined,
21
+ duplex: withBody ? 'half' : undefined,
22
+ });
23
+ }
24
+ /**
25
+ * Creates a `Headers` object from Node.js `IncomingHttpHeaders`.
26
+ *
27
+ * @param nodeHeaders - The Node.js `IncomingHttpHeaders` object to convert.
28
+ * @returns A `Headers` object containing the converted headers.
29
+ */
30
+ function createRequestHeaders(nodeHeaders) {
31
+ const headers = new Headers();
32
+ for (const [name, value] of Object.entries(nodeHeaders)) {
33
+ if (typeof value === 'string') {
34
+ headers.append(name, value);
35
+ }
36
+ else if (Array.isArray(value)) {
37
+ for (const item of value) {
38
+ headers.append(name, item);
39
+ }
40
+ }
41
+ }
42
+ return headers;
43
+ }
44
+ /**
45
+ * Creates a `URL` object from a Node.js `IncomingMessage`, taking into account the protocol, host, and port.
46
+ *
47
+ * @param nodeRequest - The Node.js `IncomingMessage` object to extract URL information from.
48
+ * @returns A `URL` object representing the request URL.
49
+ */
50
+ function createRequestUrl(nodeRequest) {
51
+ const { headers, socket, url = '' } = nodeRequest;
52
+ const protocol = headers['x-forwarded-proto'] ?? ('encrypted' in socket && socket.encrypted ? 'https' : 'http');
53
+ const hostname = headers['x-forwarded-host'] ?? headers.host ?? headers[':authority'];
54
+ const port = headers['x-forwarded-port'] ?? socket.localPort;
55
+ if (Array.isArray(hostname)) {
56
+ throw new Error('host value cannot be an array.');
57
+ }
58
+ let hostnameWithPort = hostname;
59
+ if (port && !hostname?.includes(':')) {
60
+ hostnameWithPort += `:${port}`;
61
+ }
62
+ return new URL(url, `${protocol}://${hostnameWithPort}`);
63
+ }
@@ -0,0 +1,58 @@
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
+ * Streams a web-standard `Response` into a Node.js `ServerResponse`.
10
+ *
11
+ * @param source - The web-standard `Response` object to stream from.
12
+ * @param destination - The Node.js `ServerResponse` object to stream into.
13
+ * @returns A promise that resolves once the streaming operation is complete.
14
+ */
15
+ export async function writeResponseToNodeResponse(source, destination) {
16
+ const { status, headers, body } = source;
17
+ destination.statusCode = status;
18
+ let cookieHeaderSet = false;
19
+ for (const [name, value] of headers.entries()) {
20
+ if (name === 'set-cookie') {
21
+ if (cookieHeaderSet) {
22
+ continue;
23
+ }
24
+ // Sets the 'set-cookie' header only once to ensure it is correctly applied.
25
+ // Concatenating 'set-cookie' values can lead to incorrect behavior, so we use a single value from `headers.getSetCookie()`.
26
+ destination.setHeader(name, headers.getSetCookie());
27
+ cookieHeaderSet = true;
28
+ }
29
+ else {
30
+ destination.setHeader(name, value);
31
+ }
32
+ }
33
+ if (!body) {
34
+ destination.end();
35
+ return;
36
+ }
37
+ try {
38
+ const reader = body.getReader();
39
+ destination.on('close', () => {
40
+ reader.cancel().catch((error) => {
41
+ // eslint-disable-next-line no-console
42
+ console.error(`An error occurred while writing the response body for: ${destination.req.url}.`, error);
43
+ });
44
+ });
45
+ // eslint-disable-next-line no-constant-condition
46
+ while (true) {
47
+ const { done, value } = await reader.read();
48
+ if (done) {
49
+ destination.end();
50
+ break;
51
+ }
52
+ destination.write(value);
53
+ }
54
+ }
55
+ catch {
56
+ destination.end('Internal server error.');
57
+ }
58
+ }