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
10 changes: 9 additions & 1 deletion src/registry/routes/component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Readable } from 'node:stream';
import { encode } from '@rdevis/turbo-stream';
import type { Request, RequestHandler, Response } from 'express';
import type { CookieOptions, Request, RequestHandler, Response } from 'express';
import { serializeError } from 'serialize-error';
import strings from '../../resources';
import type { Config } from '../../types';
Expand Down Expand Up @@ -49,6 +49,14 @@ export default function component(
res.set(result.headers);
}

if (Array.isArray(result.cookies) && result.cookies.length > 0) {
for (const cookie of result.cookies) {
const opts: CookieOptions = (cookie.options ??
{}) as CookieOptions;
res.cookie(cookie.name, cookie.value as any, opts);
}
}

const streamEnabled =
!!result.response.data?.component?.props?.[stream];
if (streamEnabled) {
Expand Down
20 changes: 19 additions & 1 deletion src/registry/routes/components.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import async from 'async';

import type { Request, RequestHandler, Response } from 'express';
import type { CookieOptions, Request, RequestHandler, Response } from 'express';
import strings from '../../resources';
import type { Config } from '../../types';
import type { Repository } from '../domain/repository';
Expand Down Expand Up @@ -30,6 +30,23 @@ export default function components(
res.set(results[0].headers);
};

const setCookies = (
results: GetComponentResult[] | undefined,
res: Response
) => {
if (!results || results.length !== 1 || !results[0] || !res.cookie) {
return;
}
const cookies = results[0].cookies;
if (Array.isArray(cookies) && cookies.length > 0) {
for (const cookie of cookies) {
const opts: CookieOptions = (cookie.options ?? {}) as CookieOptions;
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
res.cookie(cookie.name, cookie.value as any, opts);
}
}
};

return (req: Request, res: Response) => {
const components = req.body.components as Component[];
const registryErrors = strings.errors.registry;
Expand Down Expand Up @@ -85,6 +102,7 @@ export default function components(
(_err: any, results: GetComponentResult[]) => {
try {
setHeaders(results, res);
setCookies(results, res);
res.status(200).json(results);
} catch (e) {
// @ts-ignore I think this will never reach (how can setHeaders throw?)
Expand Down
25 changes: 25 additions & 0 deletions src/registry/routes/helpers/get-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Domain from 'node:domain';
import type { IncomingHttpHeaders } from 'node:http';
import vm from 'node:vm';
import acceptLanguageParser from 'accept-language-parser';
import type { CookieOptions } from 'express';
import Cache from 'nice-cache';
import Client from 'oc-client';
import emptyResponseHandler from 'oc-empty-response-handler';
Expand Down Expand Up @@ -43,6 +44,11 @@ export interface RendererOptions {
export interface GetComponentResult {
status: number;
headers?: Record<string, string>;
cookies?: Array<{
name: string;
value: any;
options?: CookieOptions;
}>;
response: {
data?: any;
type?: string;
Expand Down Expand Up @@ -143,6 +149,11 @@ export default function getComponent(conf: Config, repository: Repository) {
const nestedRenderer = NestedRenderer(renderer, options.conf);
const retrievingInfo = GetComponentRetrievingInfo(options);
let responseHeaders: Record<string, string> = {};
const responseCookies: Array<{
name: string;
value: any;
options?: CookieOptions;
}> = [];

const getLanguage = () => {
const paramOverride =
Expand All @@ -151,6 +162,9 @@ export default function getComponent(conf: Config, repository: Repository) {
};

const callback = (result: GetComponentResult) => {
if (responseCookies.length > 0 && !result.cookies) {
result.cookies = responseCookies;
}
if (result.response.error) {
retrievingInfo.extend(result.response);
}
Expand Down Expand Up @@ -578,6 +592,17 @@ export default function getComponent(conf: Config, repository: Repository) {
responseHeaders[header.toLowerCase()] = value;
}
},
setCookie: (
name?: string,
value?: any,
options?: CookieOptions
) => {
if (typeof name !== 'string') {
throw strings.errors.registry
.COMPONENT_SET_COOKIE_PARAMETERS_NOT_VALID;
}
responseCookies.push({ name, value, options });
},
templates: repository.getTemplatesInfo(),
streamSymbol: stream
};
Expand Down
2 changes: 2 additions & 0 deletions src/resources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ export default {
COMPONENT_VERSION_NOT_VALID_CODE: 'version_not_valid',
COMPONENT_SET_HEADER_PARAMETERS_NOT_VALID:
'context.setHeader parameters must be strings',
COMPONENT_SET_COOKIE_PARAMETERS_NOT_VALID:
'context.setCookie parameters are not valid',
CONFIGURATION_DEPENDENCIES_MUST_BE_ARRAY:
'Registry configuration is not valid: dependencies must be an array',
CONFIGURATION_EMPTY: 'Registry configuration is empty',
Expand Down
59 changes: 59 additions & 0 deletions test/acceptance/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,27 @@ describe('registry', () => {
});
});

describe('GET /hello-world-custom-cookies', () => {
describe('with the default configuration and strong version 1.0.0', () => {
before((done) => {
next(
request('http://localhost:3030/hello-world-custom-cookies/1.0.0'),
done
);
});

it('should return the component and set cookies', () => {
expect(result.version).to.equal('1.0.0');
expect(result.name).to.equal('hello-world-custom-cookies');
expect(result.headers).to.be.undefined;
const setCookie = headers['set-cookie'];
expect(setCookie).to.be.an('array');
expect(setCookie.join(';')).to.contain('Test-Cookie=Cookie-Value');
expect(setCookie.join(';')).to.contain('Another-Cookie=Another-Value');
});
});
});

describe('POST /hello-world-custom-headers', () => {
describe('with the default configuration (no customHeadersToSkipOnWeakVersion defined) and strong version 1.0.0', () => {
before((done) => {
Expand Down Expand Up @@ -351,6 +372,43 @@ describe('registry', () => {
});
});

describe('POST /hello-world-custom-cookies', () => {
describe('with the default configuration and strong version 1.0.0', () => {
before((done) => {
next(
request('http://localhost:3030', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
components: [
{
name: 'hello-world-custom-cookies',
version: '1.0.0'
}
]
})
}),
done
);
});

it('should set HTTP cookies', () => {
const setCookie = headers['set-cookie'];
expect(setCookie).to.be.an('array');
expect(setCookie.join(';')).to.contain('Test-Cookie=Cookie-Value');
expect(setCookie.join(';')).to.contain('Another-Cookie=Another-Value');
});

it('should return the component and keep headers in body the same', () => {
expect(result[0].response.version).to.equal('1.0.0');
expect(result[0].response.name).to.equal('hello-world-custom-cookies');
expect(result[0].headers).to.be.deep.equal({});
});
});
});

describe('GET /', () => {
before((done) => {
next(request('http://localhost:3030'), done);
Expand All @@ -368,6 +426,7 @@ describe('registry', () => {
'http://localhost:3030/empty',
'http://localhost:3030/handlebars3-component',
'http://localhost:3030/hello-world',
'http://localhost:3030/hello-world-custom-cookies',
'http://localhost:3030/hello-world-custom-headers',
'http://localhost:3030/jade-filters',
'http://localhost:3030/language',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "hello-world-custom-cookies",
"description": "",
"version": "1.0.0",
"repository": "",
"oc": {
"files": {
"template": {
"type": "handlebars",
"hashKey": "dbf1f0cb2ae6a52f402b26dff36d5bd696519933",
"src": "template.js",
"version": "6.0.25",
"size": 187
},
"dataProvider": {
"type": "node.js",
"hashKey": "ef8a15013fac7073b79cc9b21113f1d699ee83ae",
"src": "server.js",
"size": 334
},
"static": []
},
"version": "0.50.27",
"packaged": true,
"date": 1756543269656
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions test/fixtures/components/hello-world-custom-cookies/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "hello-world-custom-cookies",
"description": "",
"version": "1.0.0",
"repository": "",
"oc": {
"files": {
"data": "server.js",
"template": {
"src": "template.html",
"type": "handlebars"
}
}
}
}


9 changes: 9 additions & 0 deletions test/fixtures/components/hello-world-custom-cookies/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

module.exports.data = function (context, callback) {
context.setCookie('Test-Cookie', 'Cookie-Value');
context.setCookie('Another-Cookie', 'Another-Value', { httpOnly: true });
callback(null, {});
};


Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello world!
1 change: 1 addition & 0 deletions test/unit/registry-domain-repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ describe('registry : domain : repository', () => {
'empty',
'handlebars3-component',
'hello-world',
'hello-world-custom-cookies',
'hello-world-custom-headers',
'jade-filters',
'language',
Expand Down