diff --git a/package-lock.json b/package-lock.json index f336fa28..03ee0f24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25827,7 +25827,6 @@ "@mongodb-js/tsconfig-devtools": "^1.0.1", "@types/chai": "^4.2.21", "@types/mocha": "^9.1.1", - "@types/node": "^17.0.35", "@types/react": "^17.0.53", "@types/react-dom": "^17.0.19", "@types/sinon-chai": "^3.2.5", @@ -25840,8 +25839,6 @@ "prettier": "^2.3.2", "react": "^17.0.2", "react-dom": "^17.0.2", - "sinon-chai": "^3.7.0", - "ts-sinon": "^2.0.1", "typescript": "^5.0.4" } }, @@ -31734,7 +31731,6 @@ "@mongodb-js/tsconfig-devtools": "^1.0.1", "@types/chai": "^4.2.21", "@types/mocha": "^9.1.1", - "@types/node": "^17.0.35", "@types/react": "^17.0.53", "@types/react-dom": "^17.0.19", "@types/sinon-chai": "^3.2.5", @@ -31747,8 +31743,6 @@ "prettier": "^2.3.2", "react": "^17.0.2", "react-dom": "^17.0.2", - "sinon-chai": "^3.7.0", - "ts-sinon": "^2.0.1", "typescript": "^5.0.4" }, "dependencies": { diff --git a/packages/devtools-connect/package.json b/packages/devtools-connect/package.json index ba550044..74e8c1c0 100644 --- a/packages/devtools-connect/package.json +++ b/packages/devtools-connect/package.json @@ -47,7 +47,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@mongodb-js/oidc-http-server-pages": "0.1.1", + "@mongodb-js/oidc-http-server-pages": "1.0.0", "lodash.merge": "^4.6.2", "mongodb-connection-string-url": "^3.0.0", "socks": "^2.7.3", diff --git a/packages/devtools-connect/src/oidc/handler.ts b/packages/devtools-connect/src/oidc/handler.ts index 57138948..543128a5 100644 --- a/packages/devtools-connect/src/oidc/handler.ts +++ b/packages/devtools-connect/src/oidc/handler.ts @@ -1,9 +1,8 @@ import type { RedirectServerRequestInfo } from '@mongodb-js/oidc-plugin'; import type { DevtoolsConnectOptions } from '../connect'; import { - OIDCAcceptedPage, - OIDCNotFoundPage, - OIDCErrorPage, + getStaticPage, + HttpServerPage, } from '@mongodb-js/oidc-http-server-pages'; export function oidcServerRequestHandler( @@ -31,13 +30,31 @@ export function oidcServerRequestHandler( switch (result) { case 'accepted': - res.end(OIDCAcceptedPage({ productDocsLink, productName })); + res.end( + getStaticPage(HttpServerPage.OIDCAcceptedPage, { + productDocsLink, + productName, + }) + ); break; case 'rejected': - res.end(OIDCErrorPage({ productDocsLink, productName, ...info })); + res.end( + getStaticPage(HttpServerPage.OIDCErrorPage, { + productDocsLink, + productName, + error: info.error, + errorDescription: info.errorDescription, + errorURI: info.errorURI, + }) + ); break; default: - res.end(OIDCNotFoundPage({ productDocsLink, productName })); + res.end( + getStaticPage(HttpServerPage.OIDCNotFoundPage, { + productDocsLink, + productName, + }) + ); break; } } diff --git a/packages/oidc-http-server-pages/package.json b/packages/oidc-http-server-pages/package.json index 64a581ca..ff213deb 100644 --- a/packages/oidc-http-server-pages/package.json +++ b/packages/oidc-http-server-pages/package.json @@ -13,7 +13,7 @@ "email": "compass@mongodb.com" }, "homepage": "https://github.com/mongodb-js/devtools-shared", - "version": "0.1.1", + "version": "1.0.0", "repository": { "type": "git", "url": "https://github.com/mongodb-js/devtools-shared.git" @@ -31,7 +31,7 @@ "scripts": { "bootstrap": "npm run compile", "prepublishOnly": "npm run compile", - "compile": "tsc -p tsconfig.json && node dist/create-static-pages.js > dist/static-pages.js && gen-esm-wrapper . ./dist/.esm-wrapper.mjs", + "compile": "tsc -p tsconfig.json && node dist/create-templates.js && gen-esm-wrapper . ./dist/.esm-wrapper.mjs", "typecheck": "tsc --noEmit", "eslint": "eslint", "prettier": "prettier", @@ -39,7 +39,7 @@ "depcheck": "depcheck", "check": "npm run typecheck && npm run lint && npm run depcheck", "check-ci": "npm run check", - "test": "mocha", + "test": "npm run compile && mocha", "test-cov": "nyc -x \"**/*.spec.*\" --reporter=lcov --reporter=text --reporter=html npm run test", "test-watch": "npm run test -- --watch", "test-ci": "npm run test-cov", @@ -55,7 +55,6 @@ "@leafygreen-ui/emotion": "^4.0.7", "@types/chai": "^4.2.21", "@types/mocha": "^9.1.1", - "@types/node": "^17.0.35", "@types/react": "^17.0.53", "@types/react-dom": "^17.0.19", "@types/sinon-chai": "^3.2.5", @@ -68,8 +67,6 @@ "prettier": "^2.3.2", "react": "^17.0.2", "react-dom": "^17.0.2", - "sinon-chai": "^3.7.0", - "ts-sinon": "^2.0.1", "typescript": "^5.0.4" } } diff --git a/packages/oidc-http-server-pages/src/create-static-pages.spec.tsx b/packages/oidc-http-server-pages/src/create-static-pages.spec.tsx deleted file mode 100644 index fad6d245..00000000 --- a/packages/oidc-http-server-pages/src/create-static-pages.spec.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import type { Component } from 'react'; -import { generateStaticPagesModule } from './create-static-pages'; -import { H1 } from '@mongodb-js/compass-components'; -import { runInNewContext } from 'vm'; -import zlib from 'zlib'; -import sinon from 'ts-sinon'; -import chai, { expect } from 'chai'; -import sinonChai from 'sinon-chai'; -chai.use(sinonChai); - -describe('generateStaticPagesModule', function () { - let exports: Record< - string, - (props: React.ComponentProps) => string - >; - - beforeEach(function () { - function Component({ prop1, prop2 }: { prop1?: string; prop2?: string }) { - return ( -
-

{prop1 || 'default'}

- {prop2 &&

{prop2}

} -
- ); - } - - const result = generateStaticPagesModule([[Component, ['prop1', 'prop2']]]); - exports = {}; - const require = sinon.stub(); - require.throws(); - require.withArgs('zlib').returns(zlib); - runInNewContext(result, { - module: { exports }, - require, - Buffer, - }); - }); - - it('includes selected props', function () { - expect(exports.Component({ prop1: 'prop1value' })).to.match( - /]+>prop1value<\/h1>/ - ); - expect(exports.Component({ prop1: 'prop1value' })).not.to.include('prop2'); - expect(exports.Component({ prop1: 'prop1value' })).not.to.include( - 'data-attr' - ); - expect(exports.Component({ prop2: 'prop2value' })).to.match( - /]+>default<\/h1>/ - ); - expect(exports.Component({ prop2: 'prop2value' })).to.match( - /]+>prop2value<\/p>/ - ); - expect(exports.Component({ prop2: 'prop2value' })).to.include( - 'data-attr="prop2value"' - ); - }); - - it('properly escapes props', function () { - expect(exports.Component({ prop2: 'prop2<&"\'>value' })).to.include( - 'data-attr="prop2<&"'>value"' - ); - expect(exports.Component({ prop2: 'prop2<&"\'>value' })).to.include( - '>prop2<&"'>value

' - ); - }); - - it('includes relevant style information', function () { - expect(exports.Component({ prop1: 'prop1value' })).to.include('(array: T[]): Iterable { - if (array.length === 0) { - yield []; - return; - } - const first = array[0]; - for (const slicedSubset of allSubsets(array.slice(1))) { - yield [first, ...slicedSubset]; - yield slicedSubset; - } -} - -function placeholder(prop: string): string { - return `{{prop:${prop}}}`; -} - -const S = JSON.stringify; // Useful for embedding strings into real JS code. - -// Generate static pages based on a component. -// Props need to be optional string props; for every subset of props, -// a static page will be rendered that takes these props, and a function -// to take that static page and replace placeholder with the actual prop values -// will be emitted. -// Rather than including the generated markup directly, it is saved to a global -// table and emitted later, so that that global table can be compressed. -function generateStaticPage( - Component: React.FunctionComponent>> & { - name: string; - }, - props: string[], - markupTable: Record -): string { - let result = `module.exports[${S(Component.name)}] = function(props) { - const actualProps = JSON.stringify( - Object.keys(props) - .filter(prop => ${S( - props - )}.includes(prop) && props[prop] != null).sort());`; - for (const availableProps of allSubsets(props)) { - const propsObject: Partial> = {}; - const replacers: string[] = []; - for (const prop of availableProps) { - propsObject[prop] = placeholder(prop); - replacers.push( - `.replaceAll(${S(placeholder(prop))}, escapeHTML(props[${S(prop)}]))` - ); - } - - const markup = renderStylesToString( - renderToStaticMarkup(React.createElement(Component, propsObject)) - ); - const markupHash = createHash('sha256').update(markup).digest('hex'); - markupTable[markupHash] = markup; - - result += `if (actualProps === ${S(S(availableProps.sort()))}) { - return (getMarkup(${S(markupHash)})${replacers.join('')}); - }`; - } - - result += '}\n'; - return result; -} - -// Create CJS module with pages as exports. -export function generateStaticPagesModule( - components: [ - React.FunctionComponent>> & { - name: string; - }, - string[] - ][] -): string { - let result = ` - 'use strict'; - function escapeHTML(str) { - return str.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); - } - `; - const markupTable: Record = {}; - for (const component of components) { - result += generateStaticPage(...component, markupTable); - } - - // At the time of writing, brotli compression results in a 96% size decrease - // in the resulting file. We decompress the markup table lazily, so that it - // is not loaded into memory when OIDC is not in use. - const compressedMarkupTable = brotliCompressSync(S(markupTable), { - params: { - [zlibConstants.BROTLI_PARAM_MODE]: zlibConstants.BROTLI_MODE_TEXT, - [zlibConstants.BROTLI_PARAM_QUALITY]: zlibConstants.BROTLI_MAX_QUALITY, - }, - }); - result += ` - const markupTableSrc = ${S(compressedMarkupTable.toString('base64'))}; - let markupTable; - function getMarkup(hash) { - if (markupTable === undefined) { - markupTable = JSON.parse( - require('zlib').brotliDecompressSync( - Buffer.from(markupTableSrc, 'base64'))); - } - return markupTable[hash]; - } - `; - return result; -} - -if (require.main === module) { - // eslint-disable-next-line no-console - console.log( - generateStaticPagesModule([ - [ - OIDCErrorPage, - [ - 'error', - 'errorDescription', - 'errorURI', - 'productDocsLink', - 'productName', - ], - ], - [OIDCAcceptedPage, ['productDocsLink', 'productName']], - [OIDCNotFoundPage, ['productDocsLink', 'productName']], - ]) - ); -} diff --git a/packages/oidc-http-server-pages/src/create-templates.spec.tsx b/packages/oidc-http-server-pages/src/create-templates.spec.tsx new file mode 100644 index 00000000..db0c47da --- /dev/null +++ b/packages/oidc-http-server-pages/src/create-templates.spec.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { expect } from 'chai'; +import { H1 } from '@mongodb-js/compass-components'; +import { generateTemplates } from './create-templates'; +import type { PageTemplates } from './types'; + +describe('generateTemplates', function () { + type TestPage = 'Page1'; + type TestPagesProps = { + Page1: { + prop1?: string; + prop2?: string; + never?: string; + }; + }; + function Component1({ prop1, prop2 }: { prop1?: string; prop2?: string }) { + return ( +
+

{prop1 || 'default'}

+ {prop2 &&

{prop2}

} +
+ ); + } + + let result: PageTemplates; + before(function () { + result = generateTemplates({ + Page1: { + Component: Component1, + parameters: ['prop1', 'prop2'], + }, + }); + }); + + it('creates 4 templates for Page1', function () { + expect(result).to.have.own.property('Page1'); + expect(result['Page1']).to.have.length(4); + }); + + it('includes a template with placeholders for prop1 + prop2', function () { + const templates = result['Page1']; + const fullTemplate = templates.find( + ({ parameters }) => + Object.keys(parameters).includes('prop1') && + Object.keys(parameters).includes('prop2') + ); + expect(fullTemplate).to.exist; + expect(fullTemplate).to.have.own.property('html'); + expect(fullTemplate?.html).to.match(/]+>{{prop:prop1}}<\/h1>/); + expect(fullTemplate?.html).to.match(/]+>{{prop:prop2}}<\/p>/); + }); + + it('includes a template with placeholders for no props', function () { + const templates = result['Page1']; + const fullTemplate = templates.find( + ({ parameters }) => Object.keys(parameters).length === 0 + ); + expect(fullTemplate).to.exist; + expect(fullTemplate).to.have.own.property('html'); + expect(fullTemplate?.html).to.match(/]+>default<\/h1>/); + expect(fullTemplate?.html).not.to.contain('{{prop:prop1}}'); + expect(fullTemplate?.html).not.to.contain('{{prop:prop2}}'); + }); +}); diff --git a/packages/oidc-http-server-pages/src/create-templates.ts b/packages/oidc-http-server-pages/src/create-templates.ts new file mode 100644 index 00000000..d44225a3 --- /dev/null +++ b/packages/oidc-http-server-pages/src/create-templates.ts @@ -0,0 +1,169 @@ +// Helper script to prepare templates of the +// OIDC plugin pages. This script is run during package compilation. +// +// By generating static pages, we can avoid having all dependencies +// of this package pulling in react, react-dom, leafygreen, compass-components, etc. +// +// Furthermore, the templates are consumable by other languages +// https://jira.mongodb.org/browse/COMPASS-7646 +// + +import { renderToStaticMarkup } from 'react-dom/server'; +import { renderStylesToString } from '@leafygreen-ui/emotion'; +import { + OIDCAcceptedPage, + OIDCErrorPage, + OIDCNotFoundPage, +} from './pages-source'; +import React from 'react'; +import { gzipSync, brotliCompressSync, constants as zlibConstants } from 'zlib'; +import { writeFileSync } from 'fs'; +import path from 'path'; +import type { PageTemplates, ITemplate, HttpServerPageProps } from './types'; +import { HttpServerPage } from './types'; + +/** Iterate all sub-arrays of an array */ +function* allSubsets(array: T[]): Iterable { + if (array.length === 0) { + yield []; + return; + } + const first = array[0]; + for (const slicedSubset of allSubsets(array.slice(1))) { + yield [first, ...slicedSubset]; + yield slicedSubset; + } +} + +function placeholder(prop: string): string { + return `{{prop:${prop}}}`; +} + +type Component = React.FunctionComponent>> & { + name: string; +}; + +function getPageTemplates>({ + Component, + parameters, +}: { + Component: Component; + parameters: (string & keyof TParameters)[]; +}): ITemplate[] { + const templates: ITemplate[] = []; + for (const paramsSubset of allSubsets(parameters)) { + const propsObject = Object.fromEntries( + paramsSubset.map((prop) => [prop, placeholder(prop)]) + ); + const markup = renderStylesToString( + renderToStaticMarkup(React.createElement(Component, propsObject)) + ); + templates.push({ + parameters: propsObject as TParameters, + html: markup, + }); + } + return templates; +} + +export function generateTemplates< + TPageParameters extends Record< + string, + Record + > = HttpServerPageProps, + TPage extends string & keyof TPageParameters = string & keyof TPageParameters +>( + pages: Record< + TPage, + { + Component: Component; + parameters: string[]; + } + > +): PageTemplates { + const templates: Partial> = {}; + for (const pageName of Object.keys(pages) as TPage[]) { + const { Component, parameters } = pages[pageName]; + const PageTemplates = getPageTemplates({ + Component, + parameters, + }); + templates[pageName] = PageTemplates; + } + return templates as PageTemplates; +} + +function generateGzip(data: string): void { + const buffer = gzipSync(data); + writeFileSync(path.join(__dirname, 'templates.gz'), buffer, 'binary'); +} + +function generateJS(data: string): void { + const buffer = brotliCompressSync(data, { + params: { + [zlibConstants.BROTLI_PARAM_MODE]: zlibConstants.BROTLI_MODE_TEXT, + [zlibConstants.BROTLI_PARAM_QUALITY]: zlibConstants.BROTLI_MAX_QUALITY, + }, + }); + writeFileSync( + path.join(__dirname, 'get-templates.js'), + ` + const { brotliDecompressSync } = require('zlib'); + function getTemplates() { + const buffer = brotliDecompressSync( + Buffer.from( + '${buffer.toString('base64')}', + 'base64' + ) + ); + return JSON.parse(buffer.toString()); + } + module.exports = getTemplates; + ` + ); +} + +export function generateCompressedTemplates< + TPageParameters extends Record< + string, + Record + > = HttpServerPageProps, + TPage extends string & keyof TPageParameters = string & keyof TPageParameters +>( + pages: Record< + TPage, + { + Component: Component; + parameters: string[]; + } + > +): void { + const templates = JSON.stringify( + generateTemplates(pages) + ); + generateGzip(templates); + generateJS(templates); +} + +if (require.main === module) { + generateCompressedTemplates({ + [HttpServerPage.OIDCErrorPage]: { + Component: OIDCErrorPage, + parameters: [ + 'error', + 'errorDescription', + 'errorURI', + 'productDocsLink', + 'productName', + ], + }, + [HttpServerPage.OIDCAcceptedPage]: { + Component: OIDCAcceptedPage, + parameters: ['productDocsLink', 'productName'], + }, + [HttpServerPage.OIDCNotFoundPage]: { + Component: OIDCNotFoundPage, + parameters: ['productDocsLink', 'productName'], + }, + }); +} diff --git a/packages/oidc-http-server-pages/src/get-static-page.spec.ts b/packages/oidc-http-server-pages/src/get-static-page.spec.ts new file mode 100644 index 00000000..34240d47 --- /dev/null +++ b/packages/oidc-http-server-pages/src/get-static-page.spec.ts @@ -0,0 +1,87 @@ +import { expect } from 'chai'; +import type { PageTemplates } from './types'; +import { getStaticPage } from '../dist/get-static-page'; + +describe('getStaticPage', function () { + type TestPage = 'Page1'; + type TestPagesProps = { + Page1: { + prop1?: string; + prop2?: string; + never?: string; + }; + }; + + const templates: PageTemplates = { + Page1: [ + { + parameters: { prop1: '{{prop:prop1}}', prop2: '{{prop:prop2}}' }, + html: '

{{prop:prop1}}

{{prop:prop2}}', + }, + { + parameters: { prop1: '{{prop:prop1}}' }, + html: '

{{prop:prop1}}

', + }, + { + parameters: { prop2: '{{prop:prop2}}' }, + html: '

default

{{prop:prop2}}', + }, + { + parameters: {}, + html: '

default

', + }, + ], + }; + + it('scenario: prop1 + prop2', function () { + const prop1 = 'abc'; + const prop2 = 'def'; + const html = getStaticPage( + 'Page1', + { prop1, prop2 }, + templates + ); + expect(html).to.equal(`

${prop1}

${prop2}`); + }); + + it('scenario: no props', function () { + const html = getStaticPage( + 'Page1', + {}, + templates + ); + expect(html).to.equal('

default

'); + }); + + it('scenario: prop1 with special characters', function () { + const prop1 = `'one' & "two" & `; + const html = getStaticPage( + 'Page1', + { prop1 }, + templates + ); + expect(html).to.equal( + '

'one' & "two" & <three>

' + ); + }); + + it('no page template is found', function () { + expect(() => + getStaticPage( + 'UnknownPage' as TestPage, + {}, + templates + ) + ).to.throw(Error, /No template found/); + }); + + it('throws with incorrect properties', function () { + expect(() => + getStaticPage( + 'UnknownPage' as TestPage, + { never: 'no' }, + templates + ) + ).to.throw(Error, /No template found/); + }); +}); diff --git a/packages/oidc-http-server-pages/src/get-static-page.ts b/packages/oidc-http-server-pages/src/get-static-page.ts new file mode 100644 index 00000000..7cc6a3b4 --- /dev/null +++ b/packages/oidc-http-server-pages/src/get-static-page.ts @@ -0,0 +1,71 @@ +import type { ITemplate, PageTemplates, HttpServerPageProps } from './types'; +import getTemplates from './get-templates.js'; + +function findTemplate( + templates: ITemplate[], + parameterKeys: string[] +): ITemplate | undefined { + const parametersJoined = parameterKeys.sort().join('-'); + for (const template of templates) { + const templateParametersJoined = Object.keys(template.parameters) + .sort() + .join('-'); + if (parametersJoined === templateParametersJoined) return template; + } +} + +function replacePlaceholders( + template: ITemplate>, + parameters: Record +): string { + let { html } = template; + for (const [key, placeholder] of Object.entries(template.parameters)) { + html = html.replaceAll(placeholder, escapeHTML(parameters[key])); + } + return html; +} + +function escapeHTML(str: string): string { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +export function getStaticPage< + TPageParameters extends Record< + string, + Record + > = HttpServerPageProps, + TPage extends string & keyof TPageParameters = string & keyof TPageParameters +>( + page: TPage, + parameters: TPageParameters[TPage], + templates?: PageTemplates +): string { + if (!templates) { + templates = getTemplates() as PageTemplates; + } + + const pageTemplates = templates && templates[page]; + if (!pageTemplates) { + throw new Error(`No template found for ${page}`); + } + + const nonEmptyParameters = Object.keys(parameters).filter( + (key) => typeof parameters[key] !== 'undefined' && parameters !== null + ); + const template = findTemplate(pageTemplates, nonEmptyParameters); + if (!template) { + throw new Error( + `No template found for ${page}; parameters: ${Object.keys( + parameters + ).join(',')}. The parameters might be incorrect.` + ); + } + + const html = replacePlaceholders(template, parameters); + return html; +} diff --git a/packages/oidc-http-server-pages/src/get-templates.js.d.ts b/packages/oidc-http-server-pages/src/get-templates.js.d.ts new file mode 100644 index 00000000..545f403c --- /dev/null +++ b/packages/oidc-http-server-pages/src/get-templates.js.d.ts @@ -0,0 +1,5 @@ +import type { PageTemplates } from './types'; + +declare const TGetTemplates: () => PageTemplates; + +export default TGetTemplates; diff --git a/packages/oidc-http-server-pages/src/index.ts b/packages/oidc-http-server-pages/src/index.ts index a8f0610f..c69fb20c 100644 --- a/packages/oidc-http-server-pages/src/index.ts +++ b/packages/oidc-http-server-pages/src/index.ts @@ -1,6 +1,2 @@ -export { - OIDCPageBaseProps, - OIDCAcceptedPage, - OIDCNotFoundPage, - OIDCErrorPage, -} from './static-pages'; +export { getStaticPage } from './get-static-page'; +export { HttpServerPage } from './types'; diff --git a/packages/oidc-http-server-pages/src/pages-source.tsx b/packages/oidc-http-server-pages/src/pages-source.tsx index 48803dbe..0c4a9d0c 100644 --- a/packages/oidc-http-server-pages/src/pages-source.tsx +++ b/packages/oidc-http-server-pages/src/pages-source.tsx @@ -56,10 +56,10 @@ const failureIconStyles = css({ marginBottom: spacing[3] + spacing[1], }); -export interface OIDCPageBaseProps { +export type OIDCPageBaseProps = { productDocsLink?: string; productName?: string; -} +}; function DocsLink({ productDocsLink, @@ -101,7 +101,9 @@ function PageContainer({ ); } -export function OIDCAcceptedPage(baseProps: OIDCPageBaseProps): JSX.Element { +export type OIDCAcceptedPageProps = OIDCPageBaseProps; + +export function OIDCAcceptedPage(props: OIDCAcceptedPageProps): JSX.Element { return (

Login Successful

You can close this window now. - +
); } -export function OIDCNotFoundPage(baseProps: OIDCPageBaseProps): JSX.Element { +export type OIDCNotFoundPageProps = OIDCPageBaseProps; + +export function OIDCNotFoundPage(props: OIDCNotFoundPageProps): JSX.Element { return (

Page Not Found

This page is not available. - +
); } +export type OIDCErrorPageProps = { + error?: string; + errorDescription?: string; + errorURI?: string; +} & OIDCPageBaseProps; + export function OIDCErrorPage({ error, errorDescription, errorURI, ...baseProps -}: { - error?: string; - errorDescription?: string; - errorURI?: string; -} & OIDCPageBaseProps): JSX.Element { +}: OIDCErrorPageProps): JSX.Element { return ( diff --git a/packages/oidc-http-server-pages/src/types.ts b/packages/oidc-http-server-pages/src/types.ts new file mode 100644 index 00000000..591bfd46 --- /dev/null +++ b/packages/oidc-http-server-pages/src/types.ts @@ -0,0 +1,32 @@ +import type { + OIDCAcceptedPageProps, + OIDCErrorPageProps, + OIDCNotFoundPageProps, +} from './pages-source'; +export interface ITemplate< + TParameters extends Record = Record +> { + parameters: TParameters; + html: string; +} + +export enum HttpServerPage { + OIDCErrorPage = 'OIDCErrorPage', + OIDCAcceptedPage = 'OIDCAcceptedPage', + OIDCNotFoundPage = 'OIDCNotFoundPage', +} + +export type HttpServerPageProps = { + [HttpServerPage.OIDCErrorPage]: OIDCErrorPageProps; + [HttpServerPage.OIDCAcceptedPage]: OIDCAcceptedPageProps; + [HttpServerPage.OIDCNotFoundPage]: OIDCNotFoundPageProps; +}; + +export type PageTemplates< + TPageProps extends Record< + string, + Record + > = HttpServerPageProps +> = { + [Key in keyof TPageProps]: ITemplate[]; +};